Merge "Remove alanstokes@google.com from fsverity/OWNERS" into main
diff --git a/OWNERS b/OWNERS
index 6fdb550..b1c1fea 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,11 +1,7 @@
-alanstokes@google.com
drysdale@google.com
-eranm@google.com
hasinitg@google.com
jbires@google.com
jeffv@google.com
-kroot@google.com
sethmo@google.com
swillden@google.com
-trong@google.com
zeuthen@google.com
diff --git a/fsverity/fsverity_manifest_generator.py b/fsverity/fsverity_manifest_generator.py
index ca7ac5c..d232450 100644
--- a/fsverity/fsverity_manifest_generator.py
+++ b/fsverity/fsverity_manifest_generator.py
@@ -31,7 +31,7 @@
cmd = [fsverity_path, 'digest', input_file]
cmd.extend(['--compact'])
cmd.extend(['--hash-alg', HASH_ALGORITHM])
- out = subprocess.check_output(cmd, universal_newlines=True).strip()
+ out = subprocess.check_output(cmd, text=True).strip()
return bytes(bytearray.fromhex(out))
if __name__ == '__main__':
@@ -46,22 +46,50 @@
required=True)
p.add_argument(
'--base-dir',
- help='directory to use as a relative root for the inputs',
- required=True)
+ help='directory to use as a relative root for the inputs. Also see the documentation of '
+ 'inputs')
p.add_argument(
'inputs',
nargs='*',
- help='input file for the build manifest')
+ help='input file for the build manifest. It can be in either of two forms: <file> or '
+ '<file>,<path_on_device>. If the first form is used, --base-dir must be provided, and the '
+ 'path on device will be the filepath relative to the base dir')
args = p.parse_args()
+ links = {}
digests = FSVerityDigests()
for f in sorted(args.inputs):
- # f is a full path for now; make it relative so it starts with {mount_point}/
- digest = digests.digests[os.path.relpath(f, args.base_dir)]
- digest.digest = _digest(args.fsverity_path, f)
- digest.hash_alg = HASH_ALGORITHM
+ if args.base_dir:
+ # f is a full path for now; make it relative so it starts with {mount_point}/
+ rel = os.path.relpath(f, args.base_dir)
+ else:
+ parts = f.split(',')
+ if len(parts) != 2 or not parts[0] or not parts[1]:
+ sys.exit("Since --base-path wasn't provided, all inputs must be pairs separated by commas "
+ "but this input wasn't: " + f)
+ f, rel = parts
- manifest = digests.SerializeToString()
+ # Some fsv_meta files are links to other ones. Don't read through the link, because the
+ # layout of files in the build system may not match the layout of files on the device.
+ # Instead, read its target and use it to copy the digest from the real file after all files
+ # are processed.
+ if os.path.islink(f):
+ links[rel] = os.path.normpath(os.path.join(os.path.dirname(rel), os.readlink(f)))
+ else:
+ digest = digests.digests[rel]
+ digest.digest = _digest(args.fsverity_path, f)
+ digest.hash_alg = HASH_ALGORITHM
+
+ for link_rel, real_rel in links.items():
+ if real_rel not in digests.digests:
+ sys.exit(f'There was a fsv_meta symlink to {real_rel}, but that file was not a fsv_meta file')
+ link_digest = digests.digests[link_rel]
+ real_digest = digests.digests[real_rel]
+ link_digest.CopyFrom(real_digest)
+
+ # Serialize with deterministic=True for reproducible builds and build caching.
+ # The serialized contents will still change across different versions of protobuf.
+ manifest = digests.SerializeToString(deterministic=True)
with open(args.output, "wb") as f:
f.write(manifest)
diff --git a/fsverity_init/Android.bp b/fsverity_init/Android.bp
deleted file mode 100644
index 212aac4..0000000
--- a/fsverity_init/Android.bp
+++ /dev/null
@@ -1,41 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "system_security_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["system_security_license"],
-}
-
-cc_binary {
- name: "fsverity_init",
- srcs: [
- "fsverity_init.cpp",
- ],
- static_libs: [
- "aconfig_fsverity_init_c_lib",
- "libmini_keyctl_static",
- ],
- shared_libs: [
- "libbase",
- "libkeyutils",
- "liblog",
- ],
- cflags: [
- "-Werror",
- "-Wall",
- "-Wextra",
- ],
-}
-
-aconfig_declarations {
- name: "aconfig_fsverity_init",
- package: "android.security.flag",
- container: "system",
- srcs: ["flags.aconfig"],
-}
-
-cc_aconfig_library {
- name: "aconfig_fsverity_init_c_lib",
- aconfig_declarations: "aconfig_fsverity_init",
-}
diff --git a/fsverity_init/OWNERS b/fsverity_init/OWNERS
deleted file mode 100644
index f9e7b25..0000000
--- a/fsverity_init/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-alanstokes@google.com
-ebiggers@google.com
-jeffv@google.com
-jiyong@google.com
-victorhsieh@google.com
diff --git a/fsverity_init/flags.aconfig b/fsverity_init/flags.aconfig
deleted file mode 100644
index 495c71c..0000000
--- a/fsverity_init/flags.aconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-package: "android.security.flag"
-container: "system"
-
-flag {
- name: "deprecate_fsverity_init"
- namespace: "hardware_backed_security"
- description: "Feature flag for deprecate fsverity_init"
- bug: "290064770"
- is_fixed_read_only: true
-}
diff --git a/fsverity_init/fsverity_init.cpp b/fsverity_init/fsverity_init.cpp
deleted file mode 100644
index 717beeb..0000000
--- a/fsverity_init/fsverity_init.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// fsverity_init is a tool for loading X.509 certificates into the kernel keyring used by the
-// fsverity builtin signature verification kernel feature
-// (https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#built-in-signature-verification).
-// Starting in Android 14, Android has actually stopped using this feature, as it was too inflexible
-// and caused problems. It has been replaced by userspace signature verification. Also, some uses
-// of fsverity in Android are now for integrity-only use cases.
-//
-// Regardless, there may exist fsverity files on-disk that were created by Android 13 or earlier.
-// These files still have builtin signatures. If the kernel is an older kernel that still has
-// CONFIG_FS_VERITY_BUILTIN_SIGNATURES enabled, these files cannot be opened unless the
-// corresponding key is in the ".fs-verity" keyring. Therefore, this tool still has to exist and be
-// used to load keys into the kernel, even though this has no security purpose anymore.
-//
-// This tool can be removed as soon as all supported kernels are guaranteed to have
-// CONFIG_FS_VERITY_BUILTIN_SIGNATURES disabled, or alternatively as soon as support for upgrades
-// from Android 13 or earlier is no longer required.
-//
-
-#define LOG_TAG "fsverity_init"
-
-#include <sys/types.h>
-
-#include <filesystem>
-#include <string>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <android_security_flag.h>
-#include <log/log.h>
-#include <mini_keyctl_utils.h>
-
-void LoadKeyFromFile(key_serial_t keyring_id, const char* keyname, const std::string& path) {
- LOG(INFO) << "LoadKeyFromFile path=" << path << " keyname=" << keyname;
- std::string content;
- if (!android::base::ReadFileToString(path, &content)) {
- LOG(ERROR) << "Failed to read key from " << path;
- return;
- }
- if (add_key("asymmetric", keyname, content.c_str(), content.size(), keyring_id) < 0) {
- PLOG(ERROR) << "Failed to add key from " << path;
- }
-}
-
-void LoadKeyFromDirectory(key_serial_t keyring_id, const char* keyname_prefix, const char* dir) {
- if (!std::filesystem::exists(dir)) {
- return;
- }
- int counter = 0;
- for (const auto& entry : std::filesystem::directory_iterator(dir)) {
- if (!android::base::EndsWithIgnoreCase(entry.path().c_str(), ".der")) continue;
- std::string keyname = keyname_prefix + std::to_string(counter);
- counter++;
- LoadKeyFromFile(keyring_id, keyname.c_str(), entry.path());
- }
-}
-
-void LoadKeyFromVerifiedPartitions(key_serial_t keyring_id) {
- // NB: Directories need to be synced with FileIntegrityService.java in
- // frameworks/base.
- LoadKeyFromDirectory(keyring_id, "fsv_system_", "/system/etc/security/fsverity");
- LoadKeyFromDirectory(keyring_id, "fsv_product_", "/product/etc/security/fsverity");
-}
-
-int main(int argc, const char** argv) {
- if (android::security::flag::deprecate_fsverity_init()) {
- // Don't load keys to the built-in fs-verity keyring in kernel. This will make existing
- // files not readable. We expect to only enable the flag when there are no such files or
- // when failure is ok (e.g. with a fallback).
- return 0;
- }
-
- if (argc < 2) {
- LOG(ERROR) << "Not enough arguments";
- return -1;
- }
-
- key_serial_t keyring_id = android::GetKeyringId(".fs-verity");
- if (keyring_id < 0) {
- // This is expected on newer kernels. See comment at the beginning of this file.
- LOG(DEBUG) << "no initialization required";
- return 0;
- }
-
- const std::string_view command = argv[1];
-
- if (command == "--load-verified-keys") {
- LoadKeyFromVerifiedPartitions(keyring_id);
- } else {
- LOG(ERROR) << "Unknown argument(s).";
- return -1;
- }
-
- return 0;
-}
diff --git a/keystore-engine/Android.bp b/keystore-engine/Android.bp
index 7fbfe53..d763445 100644
--- a/keystore-engine/Android.bp
+++ b/keystore-engine/Android.bp
@@ -27,7 +27,8 @@
name: "libkeystore-engine",
defaults: [
- "keystore2_use_latest_aidl_ndk_shared",
+ "keymint_use_latest_hal_aidl_ndk_static",
+ "keystore2_use_latest_aidl_ndk_static",
],
srcs: [
"android_engine.cpp",
@@ -41,7 +42,6 @@
],
shared_libs: [
- "android.system.keystore2-V4-ndk",
"libbinder_ndk",
"libcrypto",
"libcutils",
diff --git a/keystore/keystore_attestation_id.cpp b/keystore/keystore_attestation_id.cpp
index bcd3318..c91f86f 100644
--- a/keystore/keystore_attestation_id.cpp
+++ b/keystore/keystore_attestation_id.cpp
@@ -21,6 +21,7 @@
#include <log/log.h>
#include <memory>
+#include <mutex>
#include <string>
#include <vector>
@@ -51,6 +52,7 @@
constexpr const char* kAttestationSystemPackageName = "AndroidSystem";
constexpr const size_t kMaxAttempts = 3;
constexpr const unsigned long kRetryIntervalUsecs = 500000; // sleep for 500 ms
+constexpr const char* kProviderServiceName = "sec_key_att_app_id_provider";
std::vector<uint8_t> signature2SHA256(const security::keystore::Signature& sig) {
std::vector<uint8_t> digest_buffer(SHA256_DIGEST_LENGTH);
@@ -61,24 +63,24 @@
using ::aidl::android::system::keystore2::ResponseCode;
using ::android::security::keystore::BpKeyAttestationApplicationIdProvider;
-class KeyAttestationApplicationIdProvider : public BpKeyAttestationApplicationIdProvider {
- public:
- KeyAttestationApplicationIdProvider();
+[[clang::no_destroy]] std::mutex gServiceMu;
+[[clang::no_destroy]] std::shared_ptr<BpKeyAttestationApplicationIdProvider>
+ gService; // GUARDED_BY gServiceMu
- static KeyAttestationApplicationIdProvider& get();
-
- private:
- android::sp<android::IServiceManager> service_manager_;
-};
-
-KeyAttestationApplicationIdProvider& KeyAttestationApplicationIdProvider::get() {
- static KeyAttestationApplicationIdProvider mpm;
- return mpm;
+std::shared_ptr<BpKeyAttestationApplicationIdProvider> get_service() {
+ std::lock_guard<std::mutex> guard(gServiceMu);
+ if (gService.get() == nullptr) {
+ gService = std::make_shared<BpKeyAttestationApplicationIdProvider>(
+ android::defaultServiceManager()->waitForService(String16(kProviderServiceName)));
+ }
+ return gService;
}
-KeyAttestationApplicationIdProvider::KeyAttestationApplicationIdProvider()
- : BpKeyAttestationApplicationIdProvider(android::defaultServiceManager()->waitForService(
- String16("sec_key_att_app_id_provider"))) {}
+void reset_service() {
+ std::lock_guard<std::mutex> guard(gServiceMu);
+ // Drop the global reference; any thread that already has a reference can keep using it.
+ gService.reset();
+}
DECLARE_STACK_OF(ASN1_OCTET_STRING);
@@ -281,23 +283,31 @@
key_attestation_id.packageInfos.push_back(std::move(pinfo));
} else {
/* Get the attestation application ID from package manager */
- auto& pm = KeyAttestationApplicationIdProvider::get();
::android::binder::Status status;
- // Retry on failure if a service specific error code.
+ // Retry on failure.
for (size_t attempt{0}; attempt < kMaxAttempts; ++attempt) {
- status = pm.getKeyAttestationApplicationId(uid, &key_attestation_id);
+ auto pm = get_service();
+ status = pm->getKeyAttestationApplicationId(uid, &key_attestation_id);
if (status.isOk()) {
break;
- } else if (status.exceptionCode() != binder::Status::EX_SERVICE_SPECIFIC) {
- ALOGW("Retry: key attestation ID failed with service specific error: %s %d",
- status.exceptionMessage().c_str(), status.serviceSpecificErrorCode());
- usleep(kRetryIntervalUsecs);
- } else {
- ALOGW("Retry: key attestation ID failed with error: %s %d",
- status.exceptionMessage().c_str(), status.exceptionCode());
- usleep(kRetryIntervalUsecs);
}
+
+ if (status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) {
+ ALOGW("Retry: get attestation ID for %d failed with service specific error: %s %d",
+ uid, status.exceptionMessage().c_str(), status.serviceSpecificErrorCode());
+ } else if (status.exceptionCode() == binder::Status::EX_TRANSACTION_FAILED) {
+ // If the transaction failed, drop the package manager connection so that the next
+ // attempt will try again.
+ ALOGW(
+ "Retry: get attestation ID for %d transaction failed, reset connection: %s %d",
+ uid, status.exceptionMessage().c_str(), status.exceptionCode());
+ reset_service();
+ } else {
+ ALOGW("Retry: get attestation ID for %d failed with error: %s %d", uid,
+ status.exceptionMessage().c_str(), status.exceptionCode());
+ }
+ usleep(kRetryIntervalUsecs);
}
if (!status.isOk()) {
diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp
index ab3e22c..d442e48 100644
--- a/keystore/keystore_cli_v2.cpp
+++ b/keystore/keystore_cli_v2.cpp
@@ -22,11 +22,12 @@
#include <variant>
#include <vector>
+#include <android-base/strings.h>
+
#include <base/command_line.h>
#include <base/files/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
-#include <base/strings/string_util.h>
#include <aidl/android/security/apc/BnConfirmationCallback.h>
#include <aidl/android/security/apc/IProtectedConfirmation.h>
@@ -705,12 +706,12 @@
std::vector<TestCase> test_cases = GetTestCases();
for (const auto& test_case : test_cases) {
if (!prefix.empty() &&
- !base::StartsWith(test_case.name, prefix, base::CompareCase::SENSITIVE)) {
+ !android::base::StartsWith(test_case.name, prefix)) {
continue;
}
if (test_for_0_3 &&
- (base::StartsWith(test_case.name, "AES", base::CompareCase::SENSITIVE) ||
- base::StartsWith(test_case.name, "HMAC", base::CompareCase::SENSITIVE))) {
+ (android::base::StartsWith(test_case.name, "AES") ||
+ android::base::StartsWith(test_case.name, "HMAC"))) {
continue;
}
++test_count;
@@ -1016,8 +1017,7 @@
return 1;
}
- std::vector<std::string> pieces =
- base::SplitString(uiOptionsStr, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ std::vector<std::string> pieces = android::base::Tokenize(uiOptionsStr, ",");
int uiOptionsAsFlags = 0;
for (auto& p : pieces) {
int value;
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index ed9cd88..4878eb3 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_android_hardware_backed_security",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_security_license"
@@ -30,7 +31,10 @@
"keystore2_use_latest_aidl_rust",
"structured_log_rust_defaults",
],
-
+ cfgs: select(release_flag("RELEASE_AVF_ENABLE_EARLY_VM"), {
+ true: ["early_vm"],
+ default: [],
+ }),
rustlibs: [
"android.hardware.security.rkp-V3-rust",
"android.hardware.security.secureclock-V1-rust",
@@ -42,10 +46,14 @@
"android.security.maintenance-rust",
"android.security.metrics-rust",
"android.security.rkp_aidl-rust",
+ "apex_aidl_interface-rust",
"libaconfig_android_hardware_biometrics_rust",
"libandroid_security_flags_rust",
"libanyhow",
"libbinder_rs",
+ "libbssl_crypto",
+ "libder",
+ "libhex",
"libkeystore2_aaid-rust",
"libkeystore2_apc_compat-rust",
"libkeystore2_crypto_rust",
@@ -53,10 +61,10 @@
"libkeystore2_hal_names_rust",
"libkeystore2_km_compat",
"libkeystore2_selinux",
- "liblazy_static",
"liblibc",
"liblog_rust",
"libmessage_macro",
+ "libpostprocessor_client",
"librand",
"librkpd_client",
"librustutils",
@@ -105,12 +113,12 @@
defaults: ["libkeystore2_defaults"],
rustlibs: [
"libandroid_logger",
- "libhex",
"libkeystore2_test_utils",
"libkeystore2_with_test_utils",
"liblibsqlite3_sys",
"libnix",
"librusqlite",
+ "libtempfile",
],
// The test should always include watchdog.
features: [
@@ -120,6 +128,11 @@
require_root: true,
}
+vintf_fragment {
+ name: "android.system.keystore2-service.xml",
+ src: "android.system.keystore2-service.xml",
+}
+
rust_defaults {
name: "keystore2_defaults",
srcs: ["src/keystore2_main.rs"],
@@ -138,7 +151,7 @@
// selection available in the build system.
prefer_rlib: true,
- vintf_fragments: ["android.system.keystore2-service.xml"],
+ vintf_fragment_modules: ["android.system.keystore2-service.xml"],
required: ["keystore_cli_v2"],
}
@@ -162,6 +175,23 @@
srcs: ["aconfig/flags.aconfig"],
}
+java_aconfig_library {
+ name: "keystore2_flags_java",
+ aconfig_declarations: "keystore2_flags",
+}
+
+java_aconfig_library {
+ name: "keystore2_flags_java-host",
+ aconfig_declarations: "keystore2_flags",
+ host_supported: true,
+}
+
+java_aconfig_library {
+ name: "keystore2_flags_java-framework",
+ aconfig_declarations: "keystore2_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
rust_aconfig_library {
name: "libkeystore2_flags_rust",
crate_name: "keystore2_flags",
diff --git a/keystore2/OWNERS b/keystore2/OWNERS
index bf9d61b..1fcc785 100644
--- a/keystore2/OWNERS
+++ b/keystore2/OWNERS
@@ -1,8 +1,9 @@
set noparent
# Bug component: 1084732
-eranm@google.com
+cvlasov@google.com
drysdale@google.com
hasinitg@google.com
jbires@google.com
+kwadhera@google.com
sethmo@google.com
swillden@google.com
diff --git a/keystore2/TEST_MAPPING b/keystore2/TEST_MAPPING
index 57ce78c..f12a301 100644
--- a/keystore2/TEST_MAPPING
+++ b/keystore2/TEST_MAPPING
@@ -1,6 +1,9 @@
{
"presubmit": [
{
+ "name": "keystore2_client_tests"
+ },
+ {
"name": "keystore2_crypto_test"
},
{
@@ -32,9 +35,6 @@
"name": "CtsKeystorePerformanceTestCases"
},
{
- "name": "keystore2_client_tests"
- },
- {
"name": "librkpd_client.test"
},
{
diff --git a/keystore2/aconfig/flags.aconfig b/keystore2/aconfig/flags.aconfig
index 856b42e..b15230e 100644
--- a/keystore2/aconfig/flags.aconfig
+++ b/keystore2/aconfig/flags.aconfig
@@ -18,6 +18,22 @@
}
flag {
+ name: "disable_legacy_keystore_get"
+ namespace: "hardware_backed_security"
+ description: "This flag disables legacy keystore get and makes it so that get returns an error"
+ bug: "307460850"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_dump"
+ namespace: "hardware_backed_security"
+ description: "Include support for dump() on the IKeystoreMaintenance service"
+ bug: "344987718"
+ is_fixed_read_only: true
+}
+
+flag {
name: "import_previously_emulated_keys"
namespace: "hardware_backed_security"
description: "Include support for importing keys that were previously software-emulated into KeyMint"
@@ -26,9 +42,17 @@
}
flag {
- name: "database_loop_timeout"
+ name: "use_blob_state_column"
namespace: "hardware_backed_security"
- description: "Abandon Keystore database retry loop after an interval"
+ description: "Use state database column to track superseded blobentry rows"
bug: "319563050"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "attest_modules"
+ namespace: "hardware_backed_security"
+ description: "Support attestation of modules"
+ bug: "369375199"
+ is_fixed_read_only: true
+}
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index c297a15..13bf455 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -22,29 +22,10 @@
}
aidl_interface {
- name: "android.security.attestationmanager",
- srcs: ["android/security/attestationmanager/*.aidl"],
- imports: ["android.hardware.security.keymint-V3"],
- unstable: true,
- backend: {
- java: {
- platform_apis: true,
- },
- rust: {
- enabled: true,
- },
- ndk: {
- enabled: true,
- apps_enabled: false,
- },
- },
-}
-
-aidl_interface {
name: "android.security.authorization",
srcs: ["android/security/authorization/*.aidl"],
+ defaults: ["android.hardware.security.keymint-latest-defaults"],
imports: [
- "android.hardware.security.keymint-V3",
"android.hardware.security.secureclock-V1",
],
unstable: true,
@@ -82,8 +63,8 @@
aidl_interface {
name: "android.security.compat",
srcs: ["android/security/compat/*.aidl"],
+ defaults: ["android.hardware.security.keymint-latest-defaults"],
imports: [
- "android.hardware.security.keymint-V3",
"android.hardware.security.secureclock-V1",
"android.hardware.security.sharedsecret-V1",
],
@@ -105,8 +86,8 @@
aidl_interface {
name: "android.security.maintenance",
srcs: ["android/security/maintenance/*.aidl"],
- imports: [
- "android.system.keystore2-V4",
+ defaults: [
+ "android.system.keystore2-latest-defaults",
],
unstable: true,
backend: {
@@ -142,10 +123,30 @@
}
aidl_interface {
+ name: "android.security.postprocessor",
+ srcs: ["android/security/postprocessor/*.aidl"],
+ unstable: true,
+ backend: {
+ java: {
+ enabled: false,
+ },
+ cpp: {
+ enabled: false,
+ },
+ ndk: {
+ enabled: false,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+aidl_interface {
name: "android.security.metrics",
srcs: ["android/security/metrics/*.aidl"],
- imports: [
- "android.system.keystore2-V4",
+ defaults: [
+ "android.system.keystore2-latest-defaults",
],
unstable: true,
backend: {
@@ -168,21 +169,21 @@
java_defaults {
name: "keystore2_use_latest_aidl_java_static",
static_libs: [
- "android.system.keystore2-V4-java-source",
+ "android.system.keystore2-V5-java-source",
],
}
java_defaults {
name: "keystore2_use_latest_aidl_java_shared",
libs: [
- "android.system.keystore2-V4-java-source",
+ "android.system.keystore2-V5-java-source",
],
}
java_defaults {
name: "keystore2_use_latest_aidl_java",
libs: [
- "android.system.keystore2-V4-java",
+ "android.system.keystore2-V5-java",
],
}
@@ -192,28 +193,28 @@
cc_defaults {
name: "keystore2_use_latest_aidl_ndk_static",
static_libs: [
- "android.system.keystore2-V4-ndk",
+ "android.system.keystore2-V5-ndk",
],
}
cc_defaults {
name: "keystore2_use_latest_aidl_ndk_shared",
shared_libs: [
- "android.system.keystore2-V4-ndk",
+ "android.system.keystore2-V5-ndk",
],
}
cc_defaults {
name: "keystore2_use_latest_aidl_cpp_shared",
shared_libs: [
- "android.system.keystore2-V4-cpp",
+ "android.system.keystore2-V5-cpp",
],
}
cc_defaults {
name: "keystore2_use_latest_aidl_cpp_static",
static_libs: [
- "android.system.keystore2-V4-cpp",
+ "android.system.keystore2-V5-cpp",
],
}
@@ -223,6 +224,6 @@
rust_defaults {
name: "keystore2_use_latest_aidl_rust",
rustlibs: [
- "android.system.keystore2-V4-rust",
+ "android.system.keystore2-V5-rust",
],
}
diff --git a/keystore2/aidl/android/security/attestationmanager/ByteArray.aidl b/keystore2/aidl/android/security/attestationmanager/ByteArray.aidl
deleted file mode 100644
index dc37b1b..0000000
--- a/keystore2/aidl/android/security/attestationmanager/ByteArray.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.attestationmanager;
-
-/**
- * Simple data holder for a byte array, allowing for multidimensional arrays in AIDL.
- * @hide
- */
-parcelable ByteArray {
- byte[] data;
-}
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/attestationmanager/IAttestationManager.aidl b/keystore2/aidl/android/security/attestationmanager/IAttestationManager.aidl
deleted file mode 100644
index e77a21e..0000000
--- a/keystore2/aidl/android/security/attestationmanager/IAttestationManager.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.attestationmanager;
-
-import android.security.attestationmanager.ByteArray;
-import android.hardware.security.keymint.KeyParameter;
-
-/**
- * Internal interface for performing device attestation.
- * @hide
- */
-interface IAttestationManager {
- /**
- * Attest a provided list of device identifiers.
- *
- * @return The signed certificate chain, with each individual certificate encoded as a byte
- * array.
- */
- ByteArray[] attestDevice(
- in KeyParameter[] deviceIdentifiers, boolean useIndividualAttestation,
- in byte[] attestationChallenge, int securityLevel);
-}
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/metrics/HardwareAuthenticatorType.aidl b/keystore2/aidl/android/security/metrics/HardwareAuthenticatorType.aidl
index b13f6ea..d5cacfd 100644
--- a/keystore2/aidl/android/security/metrics/HardwareAuthenticatorType.aidl
+++ b/keystore2/aidl/android/security/metrics/HardwareAuthenticatorType.aidl
@@ -17,16 +17,41 @@
package android.security.metrics;
/**
- * HardwareAuthenticatorType enum as defined in Keystore2KeyCreationWithAuthInfo of
- * frameworks/proto_logging/stats/atoms.proto.
+ * AIDL enum representing the
+ * android.os.statsd.Keystore2KeyCreationWithAuthInfo.HardwareAuthenticatorType protocol buffer enum
+ * defined in frameworks/proto_logging/stats/atoms.proto.
+ *
+ * This enum is a mirror of
+ * hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
+ * except that:
+ * - The enum tag number for the ANY value is set to 5,
+ * - The enum tag numbers of all other values are incremented by 1, and
+ * - Two new values are added: AUTH_TYPE_UNSPECIFIED and NO_AUTH_TYPE.
+ * The KeyMint AIDL enum is a bitmask, but since the enum tag numbers in this metrics-specific
+ * mirror were shifted, this enum can't behave as a bitmask. As a result, we have to explicitly add
+ * values to represent the bitwise OR of pairs of values that we expect to see in the wild.
* @hide
*/
@Backing(type="int")
enum HardwareAuthenticatorType {
- /** Unspecified takes 0. Other values are incremented by 1 compared to keymint spec. */
+ // Sentinel value to represent undefined enum tag numbers (which would represent combinations of
+ // values from the KeyMint enum that aren't explicitly represented here). We don't expect to see
+ // this value in the metrics, but if we do it means that an unexpected (bitwise OR) combination
+ // of KeyMint HardwareAuthenticatorType values is being used as the HardwareAuthenticatorType
+ // key parameter.
AUTH_TYPE_UNSPECIFIED = 0,
+ // Corresponds to KeyMint's HardwareAuthenticatorType::NONE value (enum tag number 0).
NONE = 1,
+ // Corresponds to KeyMint's HardwareAuthenticatorType::PASSWORD value (enum tag number 1 << 0).
PASSWORD = 2,
+ // Corresponds to KeyMint's HardwareAuthenticatorType::FINGERPRINT value (enum tag number
+ // 1 << 1).
FINGERPRINT = 3,
+ // Corresponds to the (bitwise OR) combination of KeyMint's HardwareAuthenticatorType::PASSWORD
+ // and HardwareAuthenticatorType::FINGERPRINT values.
+ PASSWORD_OR_FINGERPRINT = 4,
+ // Corresponds to KeyMint's HardwareAuthenticatorType::ANY value (enum tag number 0xFFFFFFFF).
ANY = 5,
+ // No HardwareAuthenticatorType was specified in the key parameters.
+ NO_AUTH_TYPE = 6,
}
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl b/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl
new file mode 100644
index 0000000..8d9daad
--- /dev/null
+++ b/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl
@@ -0,0 +1,34 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android.security.postprocessor;
+
+/**
+ * General parcelable for holding the encoded certificates to be used in Keystore. This parcelable
+ * is returned by `IKeystoreCertificatePostProcessor::processKeystoreCertificates`.
+ * @hide
+ */
+@RustDerive(Clone=true)
+parcelable CertificateChain {
+ /**
+ * Holds the DER-encoded representation of the leaf certificate.
+ */
+ byte[] leafCertificate;
+ /**
+ * Holds a byte array containing the concatenation of all the remaining elements of the
+ * certificate chain with root certificate as the last with each certificate represented in
+ * DER-encoded format.
+ */
+ byte[] remainingChain;
+}
diff --git a/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl b/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl
new file mode 100644
index 0000000..0ceaacb
--- /dev/null
+++ b/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.postprocessor;
+
+import android.security.postprocessor.CertificateChain;
+
+interface IKeystoreCertificatePostProcessor {
+ /**
+ * Allows implementing services to process the keystore certificates after the certificate
+ * chain has been generated.
+ *
+ * certificateChain holds the chain associated with a newly generated Keystore asymmetric
+ * keypair, where the leafCertificate is the certificate for the public key of generated key.
+ * The remaining attestation certificates are stored as a concatenated byte array of the
+ * encoded certificates with root certificate as the last element.
+ *
+ * Successful calls would get the processed certificate chain which then replaces the original
+ * certificate chain. In case of any failures/exceptions, keystore would fallback to the
+ * original certificate chain.
+ *
+ * @hide
+ */
+ CertificateChain processKeystoreCertificates(in CertificateChain certificateChain);
+}
diff --git a/keystore2/android.system.keystore2-service.xml b/keystore2/android.system.keystore2-service.xml
index 4d8a756..35b9cc8 100644
--- a/keystore2/android.system.keystore2-service.xml
+++ b/keystore2/android.system.keystore2-service.xml
@@ -1,7 +1,7 @@
<manifest version="1.0" type="framework">
<hal format="aidl">
<name>android.system.keystore2</name>
- <version>4</version>
+ <version>5</version>
<interface>
<name>IKeystoreService</name>
<instance>default</instance>
diff --git a/keystore2/apc_compat/apc_compat.cpp b/keystore2/apc_compat/apc_compat.cpp
index ffe7595..0ff2fc5 100644
--- a/keystore2/apc_compat/apc_compat.cpp
+++ b/keystore2/apc_compat/apc_compat.cpp
@@ -26,6 +26,7 @@
#include <android/binder_manager.h>
#include <memory>
+#include <set>
#include <string>
#include <thread>
#include <vector>
@@ -214,6 +215,14 @@
return nullptr;
}
+ class DeathRecipientCookie {
+ public:
+ DeathRecipientCookie(std::weak_ptr<ConfuiAidlCompatSession> session)
+ : mAidlSession(session) {}
+ DeathRecipientCookie() = delete;
+ std::weak_ptr<ConfuiAidlCompatSession> mAidlSession;
+ };
+
uint32_t promptUserConfirmation(ApcCompatCallback callback, const char* prompt_text,
const uint8_t* extra_data, size_t extra_data_size,
const char* locale, ApcCompatUiOptions ui_options) {
@@ -234,12 +243,19 @@
if (!aidlService_) {
return APC_COMPAT_ERROR_SYSTEM_ERROR;
}
- auto linkRet =
- AIBinder_linkToDeath(aidlService_->asBinder().get(), death_recipient_.get(), this);
- if (linkRet != STATUS_OK) {
- LOG(ERROR) << "Communication error: promptUserConfirmation: "
- "Trying to register death recipient: ";
- return APC_COMPAT_ERROR_SYSTEM_ERROR;
+
+ {
+ auto cookieLock = std::lock_guard(deathRecipientCookie_lock_);
+ void* cookie = new DeathRecipientCookie(this->ref<ConfuiAidlCompatSession>());
+ auto linkRet = AIBinder_linkToDeath(aidlService_->asBinder().get(),
+ death_recipient_.get(), cookie);
+ if (linkRet != STATUS_OK) {
+ LOG(ERROR) << "Communication error: promptUserConfirmation: "
+ "Trying to register death recipient: ";
+ delete static_cast<DeathRecipientCookie*>(cookie);
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+ deathRecipientCookie_.insert(cookie);
}
auto rc = aidlService_->promptUserConfirmation(ref<ConfuiAidlCompatSession>(), aidl_prompt,
@@ -275,8 +291,21 @@
if (callback.result != nullptr) {
if (aidlService_) {
- AIBinder_unlinkToDeath(aidlService_->asBinder().get(), death_recipient_.get(),
- this);
+ // unlink all of the registered death recipients in case there
+ // were multiple calls to promptUserConfirmation before a call
+ // to finalize
+ std::set<void*> cookiesToUnlink;
+ {
+ auto cookieLock = std::lock_guard(deathRecipientCookie_lock_);
+ cookiesToUnlink = deathRecipientCookie_;
+ deathRecipientCookie_.clear();
+ }
+
+ // Unlink these outside of the lock
+ for (void* cookie : cookiesToUnlink) {
+ AIBinder_unlinkToDeath(aidlService_->asBinder().get(), death_recipient_.get(),
+ cookie);
+ }
}
CompatSessionCB::finalize(responseCode2Compat(responseCode), callback, dataConfirmed,
confirmationToken);
@@ -293,17 +322,46 @@
void serviceDied() {
aidlService_.reset();
aidlService_ = nullptr;
+ {
+ std::lock_guard lock(deathRecipientCookie_lock_);
+ deathRecipientCookie_.clear();
+ }
finalize(AidlConfirmationUI::SYSTEM_ERROR, {}, {});
}
- static void binderDiedCallbackAidl(void* ptr) {
- LOG(ERROR) << __func__ << " : ConfuiAidlCompatSession Service died.";
- auto aidlSession = static_cast<ConfuiAidlCompatSession*>(ptr);
- if (aidlSession == nullptr) {
- LOG(ERROR) << __func__ << ": Null ConfuiAidlCompatSession HAL died.";
- return;
+ void serviceUnlinked(void* cookie) {
+ {
+ std::lock_guard lock(deathRecipientCookie_lock_);
+ deathRecipientCookie_.erase(cookie);
}
- aidlSession->serviceDied();
+ }
+
+ static void binderDiedCallbackAidl(void* ptr) {
+ auto aidlSessionCookie = static_cast<ConfuiAidlCompatSession::DeathRecipientCookie*>(ptr);
+ if (aidlSessionCookie == nullptr) {
+ LOG(ERROR) << __func__ << ": Null cookie for binderDiedCallbackAidl when HAL died!";
+ return;
+ } else if (auto aidlSession = aidlSessionCookie->mAidlSession.lock();
+ aidlSession != nullptr) {
+ LOG(WARNING) << __func__ << " : Notififying ConfuiAidlCompatSession Service died.";
+ aidlSession->serviceDied();
+ } else {
+ LOG(ERROR) << __func__
+ << " : ConfuiAidlCompatSession Service died but object is no longer around "
+ "to be able to notify.";
+ }
+ }
+
+ static void binderUnlinkedCallbackAidl(void* ptr) {
+ auto aidlSessionCookie = static_cast<ConfuiAidlCompatSession::DeathRecipientCookie*>(ptr);
+ if (aidlSessionCookie == nullptr) {
+ LOG(ERROR) << __func__ << ": Null cookie!";
+ return;
+ } else if (auto aidlSession = aidlSessionCookie->mAidlSession.lock();
+ aidlSession != nullptr) {
+ aidlSession->serviceUnlinked(ptr);
+ }
+ delete aidlSessionCookie;
}
int getReturnCode(const ::ndk::ScopedAStatus& result) {
@@ -343,6 +401,7 @@
: aidlService_(service), callback_{nullptr, nullptr} {
death_recipient_ = ::ndk::ScopedAIBinder_DeathRecipient(
AIBinder_DeathRecipient_new(binderDiedCallbackAidl));
+ AIBinder_DeathRecipient_setOnUnlinked(death_recipient_.get(), binderUnlinkedCallbackAidl);
}
virtual ~ConfuiAidlCompatSession() = default;
@@ -351,6 +410,8 @@
private:
std::shared_ptr<AidlConfirmationUI> aidlService_;
+ std::mutex deathRecipientCookie_lock_;
+ std::set<void*> deathRecipientCookie_;
// The callback_lock_ protects the callback_ field against concurrent modification.
// IMPORTANT: It must never be held while calling the call back.
diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs
index 8e6040b..b173da8 100644
--- a/keystore2/legacykeystore/lib.rs
+++ b/keystore2/legacykeystore/lib.rs
@@ -134,6 +134,7 @@
}
fn get(&mut self, caller_uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
+ ensure_keystore_get_is_enabled()?;
self.with_transaction(TransactionBehavior::Deferred, |tx| {
tx.query_row(
"SELECT profile FROM profiles WHERE owner = ? AND alias = ?;",
@@ -239,6 +240,17 @@
}
}
+fn ensure_keystore_get_is_enabled() -> Result<()> {
+ if keystore2_flags::disable_legacy_keystore_get() {
+ Err(Error::deprecated()).context(concat!(
+ "Retrieving from Keystore's legacy database is ",
+ "no longer supported, store in an app-specific database instead"
+ ))
+ } else {
+ Ok(())
+ }
+}
+
struct LegacyKeystoreDeleteListener {
legacy_keystore: Arc<LegacyKeystore>,
}
@@ -313,6 +325,7 @@
}
fn get(&self, alias: &str, uid: i32) -> Result<Vec<u8>> {
+ ensure_keystore_get_is_enabled()?;
let mut db = self.open_db().context("In get.")?;
let uid = Self::get_effective_uid(uid).context("In get.")?;
diff --git a/keystore2/postprocessor_client/Android.bp b/keystore2/postprocessor_client/Android.bp
new file mode 100644
index 0000000..7f0194a
--- /dev/null
+++ b/keystore2/postprocessor_client/Android.bp
@@ -0,0 +1,47 @@
+//
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_defaults {
+ name: "libpostprocessor_client_defaults",
+ crate_name: "postprocessor_client",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "android.security.postprocessor-rust",
+ "libanyhow",
+ "libbinder_rs",
+ "liblog_rust",
+ "libmessage_macro",
+ "libthiserror",
+ ],
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ ],
+}
+
+rust_library {
+ name: "libpostprocessor_client",
+ defaults: [
+ "libpostprocessor_client_defaults",
+ ],
+}
diff --git a/keystore2/postprocessor_client/src/lib.rs b/keystore2/postprocessor_client/src/lib.rs
new file mode 100644
index 0000000..beeb5f5
--- /dev/null
+++ b/keystore2/postprocessor_client/src/lib.rs
@@ -0,0 +1,109 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Helper wrapper around PostProcessor interface.
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::Certificate::Certificate;
+use android_security_postprocessor::aidl::android::security::postprocessor::{
+ CertificateChain::CertificateChain,
+ IKeystoreCertificatePostProcessor::IKeystoreCertificatePostProcessor,
+};
+use anyhow::{Context, Result};
+use binder::{StatusCode, Strong};
+use log::{error, info, warn};
+use message_macro::source_location_msg;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc;
+use std::thread;
+use std::time::Duration;
+
+/// Errors occurred during the interaction with Certificate Processor
+#[derive(Debug, Clone, Copy, thiserror::Error, PartialEq, Eq)]
+#[error("Binder transaction error {0:?}")]
+pub struct Error(pub StatusCode);
+
+static CERT_PROCESSOR_FAILURE: AtomicBool = AtomicBool::new(false);
+
+fn send_certificate_chain_to_processor(
+ attestation_chain: CertificateChain,
+) -> Result<CertificateChain> {
+ let cert_processing_server: Strong<dyn IKeystoreCertificatePostProcessor> = wait_for_interface(
+ "rkp_cert_processor.service".to_string(),
+ )
+ .context(source_location_msg!("While trying to connect to the post processor service."))?;
+ cert_processing_server
+ .processKeystoreCertificates(&attestation_chain)
+ .context(source_location_msg!("While trying to post process certificates."))
+}
+
+/// Processes the keystore certificates after the certificate chain has been generated by Keystore.
+/// More details about this function provided in IKeystoreCertificatePostProcessor.aidl
+pub fn process_certificate_chain(
+ mut certificates: Vec<Certificate>,
+ attestation_certs: Vec<u8>,
+) -> Vec<Certificate> {
+ // If no certificates are provided from keymint, return the original chain.
+ if certificates.is_empty() {
+ error!("No leaf certificate provided.");
+ return vec![Certificate { encodedCertificate: attestation_certs }];
+ }
+
+ if certificates.len() > 1 {
+ warn!("dropping {} unexpected extra certificates after the leaf", certificates.len() - 1);
+ }
+
+ let attestation_chain = CertificateChain {
+ leafCertificate: certificates[0].encodedCertificate.clone(),
+ remainingChain: attestation_certs.clone(),
+ };
+ let result = send_certificate_chain_to_processor(attestation_chain);
+ match result {
+ Ok(certificate_chain) => {
+ info!("Post processing successful. Replacing certificates.");
+ vec![
+ Certificate { encodedCertificate: certificate_chain.leafCertificate },
+ Certificate { encodedCertificate: certificate_chain.remainingChain },
+ ]
+ }
+ Err(err) => {
+ warn!("Failed to replace certificates ({err:#?}), falling back to original chain.");
+ certificates.push(Certificate { encodedCertificate: attestation_certs });
+ certificates
+ }
+ }
+}
+
+fn wait_for_interface(
+ service_name: String,
+) -> Result<Strong<dyn IKeystoreCertificatePostProcessor>> {
+ if CERT_PROCESSOR_FAILURE.load(Ordering::Relaxed) {
+ return Err(Error(StatusCode::INVALID_OPERATION).into());
+ }
+ let (sender, receiver) = mpsc::channel();
+ let _t = thread::spawn(move || {
+ if let Err(e) = sender.send(binder::wait_for_interface(&service_name)) {
+ error!("failed to send result of wait_for_interface({service_name}), likely due to timeout: {e:?}");
+ }
+ });
+
+ match receiver.recv_timeout(Duration::from_secs(5)) {
+ Ok(service_binder) => Ok(service_binder?),
+ Err(e) => {
+ error!("Timed out while connecting to post processor service: {e:#?}");
+ // Cert processor has failed. Retry only after reboot.
+ CERT_PROCESSOR_FAILURE.store(true, Ordering::Relaxed);
+ Err(e.into())
+ }
+ }
+}
diff --git a/keystore2/rkpd_client/src/lib.rs b/keystore2/rkpd_client/src/lib.rs
index d8a5276..936fe3d 100644
--- a/keystore2/rkpd_client/src/lib.rs
+++ b/keystore2/rkpd_client/src/lib.rs
@@ -310,331 +310,4 @@
}
#[cfg(test)]
-mod tests {
- use super::*;
- use android_security_rkp_aidl::aidl::android::security::rkp::IRegistration::BnRegistration;
- use std::sync::atomic::{AtomicU32, Ordering};
- use std::sync::{Arc, Mutex};
-
- const DEFAULT_RPC_SERVICE_NAME: &str =
- "android.hardware.security.keymint.IRemotelyProvisionedComponent/default";
-
- struct MockRegistrationValues {
- key: RemotelyProvisionedKey,
- latency: Option<Duration>,
- thread_join_handles: Vec<Option<std::thread::JoinHandle<()>>>,
- }
-
- struct MockRegistration(Arc<Mutex<MockRegistrationValues>>);
-
- impl MockRegistration {
- pub fn new_native_binder(
- key: &RemotelyProvisionedKey,
- latency: Option<Duration>,
- ) -> Strong<dyn IRegistration> {
- let result = Self(Arc::new(Mutex::new(MockRegistrationValues {
- key: RemotelyProvisionedKey {
- keyBlob: key.keyBlob.clone(),
- encodedCertChain: key.encodedCertChain.clone(),
- },
- latency,
- thread_join_handles: Vec::new(),
- })));
- BnRegistration::new_binder(result, BinderFeatures::default())
- }
- }
-
- impl Drop for MockRegistration {
- fn drop(&mut self) {
- let mut values = self.0.lock().unwrap();
- for handle in values.thread_join_handles.iter_mut() {
- // These are test threads. So, no need to worry too much about error handling.
- handle.take().unwrap().join().unwrap();
- }
- }
- }
-
- impl Interface for MockRegistration {}
-
- impl IRegistration for MockRegistration {
- fn getKey(&self, _: i32, cb: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> {
- let mut values = self.0.lock().unwrap();
- let key = RemotelyProvisionedKey {
- keyBlob: values.key.keyBlob.clone(),
- encodedCertChain: values.key.encodedCertChain.clone(),
- };
- let latency = values.latency;
- let get_key_cb = cb.clone();
-
- // Need a separate thread to trigger timeout in the caller.
- let join_handle = std::thread::spawn(move || {
- if let Some(duration) = latency {
- std::thread::sleep(duration);
- }
- get_key_cb.onSuccess(&key).unwrap();
- });
- values.thread_join_handles.push(Some(join_handle));
- Ok(())
- }
-
- fn cancelGetKey(&self, _: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> {
- Ok(())
- }
-
- fn storeUpgradedKeyAsync(
- &self,
- _: &[u8],
- _: &[u8],
- cb: &Strong<dyn IStoreUpgradedKeyCallback>,
- ) -> binder::Result<()> {
- // We are primarily concerned with timing out correctly. Storing the key in this mock
- // registration isn't particularly interesting, so skip that part.
- let values = self.0.lock().unwrap();
- let store_cb = cb.clone();
- let latency = values.latency;
-
- std::thread::spawn(move || {
- if let Some(duration) = latency {
- std::thread::sleep(duration);
- }
- store_cb.onSuccess().unwrap();
- });
- Ok(())
- }
- }
-
- fn get_mock_registration(
- key: &RemotelyProvisionedKey,
- latency: Option<Duration>,
- ) -> Result<binder::Strong<dyn IRegistration>> {
- let (tx, rx) = oneshot::channel();
- let cb = GetRegistrationCallback::new_native_binder(tx);
- let mock_registration = MockRegistration::new_native_binder(key, latency);
-
- assert!(cb.onSuccess(&mock_registration).is_ok());
- tokio_rt().block_on(rx).unwrap()
- }
-
- // Using the same key ID makes test cases race with each other. So, we use separate key IDs for
- // different test cases.
- fn get_next_key_id() -> u32 {
- static ID: AtomicU32 = AtomicU32::new(0);
- ID.fetch_add(1, Ordering::Relaxed)
- }
-
- #[test]
- fn test_get_registration_cb_success() {
- let key: RemotelyProvisionedKey = Default::default();
- let registration = get_mock_registration(&key, /*latency=*/ None);
- assert!(registration.is_ok());
- }
-
- #[test]
- fn test_get_registration_cb_cancel() {
- let (tx, rx) = oneshot::channel();
- let cb = GetRegistrationCallback::new_native_binder(tx);
- assert!(cb.onCancel().is_ok());
-
- let result = tokio_rt().block_on(rx).unwrap();
- assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled);
- }
-
- #[test]
- fn test_get_registration_cb_error() {
- let (tx, rx) = oneshot::channel();
- let cb = GetRegistrationCallback::new_native_binder(tx);
- assert!(cb.onError("error").is_ok());
-
- let result = tokio_rt().block_on(rx).unwrap();
- assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::GetRegistrationFailed);
- }
-
- #[test]
- fn test_get_key_cb_success() {
- let mock_key =
- RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
- let (tx, rx) = oneshot::channel();
- let cb = GetKeyCallback::new_native_binder(tx);
- assert!(cb.onSuccess(&mock_key).is_ok());
-
- let key = tokio_rt().block_on(rx).unwrap().unwrap();
- assert_eq!(key, mock_key);
- }
-
- #[test]
- fn test_get_key_cb_cancel() {
- let (tx, rx) = oneshot::channel();
- let cb = GetKeyCallback::new_native_binder(tx);
- assert!(cb.onCancel().is_ok());
-
- let result = tokio_rt().block_on(rx).unwrap();
- assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled);
- }
-
- #[test]
- fn test_get_key_cb_error() {
- for get_key_error in GetKeyErrorCode::enum_values() {
- let (tx, rx) = oneshot::channel();
- let cb = GetKeyCallback::new_native_binder(tx);
- assert!(cb.onError(get_key_error, "error").is_ok());
-
- let result = tokio_rt().block_on(rx).unwrap();
- assert_eq!(
- result.unwrap_err().downcast::<Error>().unwrap(),
- Error::GetKeyFailed(get_key_error),
- );
- }
- }
-
- #[test]
- fn test_store_upgraded_cb_success() {
- let (tx, rx) = oneshot::channel();
- let cb = StoreUpgradedKeyCallback::new_native_binder(tx);
- assert!(cb.onSuccess().is_ok());
-
- tokio_rt().block_on(rx).unwrap().unwrap();
- }
-
- #[test]
- fn test_store_upgraded_key_cb_error() {
- let (tx, rx) = oneshot::channel();
- let cb = StoreUpgradedKeyCallback::new_native_binder(tx);
- assert!(cb.onError("oh no! it failed").is_ok());
-
- let result = tokio_rt().block_on(rx).unwrap();
- assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::StoreUpgradedKeyFailed);
- }
-
- #[test]
- fn test_get_mock_key_success() {
- let mock_key =
- RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
- let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap();
-
- let key = tokio_rt()
- .block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0))
- .unwrap();
- assert_eq!(key, mock_key);
- }
-
- #[test]
- fn test_get_mock_key_timeout() {
- let mock_key =
- RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
- let latency = RKPD_TIMEOUT + Duration::from_secs(1);
- let registration = get_mock_registration(&mock_key, Some(latency)).unwrap();
-
- let result =
- tokio_rt().block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0));
- assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RetryableTimeout);
- }
-
- #[test]
- fn test_store_mock_key_success() {
- let mock_key =
- RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
- let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap();
- tokio_rt()
- .block_on(store_rkpd_attestation_key_with_registration_async(®istration, &[], &[]))
- .unwrap();
- }
-
- #[test]
- fn test_store_mock_key_timeout() {
- let mock_key =
- RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
- let latency = RKPD_TIMEOUT + Duration::from_secs(1);
- let registration = get_mock_registration(&mock_key, Some(latency)).unwrap();
-
- let result = tokio_rt().block_on(store_rkpd_attestation_key_with_registration_async(
- ®istration,
- &[],
- &[],
- ));
- assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::Timeout);
- }
-
- #[test]
- fn test_get_rkpd_attestation_key() {
- binder::ProcessState::start_thread_pool();
- let key_id = get_next_key_id();
- let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
- assert!(!key.keyBlob.is_empty());
- assert!(!key.encodedCertChain.is_empty());
- }
-
- #[test]
- fn test_get_rkpd_attestation_key_same_caller() {
- binder::ProcessState::start_thread_pool();
- let key_id = get_next_key_id();
-
- // Multiple calls should return the same key.
- let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
- let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
-
- assert_eq!(first_key.keyBlob, second_key.keyBlob);
- assert_eq!(first_key.encodedCertChain, second_key.encodedCertChain);
- }
-
- #[test]
- fn test_get_rkpd_attestation_key_different_caller() {
- binder::ProcessState::start_thread_pool();
- let first_key_id = get_next_key_id();
- let second_key_id = get_next_key_id();
-
- // Different callers should be getting different keys.
- let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, first_key_id).unwrap();
- let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, second_key_id).unwrap();
-
- assert_ne!(first_key.keyBlob, second_key.keyBlob);
- assert_ne!(first_key.encodedCertChain, second_key.encodedCertChain);
- }
-
- #[test]
- // Couple of things to note:
- // 1. This test must never run with UID of keystore. Otherwise, it can mess up keys stored by
- // keystore.
- // 2. Storing and reading the stored key is prone to race condition. So, we only do this in one
- // test case.
- fn test_store_rkpd_attestation_key() {
- binder::ProcessState::start_thread_pool();
- let key_id = get_next_key_id();
- let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
- let new_blob: [u8; 8] = rand::random();
-
- assert!(
- store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &key.keyBlob, &new_blob).is_ok()
- );
-
- let new_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
-
- // Restore original key so that we don't leave RKPD with invalid blobs.
- assert!(
- store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &new_blob, &key.keyBlob).is_ok()
- );
- assert_eq!(new_key.keyBlob, new_blob);
- }
-
- #[test]
- fn test_stress_get_rkpd_attestation_key() {
- binder::ProcessState::start_thread_pool();
- let key_id = get_next_key_id();
- let mut threads = vec![];
- const NTHREADS: u32 = 10;
- const NCALLS: u32 = 1000;
-
- for _ in 0..NTHREADS {
- threads.push(std::thread::spawn(move || {
- for _ in 0..NCALLS {
- let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
- assert!(!key.keyBlob.is_empty());
- assert!(!key.encodedCertChain.is_empty());
- }
- }));
- }
-
- for t in threads {
- assert!(t.join().is_ok());
- }
- }
-}
+mod tests;
diff --git a/keystore2/rkpd_client/src/tests.rs b/keystore2/rkpd_client/src/tests.rs
new file mode 100644
index 0000000..fd0468f
--- /dev/null
+++ b/keystore2/rkpd_client/src/tests.rs
@@ -0,0 +1,338 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! RKPD tests.
+
+use super::*;
+use android_security_rkp_aidl::aidl::android::security::rkp::IRegistration::BnRegistration;
+use std::sync::atomic::{AtomicU32, Ordering};
+use std::sync::{Arc, Mutex};
+
+const DEFAULT_RPC_SERVICE_NAME: &str =
+ "android.hardware.security.keymint.IRemotelyProvisionedComponent/default";
+
+struct MockRegistrationValues {
+ key: RemotelyProvisionedKey,
+ latency: Option<Duration>,
+ thread_join_handles: Vec<Option<std::thread::JoinHandle<()>>>,
+}
+
+struct MockRegistration(Arc<Mutex<MockRegistrationValues>>);
+
+impl MockRegistration {
+ pub fn new_native_binder(
+ key: &RemotelyProvisionedKey,
+ latency: Option<Duration>,
+ ) -> Strong<dyn IRegistration> {
+ let result = Self(Arc::new(Mutex::new(MockRegistrationValues {
+ key: RemotelyProvisionedKey {
+ keyBlob: key.keyBlob.clone(),
+ encodedCertChain: key.encodedCertChain.clone(),
+ },
+ latency,
+ thread_join_handles: Vec::new(),
+ })));
+ BnRegistration::new_binder(result, BinderFeatures::default())
+ }
+}
+
+impl Drop for MockRegistration {
+ fn drop(&mut self) {
+ let mut values = self.0.lock().unwrap();
+ for handle in values.thread_join_handles.iter_mut() {
+ // These are test threads. So, no need to worry too much about error handling.
+ handle.take().unwrap().join().unwrap();
+ }
+ }
+}
+
+impl Interface for MockRegistration {}
+
+impl IRegistration for MockRegistration {
+ fn getKey(&self, _: i32, cb: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> {
+ let mut values = self.0.lock().unwrap();
+ let key = RemotelyProvisionedKey {
+ keyBlob: values.key.keyBlob.clone(),
+ encodedCertChain: values.key.encodedCertChain.clone(),
+ };
+ let latency = values.latency;
+ let get_key_cb = cb.clone();
+
+ // Need a separate thread to trigger timeout in the caller.
+ let join_handle = std::thread::spawn(move || {
+ if let Some(duration) = latency {
+ std::thread::sleep(duration);
+ }
+ get_key_cb.onSuccess(&key).unwrap();
+ });
+ values.thread_join_handles.push(Some(join_handle));
+ Ok(())
+ }
+
+ fn cancelGetKey(&self, _: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> {
+ Ok(())
+ }
+
+ fn storeUpgradedKeyAsync(
+ &self,
+ _: &[u8],
+ _: &[u8],
+ cb: &Strong<dyn IStoreUpgradedKeyCallback>,
+ ) -> binder::Result<()> {
+ // We are primarily concerned with timing out correctly. Storing the key in this mock
+ // registration isn't particularly interesting, so skip that part.
+ let values = self.0.lock().unwrap();
+ let store_cb = cb.clone();
+ let latency = values.latency;
+
+ std::thread::spawn(move || {
+ if let Some(duration) = latency {
+ std::thread::sleep(duration);
+ }
+ store_cb.onSuccess().unwrap();
+ });
+ Ok(())
+ }
+}
+
+fn get_mock_registration(
+ key: &RemotelyProvisionedKey,
+ latency: Option<Duration>,
+) -> Result<binder::Strong<dyn IRegistration>> {
+ let (tx, rx) = oneshot::channel();
+ let cb = GetRegistrationCallback::new_native_binder(tx);
+ let mock_registration = MockRegistration::new_native_binder(key, latency);
+
+ assert!(cb.onSuccess(&mock_registration).is_ok());
+ tokio_rt().block_on(rx).unwrap()
+}
+
+// Using the same key ID makes test cases race with each other. So, we use separate key IDs for
+// different test cases.
+fn get_next_key_id() -> u32 {
+ static ID: AtomicU32 = AtomicU32::new(0);
+ ID.fetch_add(1, Ordering::Relaxed)
+}
+
+#[test]
+fn test_get_registration_cb_success() {
+ let key: RemotelyProvisionedKey = Default::default();
+ let registration = get_mock_registration(&key, /*latency=*/ None);
+ assert!(registration.is_ok());
+}
+
+#[test]
+fn test_get_registration_cb_cancel() {
+ let (tx, rx) = oneshot::channel();
+ let cb = GetRegistrationCallback::new_native_binder(tx);
+ assert!(cb.onCancel().is_ok());
+
+ let result = tokio_rt().block_on(rx).unwrap();
+ assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled);
+}
+
+#[test]
+fn test_get_registration_cb_error() {
+ let (tx, rx) = oneshot::channel();
+ let cb = GetRegistrationCallback::new_native_binder(tx);
+ assert!(cb.onError("error").is_ok());
+
+ let result = tokio_rt().block_on(rx).unwrap();
+ assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::GetRegistrationFailed);
+}
+
+#[test]
+fn test_get_key_cb_success() {
+ let mock_key =
+ RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+ let (tx, rx) = oneshot::channel();
+ let cb = GetKeyCallback::new_native_binder(tx);
+ assert!(cb.onSuccess(&mock_key).is_ok());
+
+ let key = tokio_rt().block_on(rx).unwrap().unwrap();
+ assert_eq!(key, mock_key);
+}
+
+#[test]
+fn test_get_key_cb_cancel() {
+ let (tx, rx) = oneshot::channel();
+ let cb = GetKeyCallback::new_native_binder(tx);
+ assert!(cb.onCancel().is_ok());
+
+ let result = tokio_rt().block_on(rx).unwrap();
+ assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled);
+}
+
+#[test]
+fn test_get_key_cb_error() {
+ for get_key_error in GetKeyErrorCode::enum_values() {
+ let (tx, rx) = oneshot::channel();
+ let cb = GetKeyCallback::new_native_binder(tx);
+ assert!(cb.onError(get_key_error, "error").is_ok());
+
+ let result = tokio_rt().block_on(rx).unwrap();
+ assert_eq!(
+ result.unwrap_err().downcast::<Error>().unwrap(),
+ Error::GetKeyFailed(get_key_error),
+ );
+ }
+}
+
+#[test]
+fn test_store_upgraded_cb_success() {
+ let (tx, rx) = oneshot::channel();
+ let cb = StoreUpgradedKeyCallback::new_native_binder(tx);
+ assert!(cb.onSuccess().is_ok());
+
+ tokio_rt().block_on(rx).unwrap().unwrap();
+}
+
+#[test]
+fn test_store_upgraded_key_cb_error() {
+ let (tx, rx) = oneshot::channel();
+ let cb = StoreUpgradedKeyCallback::new_native_binder(tx);
+ assert!(cb.onError("oh no! it failed").is_ok());
+
+ let result = tokio_rt().block_on(rx).unwrap();
+ assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::StoreUpgradedKeyFailed);
+}
+
+#[test]
+fn test_get_mock_key_success() {
+ let mock_key =
+ RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+ let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap();
+
+ let key = tokio_rt()
+ .block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0))
+ .unwrap();
+ assert_eq!(key, mock_key);
+}
+
+#[test]
+fn test_get_mock_key_timeout() {
+ let mock_key =
+ RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+ let latency = RKPD_TIMEOUT + Duration::from_secs(1);
+ let registration = get_mock_registration(&mock_key, Some(latency)).unwrap();
+
+ let result =
+ tokio_rt().block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0));
+ assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RetryableTimeout);
+}
+
+#[test]
+fn test_store_mock_key_success() {
+ let mock_key =
+ RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+ let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap();
+ tokio_rt()
+ .block_on(store_rkpd_attestation_key_with_registration_async(®istration, &[], &[]))
+ .unwrap();
+}
+
+#[test]
+fn test_store_mock_key_timeout() {
+ let mock_key =
+ RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+ let latency = RKPD_TIMEOUT + Duration::from_secs(1);
+ let registration = get_mock_registration(&mock_key, Some(latency)).unwrap();
+
+ let result = tokio_rt().block_on(store_rkpd_attestation_key_with_registration_async(
+ ®istration,
+ &[],
+ &[],
+ ));
+ assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::Timeout);
+}
+
+#[test]
+fn test_get_rkpd_attestation_key() {
+ binder::ProcessState::start_thread_pool();
+ let key_id = get_next_key_id();
+ let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+ assert!(!key.keyBlob.is_empty());
+ assert!(!key.encodedCertChain.is_empty());
+}
+
+#[test]
+fn test_get_rkpd_attestation_key_same_caller() {
+ binder::ProcessState::start_thread_pool();
+ let key_id = get_next_key_id();
+
+ // Multiple calls should return the same key.
+ let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+ let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+
+ assert_eq!(first_key.keyBlob, second_key.keyBlob);
+ assert_eq!(first_key.encodedCertChain, second_key.encodedCertChain);
+}
+
+#[test]
+fn test_get_rkpd_attestation_key_different_caller() {
+ binder::ProcessState::start_thread_pool();
+ let first_key_id = get_next_key_id();
+ let second_key_id = get_next_key_id();
+
+ // Different callers should be getting different keys.
+ let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, first_key_id).unwrap();
+ let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, second_key_id).unwrap();
+
+ assert_ne!(first_key.keyBlob, second_key.keyBlob);
+ assert_ne!(first_key.encodedCertChain, second_key.encodedCertChain);
+}
+
+#[test]
+// Couple of things to note:
+// 1. This test must never run with UID of keystore. Otherwise, it can mess up keys stored by
+// keystore.
+// 2. Storing and reading the stored key is prone to race condition. So, we only do this in one
+// test case.
+fn test_store_rkpd_attestation_key() {
+ binder::ProcessState::start_thread_pool();
+ let key_id = get_next_key_id();
+ let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+ let new_blob: [u8; 8] = rand::random();
+
+ assert!(store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &key.keyBlob, &new_blob).is_ok());
+
+ let new_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+
+ // Restore original key so that we don't leave RKPD with invalid blobs.
+ assert!(store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &new_blob, &key.keyBlob).is_ok());
+ assert_eq!(new_key.keyBlob, new_blob);
+}
+
+#[test]
+fn test_stress_get_rkpd_attestation_key() {
+ binder::ProcessState::start_thread_pool();
+ let key_id = get_next_key_id();
+ let mut threads = vec![];
+ const NTHREADS: u32 = 10;
+ const NCALLS: u32 = 1000;
+
+ for _ in 0..NTHREADS {
+ threads.push(std::thread::spawn(move || {
+ for _ in 0..NCALLS {
+ let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+ assert!(!key.keyBlob.is_empty());
+ assert!(!key.encodedCertChain.is_empty());
+ }
+ }));
+ }
+
+ for t in threads {
+ assert!(t.join().is_ok());
+ }
+}
diff --git a/keystore2/selinux/Android.bp b/keystore2/selinux/Android.bp
index 254f95e..8e644e6 100644
--- a/keystore2/selinux/Android.bp
+++ b/keystore2/selinux/Android.bp
@@ -34,7 +34,6 @@
rustlibs: [
"libanyhow",
- "liblazy_static",
"liblog_rust",
"libselinux_bindgen",
"libthiserror",
@@ -57,7 +56,6 @@
rustlibs: [
"libandroid_logger",
"libanyhow",
- "liblazy_static",
"liblog_rust",
"libselinux_bindgen",
"libthiserror",
@@ -77,7 +75,6 @@
"libandroid_logger",
"libanyhow",
"libkeystore2_selinux",
- "liblazy_static",
"liblog_rust",
"libnix",
"libnum_cpus",
diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs
index 695e029..1f1e692 100644
--- a/keystore2/selinux/src/lib.rs
+++ b/keystore2/selinux/src/lib.rs
@@ -18,6 +18,7 @@
//! * getcon
//! * selinux_check_access
//! * selabel_lookup for the keystore2_key backend.
+//!
//! And it provides an owning wrapper around context strings `Context`.
// TODO(b/290018030): Remove this and add proper safety comments.
@@ -25,7 +26,6 @@
use anyhow::Context as AnyhowContext;
use anyhow::{anyhow, Result};
-use lazy_static::lazy_static;
pub use selinux::pid_t;
use selinux::SELABEL_CTX_ANDROID_KEYSTORE2_KEY;
use selinux::SELINUX_CB_LOG;
@@ -41,15 +41,13 @@
static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
-lazy_static! {
- /// `selinux_check_access` is only thread safe if avc_init was called with lock callbacks.
- /// However, avc_init is deprecated and not exported by androids version of libselinux.
- /// `selinux_set_callbacks` does not allow setting lock callbacks. So the only option
- /// that remains right now is to put a big lock around calls into libselinux.
- /// TODO b/188079221 It should suffice to protect `selinux_check_access` but until we are
- /// certain of that, we leave the extra locks in place
- static ref LIB_SELINUX_LOCK: sync::Mutex<()> = Default::default();
-}
+/// `selinux_check_access` is only thread safe if avc_init was called with lock callbacks.
+/// However, avc_init is deprecated and not exported by androids version of libselinux.
+/// `selinux_set_callbacks` does not allow setting lock callbacks. So the only option
+/// that remains right now is to put a big lock around calls into libselinux.
+/// TODO b/188079221 It should suffice to protect `selinux_check_access` but until we are
+/// certain of that, we leave the extra locks in place
+static LIB_SELINUX_LOCK: sync::Mutex<()> = sync::Mutex::new(());
fn redirect_selinux_logs_to_logcat() {
// `selinux_set_callback` assigns the static lifetime function pointer
@@ -249,34 +247,6 @@
}
}
-/// Safe wrapper around libselinux `getpidcon`. It initializes the `Context::Raw` variant of the
-/// returned `Context`.
-///
-/// ## Return
-/// * Ok(Context::Raw()) if successful.
-/// * Err(Error::sys()) if getpidcon succeeded but returned a NULL pointer.
-/// * Err(io::Error::last_os_error()) if getpidcon failed.
-pub fn getpidcon(pid: selinux::pid_t) -> Result<Context> {
- init_logger_once();
- let _lock = LIB_SELINUX_LOCK.lock().unwrap();
-
- let mut con: *mut c_char = ptr::null_mut();
- match unsafe { selinux::getpidcon(pid, &mut con) } {
- 0 => {
- if !con.is_null() {
- Ok(Context::Raw(con))
- } else {
- Err(anyhow!(Error::sys(format!(
- "getpidcon returned a NULL context for pid {}",
- pid
- ))))
- }
- }
- _ => Err(anyhow!(io::Error::last_os_error()))
- .context(format!("getpidcon failed for pid {}", pid)),
- }
-}
-
/// Safe wrapper around selinux_check_access.
///
/// ## Return
@@ -798,12 +768,4 @@
check_keystore_perm!(reset);
check_keystore_perm!(unlock);
}
-
- #[test]
- fn test_getpidcon() {
- // Check that `getpidcon` of our pid is equal to what `getcon` returns.
- // And by using `unwrap` we make sure that both also have to return successfully
- // fully to pass the test.
- assert_eq!(getpidcon(std::process::id() as i32).unwrap(), getcon().unwrap());
- }
}
diff --git a/keystore2/src/async_task.rs b/keystore2/src/async_task.rs
index 6548445..16401a4 100644
--- a/keystore2/src/async_task.rs
+++ b/keystore2/src/async_task.rs
@@ -27,6 +27,9 @@
thread,
};
+#[cfg(test)]
+mod tests;
+
#[derive(Debug, PartialEq, Eq)]
enum State {
Exiting,
@@ -256,279 +259,3 @@
state.state = State::Running;
}
}
-
-#[cfg(test)]
-mod tests {
- use super::{AsyncTask, Shelf};
- use std::sync::{
- mpsc::{channel, sync_channel, RecvTimeoutError},
- Arc,
- };
- use std::time::Duration;
-
- #[test]
- fn test_shelf() {
- let mut shelf = Shelf::default();
-
- let s = "A string".to_string();
- assert_eq!(shelf.put(s), None);
-
- let s2 = "Another string".to_string();
- assert_eq!(shelf.put(s2), Some("A string".to_string()));
-
- // Put something of a different type on the shelf.
- #[derive(Debug, PartialEq, Eq)]
- struct Elf {
- pub name: String,
- }
- let e1 = Elf { name: "Glorfindel".to_string() };
- assert_eq!(shelf.put(e1), None);
-
- // The String value is still on the shelf.
- let s3 = shelf.get_downcast_ref::<String>().unwrap();
- assert_eq!(s3, "Another string");
-
- // As is the Elf.
- {
- let e2 = shelf.get_downcast_mut::<Elf>().unwrap();
- assert_eq!(e2.name, "Glorfindel");
- e2.name = "Celeborn".to_string();
- }
-
- // Take the Elf off the shelf.
- let e3 = shelf.remove_downcast_ref::<Elf>().unwrap();
- assert_eq!(e3.name, "Celeborn");
-
- assert_eq!(shelf.remove_downcast_ref::<Elf>(), None);
-
- // No u64 value has been put on the shelf, so getting one gives the default value.
- {
- let i = shelf.get_mut::<u64>();
- assert_eq!(*i, 0);
- *i = 42;
- }
- let i2 = shelf.get_downcast_ref::<u64>().unwrap();
- assert_eq!(*i2, 42);
-
- // No i32 value has ever been seen near the shelf.
- assert_eq!(shelf.get_downcast_ref::<i32>(), None);
- assert_eq!(shelf.get_downcast_mut::<i32>(), None);
- assert_eq!(shelf.remove_downcast_ref::<i32>(), None);
- }
-
- #[test]
- fn test_async_task() {
- let at = AsyncTask::default();
-
- // First queue up a job that blocks until we release it, to avoid
- // unpredictable synchronization.
- let (start_sender, start_receiver) = channel();
- at.queue_hi(move |shelf| {
- start_receiver.recv().unwrap();
- // Put a trace vector on the shelf
- shelf.put(Vec::<String>::new());
- });
-
- // Queue up some high-priority and low-priority jobs.
- for i in 0..3 {
- let j = i;
- at.queue_lo(move |shelf| {
- let trace = shelf.get_mut::<Vec<String>>();
- trace.push(format!("L{}", j));
- });
- let j = i;
- at.queue_hi(move |shelf| {
- let trace = shelf.get_mut::<Vec<String>>();
- trace.push(format!("H{}", j));
- });
- }
-
- // Finally queue up a low priority job that emits the trace.
- let (trace_sender, trace_receiver) = channel();
- at.queue_lo(move |shelf| {
- let trace = shelf.get_downcast_ref::<Vec<String>>().unwrap();
- trace_sender.send(trace.clone()).unwrap();
- });
-
- // Ready, set, go.
- start_sender.send(()).unwrap();
- let trace = trace_receiver.recv().unwrap();
-
- assert_eq!(trace, vec!["H0", "H1", "H2", "L0", "L1", "L2"]);
- }
-
- #[test]
- fn test_async_task_chain() {
- let at = Arc::new(AsyncTask::default());
- let (sender, receiver) = channel();
- // Queue up a job that will queue up another job. This confirms
- // that the job is not invoked with any internal AsyncTask locks held.
- let at_clone = at.clone();
- at.queue_hi(move |_shelf| {
- at_clone.queue_lo(move |_shelf| {
- sender.send(()).unwrap();
- });
- });
- receiver.recv().unwrap();
- }
-
- #[test]
- #[should_panic]
- fn test_async_task_panic() {
- let at = AsyncTask::default();
- at.queue_hi(|_shelf| {
- panic!("Panic from queued job");
- });
- // Queue another job afterwards to ensure that the async thread gets joined.
- let (done_sender, done_receiver) = channel();
- at.queue_hi(move |_shelf| {
- done_sender.send(()).unwrap();
- });
- done_receiver.recv().unwrap();
- }
-
- #[test]
- fn test_async_task_idle() {
- let at = AsyncTask::new(Duration::from_secs(3));
- // Need a SyncSender as it is Send+Sync.
- let (idle_done_sender, idle_done_receiver) = sync_channel::<()>(3);
- at.add_idle(move |_shelf| {
- idle_done_sender.send(()).unwrap();
- });
-
- // Queue up some high-priority and low-priority jobs that take time.
- for _i in 0..3 {
- at.queue_lo(|_shelf| {
- std::thread::sleep(Duration::from_millis(500));
- });
- at.queue_hi(|_shelf| {
- std::thread::sleep(Duration::from_millis(500));
- });
- }
- // Final low-priority job.
- let (done_sender, done_receiver) = channel();
- at.queue_lo(move |_shelf| {
- done_sender.send(()).unwrap();
- });
-
- // Nothing happens until the last job completes.
- assert_eq!(
- idle_done_receiver.recv_timeout(Duration::from_secs(1)),
- Err(RecvTimeoutError::Timeout)
- );
- done_receiver.recv().unwrap();
- // Now that the last low-priority job has completed, the idle task should
- // fire pretty much immediately.
- idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
-
- // Idle callback not executed again even if we wait for a while.
- assert_eq!(
- idle_done_receiver.recv_timeout(Duration::from_secs(3)),
- Err(RecvTimeoutError::Timeout)
- );
-
- // However, if more work is done then there's another chance to go idle.
- let (done_sender, done_receiver) = channel();
- at.queue_hi(move |_shelf| {
- std::thread::sleep(Duration::from_millis(500));
- done_sender.send(()).unwrap();
- });
- // Idle callback not immediately executed, because the high priority
- // job is taking a while.
- assert_eq!(
- idle_done_receiver.recv_timeout(Duration::from_millis(1)),
- Err(RecvTimeoutError::Timeout)
- );
- done_receiver.recv().unwrap();
- idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
- }
-
- #[test]
- fn test_async_task_multiple_idle() {
- let at = AsyncTask::new(Duration::from_secs(3));
- let (idle_sender, idle_receiver) = sync_channel::<i32>(5);
- // Queue a high priority job to start things off
- at.queue_hi(|_shelf| {
- std::thread::sleep(Duration::from_millis(500));
- });
-
- // Multiple idle callbacks.
- for i in 0..3 {
- let idle_sender = idle_sender.clone();
- at.add_idle(move |_shelf| {
- idle_sender.send(i).unwrap();
- });
- }
-
- // Nothing happens immediately.
- assert_eq!(
- idle_receiver.recv_timeout(Duration::from_millis(1)),
- Err(RecvTimeoutError::Timeout)
- );
- // Wait for a moment and the idle jobs should have run.
- std::thread::sleep(Duration::from_secs(1));
-
- let mut results = Vec::new();
- while let Ok(i) = idle_receiver.recv_timeout(Duration::from_millis(1)) {
- results.push(i);
- }
- assert_eq!(results, [0, 1, 2]);
- }
-
- #[test]
- fn test_async_task_idle_queues_job() {
- let at = Arc::new(AsyncTask::new(Duration::from_secs(1)));
- let at_clone = at.clone();
- let (idle_sender, idle_receiver) = sync_channel::<i32>(100);
- // Add an idle callback that queues a low-priority job.
- at.add_idle(move |shelf| {
- at_clone.queue_lo(|_shelf| {
- // Slow things down so the channel doesn't fill up.
- std::thread::sleep(Duration::from_millis(50));
- });
- let i = shelf.get_mut::<i32>();
- idle_sender.send(*i).unwrap();
- *i += 1;
- });
-
- // Nothing happens immediately.
- assert_eq!(
- idle_receiver.recv_timeout(Duration::from_millis(1500)),
- Err(RecvTimeoutError::Timeout)
- );
-
- // Once we queue a normal job, things start.
- at.queue_hi(|_shelf| {});
- assert_eq!(0, idle_receiver.recv_timeout(Duration::from_millis(200)).unwrap());
-
- // The idle callback queues a job, and completion of that job
- // means the task is going idle again...so the idle callback will
- // be called repeatedly.
- assert_eq!(1, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
- assert_eq!(2, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
- assert_eq!(3, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
- }
-
- #[test]
- #[should_panic]
- fn test_async_task_idle_panic() {
- let at = AsyncTask::new(Duration::from_secs(1));
- let (idle_sender, idle_receiver) = sync_channel::<()>(3);
- // Add an idle callback that panics.
- at.add_idle(move |_shelf| {
- idle_sender.send(()).unwrap();
- panic!("Panic from idle callback");
- });
- // Queue a job to trigger idleness and ensuing panic.
- at.queue_hi(|_shelf| {});
- idle_receiver.recv().unwrap();
-
- // Queue another job afterwards to ensure that the async thread gets joined
- // and the panic detected.
- let (done_sender, done_receiver) = channel();
- at.queue_hi(move |_shelf| {
- done_sender.send(()).unwrap();
- });
- done_receiver.recv().unwrap();
- }
-}
diff --git a/keystore2/src/async_task/tests.rs b/keystore2/src/async_task/tests.rs
new file mode 100644
index 0000000..e67303e
--- /dev/null
+++ b/keystore2/src/async_task/tests.rs
@@ -0,0 +1,287 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Async task tests.
+use super::{AsyncTask, Shelf};
+use std::sync::{
+ mpsc::{channel, sync_channel, RecvTimeoutError},
+ Arc,
+};
+use std::time::Duration;
+
+#[test]
+fn test_shelf() {
+ let mut shelf = Shelf::default();
+
+ let s = "A string".to_string();
+ assert_eq!(shelf.put(s), None);
+
+ let s2 = "Another string".to_string();
+ assert_eq!(shelf.put(s2), Some("A string".to_string()));
+
+ // Put something of a different type on the shelf.
+ #[derive(Debug, PartialEq, Eq)]
+ struct Elf {
+ pub name: String,
+ }
+ let e1 = Elf { name: "Glorfindel".to_string() };
+ assert_eq!(shelf.put(e1), None);
+
+ // The String value is still on the shelf.
+ let s3 = shelf.get_downcast_ref::<String>().unwrap();
+ assert_eq!(s3, "Another string");
+
+ // As is the Elf.
+ {
+ let e2 = shelf.get_downcast_mut::<Elf>().unwrap();
+ assert_eq!(e2.name, "Glorfindel");
+ e2.name = "Celeborn".to_string();
+ }
+
+ // Take the Elf off the shelf.
+ let e3 = shelf.remove_downcast_ref::<Elf>().unwrap();
+ assert_eq!(e3.name, "Celeborn");
+
+ assert_eq!(shelf.remove_downcast_ref::<Elf>(), None);
+
+ // No u64 value has been put on the shelf, so getting one gives the default value.
+ {
+ let i = shelf.get_mut::<u64>();
+ assert_eq!(*i, 0);
+ *i = 42;
+ }
+ let i2 = shelf.get_downcast_ref::<u64>().unwrap();
+ assert_eq!(*i2, 42);
+
+ // No i32 value has ever been seen near the shelf.
+ assert_eq!(shelf.get_downcast_ref::<i32>(), None);
+ assert_eq!(shelf.get_downcast_mut::<i32>(), None);
+ assert_eq!(shelf.remove_downcast_ref::<i32>(), None);
+}
+
+#[test]
+fn test_async_task() {
+ let at = AsyncTask::default();
+
+ // First queue up a job that blocks until we release it, to avoid
+ // unpredictable synchronization.
+ let (start_sender, start_receiver) = channel();
+ at.queue_hi(move |shelf| {
+ start_receiver.recv().unwrap();
+ // Put a trace vector on the shelf
+ shelf.put(Vec::<String>::new());
+ });
+
+ // Queue up some high-priority and low-priority jobs.
+ for i in 0..3 {
+ let j = i;
+ at.queue_lo(move |shelf| {
+ let trace = shelf.get_mut::<Vec<String>>();
+ trace.push(format!("L{}", j));
+ });
+ let j = i;
+ at.queue_hi(move |shelf| {
+ let trace = shelf.get_mut::<Vec<String>>();
+ trace.push(format!("H{}", j));
+ });
+ }
+
+ // Finally queue up a low priority job that emits the trace.
+ let (trace_sender, trace_receiver) = channel();
+ at.queue_lo(move |shelf| {
+ let trace = shelf.get_downcast_ref::<Vec<String>>().unwrap();
+ trace_sender.send(trace.clone()).unwrap();
+ });
+
+ // Ready, set, go.
+ start_sender.send(()).unwrap();
+ let trace = trace_receiver.recv().unwrap();
+
+ assert_eq!(trace, vec!["H0", "H1", "H2", "L0", "L1", "L2"]);
+}
+
+#[test]
+fn test_async_task_chain() {
+ let at = Arc::new(AsyncTask::default());
+ let (sender, receiver) = channel();
+ // Queue up a job that will queue up another job. This confirms
+ // that the job is not invoked with any internal AsyncTask locks held.
+ let at_clone = at.clone();
+ at.queue_hi(move |_shelf| {
+ at_clone.queue_lo(move |_shelf| {
+ sender.send(()).unwrap();
+ });
+ });
+ receiver.recv().unwrap();
+}
+
+#[test]
+#[should_panic]
+fn test_async_task_panic() {
+ let at = AsyncTask::default();
+ at.queue_hi(|_shelf| {
+ panic!("Panic from queued job");
+ });
+ // Queue another job afterwards to ensure that the async thread gets joined.
+ let (done_sender, done_receiver) = channel();
+ at.queue_hi(move |_shelf| {
+ done_sender.send(()).unwrap();
+ });
+ done_receiver.recv().unwrap();
+}
+
+#[test]
+fn test_async_task_idle() {
+ let at = AsyncTask::new(Duration::from_secs(3));
+ // Need a SyncSender as it is Send+Sync.
+ let (idle_done_sender, idle_done_receiver) = sync_channel::<()>(3);
+ at.add_idle(move |_shelf| {
+ idle_done_sender.send(()).unwrap();
+ });
+
+ // Queue up some high-priority and low-priority jobs that take time.
+ for _i in 0..3 {
+ at.queue_lo(|_shelf| {
+ std::thread::sleep(Duration::from_millis(500));
+ });
+ at.queue_hi(|_shelf| {
+ std::thread::sleep(Duration::from_millis(500));
+ });
+ }
+ // Final low-priority job.
+ let (done_sender, done_receiver) = channel();
+ at.queue_lo(move |_shelf| {
+ done_sender.send(()).unwrap();
+ });
+
+ // Nothing happens until the last job completes.
+ assert_eq!(
+ idle_done_receiver.recv_timeout(Duration::from_secs(1)),
+ Err(RecvTimeoutError::Timeout)
+ );
+ done_receiver.recv().unwrap();
+ // Now that the last low-priority job has completed, the idle task should
+ // fire pretty much immediately.
+ idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
+
+ // Idle callback not executed again even if we wait for a while.
+ assert_eq!(
+ idle_done_receiver.recv_timeout(Duration::from_secs(3)),
+ Err(RecvTimeoutError::Timeout)
+ );
+
+ // However, if more work is done then there's another chance to go idle.
+ let (done_sender, done_receiver) = channel();
+ at.queue_hi(move |_shelf| {
+ std::thread::sleep(Duration::from_millis(500));
+ done_sender.send(()).unwrap();
+ });
+ // Idle callback not immediately executed, because the high priority
+ // job is taking a while.
+ assert_eq!(
+ idle_done_receiver.recv_timeout(Duration::from_millis(1)),
+ Err(RecvTimeoutError::Timeout)
+ );
+ done_receiver.recv().unwrap();
+ idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
+}
+
+#[test]
+fn test_async_task_multiple_idle() {
+ let at = AsyncTask::new(Duration::from_secs(3));
+ let (idle_sender, idle_receiver) = sync_channel::<i32>(5);
+ // Queue a high priority job to start things off
+ at.queue_hi(|_shelf| {
+ std::thread::sleep(Duration::from_millis(500));
+ });
+
+ // Multiple idle callbacks.
+ for i in 0..3 {
+ let idle_sender = idle_sender.clone();
+ at.add_idle(move |_shelf| {
+ idle_sender.send(i).unwrap();
+ });
+ }
+
+ // Nothing happens immediately.
+ assert_eq!(
+ idle_receiver.recv_timeout(Duration::from_millis(1)),
+ Err(RecvTimeoutError::Timeout)
+ );
+ // Wait for a moment and the idle jobs should have run.
+ std::thread::sleep(Duration::from_secs(1));
+
+ let mut results = Vec::new();
+ while let Ok(i) = idle_receiver.recv_timeout(Duration::from_millis(1)) {
+ results.push(i);
+ }
+ assert_eq!(results, [0, 1, 2]);
+}
+
+#[test]
+fn test_async_task_idle_queues_job() {
+ let at = Arc::new(AsyncTask::new(Duration::from_secs(1)));
+ let at_clone = at.clone();
+ let (idle_sender, idle_receiver) = sync_channel::<i32>(100);
+ // Add an idle callback that queues a low-priority job.
+ at.add_idle(move |shelf| {
+ at_clone.queue_lo(|_shelf| {
+ // Slow things down so the channel doesn't fill up.
+ std::thread::sleep(Duration::from_millis(50));
+ });
+ let i = shelf.get_mut::<i32>();
+ idle_sender.send(*i).unwrap();
+ *i += 1;
+ });
+
+ // Nothing happens immediately.
+ assert_eq!(
+ idle_receiver.recv_timeout(Duration::from_millis(1500)),
+ Err(RecvTimeoutError::Timeout)
+ );
+
+ // Once we queue a normal job, things start.
+ at.queue_hi(|_shelf| {});
+ assert_eq!(0, idle_receiver.recv_timeout(Duration::from_millis(200)).unwrap());
+
+ // The idle callback queues a job, and completion of that job
+ // means the task is going idle again...so the idle callback will
+ // be called repeatedly.
+ assert_eq!(1, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
+ assert_eq!(2, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
+ assert_eq!(3, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
+}
+
+#[test]
+#[should_panic]
+fn test_async_task_idle_panic() {
+ let at = AsyncTask::new(Duration::from_secs(1));
+ let (idle_sender, idle_receiver) = sync_channel::<()>(3);
+ // Add an idle callback that panics.
+ at.add_idle(move |_shelf| {
+ idle_sender.send(()).unwrap();
+ panic!("Panic from idle callback");
+ });
+ // Queue a job to trigger idleness and ensuing panic.
+ at.queue_hi(|_shelf| {});
+ idle_receiver.recv().unwrap();
+
+ // Queue another job afterwards to ensure that the async thread gets joined
+ // and the panic detected.
+ let (done_sender, done_receiver) = channel();
+ at.queue_hi(move |_shelf| {
+ done_sender.send(()).unwrap();
+ });
+ done_receiver.recv().unwrap();
+}
diff --git a/keystore2/src/attestation_key_utils.rs b/keystore2/src/attestation_key_utils.rs
index 184b3cb..4a8923c 100644
--- a/keystore2/src/attestation_key_utils.rs
+++ b/keystore2/src/attestation_key_utils.rs
@@ -23,7 +23,7 @@
use crate::remote_provisioning::RemProvState;
use crate::utils::check_key_permission;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- AttestationKey::AttestationKey, Certificate::Certificate, KeyParameter::KeyParameter, Tag::Tag,
+ AttestationKey::AttestationKey, KeyParameter::KeyParameter, Tag::Tag,
};
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
@@ -37,7 +37,8 @@
pub enum AttestationKeyInfo {
RkpdProvisioned {
attestation_key: AttestationKey,
- attestation_certs: Certificate,
+ /// Concatenated chain of DER-encoded certificates (ending with the root).
+ attestation_certs: Vec<u8>,
},
UserGenerated {
key_id_guard: KeyIdGuard,
diff --git a/keystore2/src/audit_log.rs b/keystore2/src/audit_log.rs
index 8d9735e..4952b3b 100644
--- a/keystore2/src/audit_log.rs
+++ b/keystore2/src/audit_log.rs
@@ -34,8 +34,8 @@
match domain {
Domain::APP => uid,
Domain::SELINUX => (nspace | FLAG_NAMESPACE) as i32,
- _ => {
- log::info!("Not logging audit event for key with unexpected domain");
+ d => {
+ log::info!("Not logging audit event for key with domain {d:?}");
0
}
}
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index c76f86b..7812df6 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -20,7 +20,6 @@
use crate::ks_err;
use crate::permission::KeystorePerm;
use crate::utils::{check_keystore_permission, watchdog as wd};
-use aconfig_android_hardware_biometrics_rust;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
};
@@ -36,7 +35,6 @@
use anyhow::{Context, Result};
use keystore2_crypto::Password;
use keystore2_selinux as selinux;
-use std::ffi::CString;
/// This is the Authorization error type, it wraps binder exceptions and the
/// Authorization ResponseCode
@@ -288,13 +286,6 @@
secure_user_id: i64,
auth_types: &[HardwareAuthenticatorType],
) -> binder::Result<i64> {
- if aconfig_android_hardware_biometrics_rust::last_authentication_time() {
- self.get_last_auth_time(secure_user_id, auth_types).map_err(into_logged_binder)
- } else {
- Err(BinderStatus::new_service_specific_error(
- ResponseCode::PERMISSION_DENIED.0,
- Some(CString::new("Feature is not enabled.").unwrap().as_c_str()),
- ))
- }
+ self.get_last_auth_time(secure_user_id, auth_types).map_err(into_logged_binder)
}
}
diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs
index 09b84ec..b6f308b 100644
--- a/keystore2/src/crypto/lib.rs
+++ b/keystore2/src/crypto/lib.rs
@@ -317,7 +317,7 @@
}
}
-impl<'a> BorrowedECPoint<'a> {
+impl BorrowedECPoint<'_> {
/// Get the wrapped EC_POINT object.
pub fn get_point(&self) -> &EC_POINT {
// Safety: We only create BorrowedECPoint objects for valid EC_POINTs.
diff --git a/keystore2/src/crypto/tests/certificate_utils_test.cpp b/keystore2/src/crypto/tests/certificate_utils_test.cpp
index a851798..e2f7cdb 100644
--- a/keystore2/src/crypto/tests/certificate_utils_test.cpp
+++ b/keystore2/src/crypto/tests/certificate_utils_test.cpp
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-
#include "certificate_utils.h"
+#include <gtest/gtest.h>
#include <openssl/err.h>
#include <openssl/evp.h>
@@ -231,6 +230,72 @@
return s.str();
}
+static std::optional<std::vector<uint8_t>> EncodeX509Algor(const X509_ALGOR* alg) {
+ uint8_t* der = nullptr;
+ int der_len = i2d_X509_ALGOR(alg, &der);
+ if (der_len < 0) {
+ return std::nullopt;
+ }
+ std::vector<uint8_t> ret(der, der + der_len);
+ OPENSSL_free(der);
+ return ret;
+}
+
+// `x509_verify` not working with RSA-PSS & SHA1/SHA224 digests. so, manually
+// verify the certificate with RSA-PSS & SHA1/SHA224 digests.
+// BoringSSL after https://boringssl-review.googlesource.com/c/boringssl/+/53865
+// does not support RSA-PSS with SHA1/SHA224 digests.
+static void verifyCertFieldsExplicitly(X509* cert, Digest digest) {
+ // RSA-PSS-SHA1 AlgorithmIdentifier DER encoded value
+ const std::vector<uint8_t> expected_rsa_pss_sha1 = {
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30, 0x00,
+ };
+ // RSA-PSS-SHA224 AlgorithmIdentifier DER encoded value
+ const std::vector<uint8_t> expected_rsa_pss_sha224 = {
+ 0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
+ 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
+ 0x02, 0x04, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+ 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x1c,
+ };
+ const X509_ALGOR* alg;
+ const ASN1_BIT_STRING* sig;
+ const EVP_MD* evp_digest;
+ X509_get0_signature(&sig, &alg, cert);
+ auto encoded = EncodeX509Algor(alg);
+ ASSERT_TRUE(encoded);
+
+ // Check the AlgorithmIdentifiers.
+ if (digest == Digest::SHA1) {
+ evp_digest = EVP_sha1();
+ EXPECT_EQ(encoded.value(), expected_rsa_pss_sha1);
+ } else if (digest == Digest::SHA224) {
+ evp_digest = EVP_sha224();
+ EXPECT_EQ(encoded.value(), expected_rsa_pss_sha224);
+ } else {
+ GTEST_FAIL()
+ << "Error: This is expected to be used only for RSA-PSS with SHA1/SHA224 as digests";
+ }
+
+ // Check the signature.
+ EVP_PKEY_Ptr pubkey(X509_get_pubkey(cert));
+ ASSERT_TRUE(pubkey);
+
+ uint8_t* tbs = nullptr;
+ int tbs_len = i2d_X509_tbs(cert, &tbs);
+ ASSERT_GT(tbs_len, 0);
+
+ size_t sig_len;
+ ASSERT_TRUE(ASN1_BIT_STRING_num_bytes(sig, &sig_len));
+ EVP_PKEY_CTX* pctx;
+ bssl::ScopedEVP_MD_CTX ctx;
+ ASSERT_TRUE(EVP_DigestVerifyInit(ctx.get(), &pctx, evp_digest, nullptr, pubkey.get()));
+ ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING));
+ // The salt length should match the digest length.
+ ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1));
+ EXPECT_TRUE(EVP_DigestVerify(ctx.get(), ASN1_STRING_get0_data(sig), sig_len, tbs, tbs_len));
+}
+
INSTANTIATE_TEST_SUITE_P(CertSigningWithCallbackRsa, CertificateUtilsWithRsa,
testing::Combine(testing::ValuesIn(rsa_key_sizes),
testing::ValuesIn(rsa_paddings),
@@ -315,10 +380,10 @@
EVP_PKEY_Ptr decoded_pkey(X509_get_pubkey(decoded_cert.get()));
if ((padding == Padding::PSS) && (digest == Digest::SHA1 || digest == Digest::SHA224)) {
// BoringSSL after https://boringssl-review.googlesource.com/c/boringssl/+/53865
- // does not support these PSS combinations, so skip certificate verification for them
- // and just check _something_ was returned.
+ // does not support these PSS combinations, so verify these certificates manually.
EXPECT_NE(decoded_cert.get(), nullptr);
EXPECT_NE(decoded_pkey.get(), nullptr);
+ verifyCertFieldsExplicitly(decoded_cert.get(), digest);
} else {
ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get()));
}
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index a9a1c4b..626a1c0 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -45,6 +45,9 @@
pub(crate) mod utils;
mod versioning;
+#[cfg(test)]
+pub mod tests;
+
use crate::gc::Gc;
use crate::impl_metadata; // This is in database/utils.rs
use crate::key_parameter::{KeyParameter, KeyParameterValue, Tag};
@@ -67,12 +70,11 @@
};
use anyhow::{anyhow, Context, Result};
use keystore2_flags;
-use std::{convert::TryFrom, convert::TryInto, ops::Deref, time::SystemTimeError};
+use std::{convert::TryFrom, convert::TryInto, ops::Deref, sync::LazyLock, time::SystemTimeError};
use utils as db_utils;
use utils::SqlField;
use keystore2_crypto::ZVec;
-use lazy_static::lazy_static;
use log::error;
#[cfg(not(test))]
use rand::prelude::random;
@@ -123,6 +125,14 @@
}
}
+/// Access information for a key.
+#[derive(Debug)]
+struct KeyAccessInfo {
+ key_id: i64,
+ descriptor: KeyDescriptor,
+ vector: Option<KeyPermSet>,
+}
+
/// If the database returns a busy error code, retry after this interval.
const DB_BUSY_RETRY_INTERVAL: Duration = Duration::from_micros(500);
@@ -498,6 +508,40 @@
}
}
+/// Current state of a `blobentry` row.
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Default)]
+enum BlobState {
+ #[default]
+ /// Current blobentry (of its `subcomponent_type`) for the associated key.
+ Current,
+ /// Blobentry that is no longer the current blob (of its `subcomponent_type`) for the associated
+ /// key.
+ Superseded,
+ /// Blobentry for a key that no longer exists.
+ Orphaned,
+}
+
+impl ToSql for BlobState {
+ fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
+ match self {
+ Self::Current => Ok(ToSqlOutput::Owned(Value::Integer(0))),
+ Self::Superseded => Ok(ToSqlOutput::Owned(Value::Integer(1))),
+ Self::Orphaned => Ok(ToSqlOutput::Owned(Value::Integer(2))),
+ }
+ }
+}
+
+impl FromSql for BlobState {
+ fn column_result(value: ValueRef) -> FromSqlResult<Self> {
+ match i64::column_result(value)? {
+ 0 => Ok(Self::Current),
+ 1 => Ok(Self::Superseded),
+ 2 => Ok(Self::Orphaned),
+ v => Err(FromSqlError::OutOfRange(v)),
+ }
+ }
+}
+
/// Keys have a KeyMint blob component and optional public certificate and
/// certificate chain components.
/// KeyEntryLoadBits is a bitmap that indicates to `KeystoreDB::load_key_entry`
@@ -526,9 +570,7 @@
}
}
-lazy_static! {
- static ref KEY_ID_LOCK: KeyIdLockDb = KeyIdLockDb::new();
-}
+static KEY_ID_LOCK: LazyLock<KeyIdLockDb> = LazyLock::new(KeyIdLockDb::new);
struct KeyIdLockDb {
locked_keys: Mutex<HashSet<i64>>,
@@ -870,8 +912,9 @@
impl KeystoreDB {
const UNASSIGNED_KEY_ID: i64 = -1i64;
- const CURRENT_DB_VERSION: u32 = 1;
- const UPGRADERS: &'static [fn(&Transaction) -> Result<u32>] = &[Self::from_0_to_1];
+ const CURRENT_DB_VERSION: u32 = 2;
+ const UPGRADERS: &'static [fn(&Transaction) -> Result<u32>] =
+ &[Self::from_0_to_1, Self::from_1_to_2];
/// Name of the file that holds the cross-boot persistent database.
pub const PERSISTENT_DB_FILENAME: &'static str = "persistent.sqlite";
@@ -913,9 +956,77 @@
params![KeyLifeCycle::Unreferenced, Tag::MAX_BOOT_LEVEL.0, BlobMetaData::MaxBootLevel],
)
.context(ks_err!("Failed to delete logical boot level keys."))?;
+
+ // DB version is now 1.
Ok(1)
}
+ // This upgrade function adds an additional `state INTEGER` column to the blobentry
+ // table, and populates it based on whether each blob is the most recent of its type for
+ // the corresponding key.
+ fn from_1_to_2(tx: &Transaction) -> Result<u32> {
+ tx.execute(
+ "ALTER TABLE persistent.blobentry ADD COLUMN state INTEGER DEFAULT 0;",
+ params![],
+ )
+ .context(ks_err!("Failed to add state column"))?;
+
+ // Mark keyblobs that are not the most recent for their corresponding key.
+ // This may take a while if there are excessive numbers of keys in the database.
+ let _wp = wd::watch("KeystoreDB::from_1_to_2 mark all non-current keyblobs");
+ let sc_key_blob = SubComponentType::KEY_BLOB;
+ let mut stmt = tx
+ .prepare(
+ "UPDATE persistent.blobentry SET state=?
+ WHERE subcomponent_type = ?
+ AND id NOT IN (
+ SELECT MAX(id) FROM persistent.blobentry
+ WHERE subcomponent_type = ?
+ GROUP BY keyentryid, subcomponent_type
+ );",
+ )
+ .context("Trying to prepare query to mark superseded keyblobs")?;
+ stmt.execute(params![BlobState::Superseded, sc_key_blob, sc_key_blob])
+ .context(ks_err!("Failed to set state=superseded state for keyblobs"))?;
+ log::info!("marked non-current blobentry rows for keyblobs as superseded");
+
+ // Mark keyblobs that don't have a corresponding key.
+ // This may take a while if there are excessive numbers of keys in the database.
+ let _wp = wd::watch("KeystoreDB::from_1_to_2 mark all orphaned keyblobs");
+ let mut stmt = tx
+ .prepare(
+ "UPDATE persistent.blobentry SET state=?
+ WHERE subcomponent_type = ?
+ AND NOT EXISTS (SELECT id FROM persistent.keyentry
+ WHERE id = keyentryid);",
+ )
+ .context("Trying to prepare query to mark orphaned keyblobs")?;
+ stmt.execute(params![BlobState::Orphaned, sc_key_blob])
+ .context(ks_err!("Failed to set state=orphaned for keyblobs"))?;
+ log::info!("marked orphaned blobentry rows for keyblobs");
+
+ // Add an index to make it fast to find out of date blobentry rows.
+ let _wp = wd::watch("KeystoreDB::from_1_to_2 add blobentry index");
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.blobentry_state_index
+ ON blobentry(subcomponent_type, state);",
+ [],
+ )
+ .context("Failed to create index blobentry_state_index.")?;
+
+ // Add an index to make it fast to find unreferenced keyentry rows.
+ let _wp = wd::watch("KeystoreDB::from_1_to_2 add keyentry state index");
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.keyentry_state_index
+ ON keyentry(state);",
+ [],
+ )
+ .context("Failed to create index keyentry_state_index.")?;
+
+ // DB version is now 2.
+ Ok(2)
+ }
+
fn init_tables(tx: &Transaction) -> Result<()> {
tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.keyentry (
@@ -944,12 +1055,21 @@
)
.context("Failed to create index keyentry_domain_namespace_index.")?;
+ // Index added in v2 of database schema.
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.keyentry_state_index
+ ON keyentry(state);",
+ [],
+ )
+ .context("Failed to create index keyentry_state_index.")?;
+
tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.blobentry (
id INTEGER PRIMARY KEY,
subcomponent_type INTEGER,
keyentryid INTEGER,
- blob BLOB);",
+ blob BLOB,
+ state INTEGER DEFAULT 0);", // `state` added in v2 of schema
[],
)
.context("Failed to initialize \"blobentry\" table.")?;
@@ -961,6 +1081,14 @@
)
.context("Failed to create index blobentry_keyentryid_index.")?;
+ // Index added in v2 of database schema.
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.blobentry_state_index
+ ON blobentry(subcomponent_type, state);",
+ [],
+ )
+ .context("Failed to create index blobentry_state_index.")?;
+
tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.blobmetadata (
id INTEGER PRIMARY KEY,
@@ -1113,11 +1241,11 @@
)
}
- /// Fetches a storage statisitics atom for a given storage type. For storage
+ /// Fetches a storage statistics atom for a given storage type. For storage
/// types that map to a table, information about the table's storage is
/// returned. Requests for storage types that are not DB tables return None.
pub fn get_storage_stat(&mut self, storage_type: MetricsStorage) -> Result<StorageStats> {
- let _wp = wd::watch("KeystoreDB::get_storage_stat");
+ let _wp = wd::watch_millis_with("KeystoreDB::get_storage_stat", 500, storage_type);
match storage_type {
MetricsStorage::DATABASE => self.get_total_size(),
@@ -1195,9 +1323,28 @@
Self::cleanup_unreferenced(tx).context("Trying to cleanup unreferenced.")?;
- // Find up to `max_blobs` more superseded key blobs, load their metadata and return it.
- let result: Vec<(i64, Vec<u8>)> = {
- let _wp = wd::watch("handle_next_superseded_blob find_next");
+ // Find up to `max_blobs` more out-of-date key blobs, load their metadata and return it.
+ let result: Vec<(i64, Vec<u8>)> = if keystore2_flags::use_blob_state_column() {
+ let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob find_next v2");
+ let mut stmt = tx
+ .prepare(
+ "SELECT id, blob FROM persistent.blobentry
+ WHERE subcomponent_type = ? AND state != ?
+ LIMIT ?;",
+ )
+ .context("Trying to prepare query for superseded blobs.")?;
+
+ let rows = stmt
+ .query_map(
+ params![SubComponentType::KEY_BLOB, BlobState::Current, max_blobs as i64],
+ |row| Ok((row.get(0)?, row.get(1)?)),
+ )
+ .context("Trying to query superseded blob.")?;
+
+ rows.collect::<Result<Vec<(i64, Vec<u8>)>, rusqlite::Error>>()
+ .context("Trying to extract superseded blobs.")?
+ } else {
+ let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob find_next v1");
let mut stmt = tx
.prepare(
"SELECT id, blob FROM persistent.blobentry
@@ -1228,7 +1375,7 @@
.context("Trying to extract superseded blobs.")?
};
- let _wp = wd::watch("handle_next_superseded_blob load_metadata");
+ let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob load_metadata");
let result = result
.into_iter()
.map(|(blob_id, blob)| {
@@ -1244,22 +1391,32 @@
return Ok(result).no_gc();
}
- // We did not find any superseded key blob, so let's remove other superseded blob in
- // one transaction.
- let _wp = wd::watch("handle_next_superseded_blob delete");
- tx.execute(
- "DELETE FROM persistent.blobentry
- WHERE NOT subcomponent_type = ?
- AND (
- id NOT IN (
- SELECT MAX(id) FROM persistent.blobentry
- WHERE NOT subcomponent_type = ?
- GROUP BY keyentryid, subcomponent_type
- ) OR keyentryid NOT IN (SELECT id FROM persistent.keyentry)
- );",
- params![SubComponentType::KEY_BLOB, SubComponentType::KEY_BLOB],
- )
- .context("Trying to purge superseded blobs.")?;
+ // We did not find any out-of-date key blobs, so let's remove other types of superseded
+ // blob in one transaction.
+ if keystore2_flags::use_blob_state_column() {
+ let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob delete v2");
+ tx.execute(
+ "DELETE FROM persistent.blobentry
+ WHERE subcomponent_type != ? AND state != ?;",
+ params![SubComponentType::KEY_BLOB, BlobState::Current],
+ )
+ .context("Trying to purge out-of-date blobs (other than keyblobs)")?;
+ } else {
+ let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob delete v1");
+ tx.execute(
+ "DELETE FROM persistent.blobentry
+ WHERE NOT subcomponent_type = ?
+ AND (
+ id NOT IN (
+ SELECT MAX(id) FROM persistent.blobentry
+ WHERE NOT subcomponent_type = ?
+ GROUP BY keyentryid, subcomponent_type
+ ) OR keyentryid NOT IN (SELECT id FROM persistent.keyentry)
+ );",
+ params![SubComponentType::KEY_BLOB, SubComponentType::KEY_BLOB],
+ )
+ .context("Trying to purge superseded blobs.")?;
+ }
Ok(vec![]).no_gc()
})
@@ -1530,18 +1687,33 @@
) -> Result<()> {
match (blob, sc_type) {
(Some(blob), _) => {
+ // Mark any previous blobentry(s) of the same type for the same key as superseded.
+ tx.execute(
+ "UPDATE persistent.blobentry SET state = ?
+ WHERE keyentryid = ? AND subcomponent_type = ?",
+ params![BlobState::Superseded, key_id, sc_type],
+ )
+ .context(ks_err!(
+ "Failed to mark prior {sc_type:?} blobentrys for {key_id} as superseded"
+ ))?;
+
+ // Now insert the new, un-superseded, blob. (If this fails, the marking of
+ // old blobs as superseded will be rolled back, because we're inside a
+ // transaction.)
tx.execute(
"INSERT INTO persistent.blobentry
(subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
params![sc_type, key_id, blob],
)
.context(ks_err!("Failed to insert blob."))?;
+
if let Some(blob_metadata) = blob_metadata {
let blob_id = tx
.query_row("SELECT MAX(id) FROM persistent.blobentry;", [], |row| {
row.get(0)
})
.context(ks_err!("Failed to get new blob id."))?;
+
blob_metadata
.store_in_db(blob_id, tx)
.context(ks_err!("Trying to store blob metadata."))?;
@@ -1899,6 +2071,7 @@
/// `access_vector`.
/// * Domain::KEY_ID: The keyentry table is queried for the owning `domain` and
/// `namespace`.
+ ///
/// In each case the information returned is sufficient to perform the access
/// check and the key id can be used to load further key artifacts.
fn load_access_tuple(
@@ -1906,7 +2079,7 @@
key: &KeyDescriptor,
key_type: KeyType,
caller_uid: u32,
- ) -> Result<(i64, KeyDescriptor, Option<KeyPermSet>)> {
+ ) -> Result<KeyAccessInfo> {
match key.domain {
// Domain App or SELinux. In this case we load the key_id from
// the keyentry database for further loading of key components.
@@ -1922,7 +2095,7 @@
let key_id = Self::load_key_entry_id(tx, &access_key, key_type)
.with_context(|| format!("With key.domain = {:?}.", access_key.domain))?;
- Ok((key_id, access_key, None))
+ Ok(KeyAccessInfo { key_id, descriptor: access_key, vector: None })
}
// Domain::GRANT. In this case we load the key_id and the access_vector
@@ -1948,7 +2121,11 @@
))
})
.context("Domain::GRANT.")?;
- Ok((key_id, key.clone(), Some(access_vector.into())))
+ Ok(KeyAccessInfo {
+ key_id,
+ descriptor: key.clone(),
+ vector: Some(access_vector.into()),
+ })
}
// Domain::KEY_ID. In this case we load the domain and namespace from the
@@ -2004,7 +2181,7 @@
access_key.domain = domain;
access_key.nspace = namespace;
- Ok((key_id, access_key, access_vector))
+ Ok(KeyAccessInfo { key_id, descriptor: access_key, vector: access_vector })
}
_ => Err(anyhow!(KsError::Rc(ResponseCode::INVALID_ARGUMENT))),
}
@@ -2191,12 +2368,11 @@
.context(ks_err!("Failed to initialize transaction."))?;
// Load the key_id and complete the access control tuple.
- let (key_id, access_key_descriptor, access_vector) =
- Self::load_access_tuple(&tx, key, key_type, caller_uid).context(ks_err!())?;
+ let access = Self::load_access_tuple(&tx, key, key_type, caller_uid).context(ks_err!())?;
// Perform access control. It is vital that we return here if the permission is denied.
// So do not touch that '?' at the end.
- check_permission(&access_key_descriptor, access_vector).context(ks_err!())?;
+ check_permission(&access.descriptor, access.vector).context(ks_err!())?;
// KEY ID LOCK 2/2
// If we did not get a key id lock by now, it was because we got a key descriptor
@@ -2210,13 +2386,13 @@
// that the caller had access to the given key. But we need to make sure that the
// key id still exists. So we have to load the key entry by key id this time.
let (key_id_guard, tx) = match key_id_guard {
- None => match KEY_ID_LOCK.try_get(key_id) {
+ None => match KEY_ID_LOCK.try_get(access.key_id) {
None => {
// Roll back the transaction.
tx.rollback().context(ks_err!("Failed to roll back transaction."))?;
// Block until we have a key id lock.
- let key_id_guard = KEY_ID_LOCK.get(key_id);
+ let key_id_guard = KEY_ID_LOCK.get(access.key_id);
// Create a new transaction.
let tx = self
@@ -2230,7 +2406,7 @@
// alias may have been rebound after we rolled back the transaction.
&KeyDescriptor {
domain: Domain::KEY_ID,
- nspace: key_id,
+ nspace: access.key_id,
..Default::default()
},
key_type,
@@ -2262,6 +2438,17 @@
.context("Trying to delete keyparameters.")?;
tx.execute("DELETE FROM persistent.grant WHERE keyentryid = ?;", params![key_id])
.context("Trying to delete grants.")?;
+ // The associated blobentry rows are not immediately deleted when the owning keyentry is
+ // removed, because a KeyMint `deleteKey()` invocation is needed (specifically for the
+ // `KEY_BLOB`). That should not be done from within the database transaction. Also, calls
+ // to `deleteKey()` need to be delayed until the boot has completed, to avoid making
+ // permanent changes during an OTA before the point of no return. Mark the affected rows
+ // with `state=Orphaned` so a subsequent garbage collection can do the `deleteKey()`.
+ tx.execute(
+ "UPDATE persistent.blobentry SET state = ? WHERE keyentryid = ?",
+ params![BlobState::Orphaned, key_id],
+ )
+ .context("Trying to mark blobentrys as superseded")?;
Ok(updated != 0)
}
@@ -2277,16 +2464,15 @@
let _wp = wd::watch("KeystoreDB::unbind_key");
self.with_transaction(Immediate("TX_unbind_key"), |tx| {
- let (key_id, access_key_descriptor, access_vector) =
- Self::load_access_tuple(tx, key, key_type, caller_uid)
- .context("Trying to get access tuple.")?;
+ let access = Self::load_access_tuple(tx, key, key_type, caller_uid)
+ .context("Trying to get access tuple.")?;
// Perform access control. It is vital that we return here if the permission is denied.
// So do not touch that '?' at the end.
- check_permission(&access_key_descriptor, access_vector)
+ check_permission(&access.descriptor, access.vector)
.context("While checking permission.")?;
- Self::mark_unreferenced(tx, key_id)
+ Self::mark_unreferenced(tx, access.key_id)
.map(|need_gc| (need_gc, ()))
.context("Trying to mark the key unreferenced.")
})
@@ -2546,6 +2732,8 @@
/// The key descriptors will have the domain, nspace, and alias field set.
/// The returned list will be sorted by alias.
/// Domain must be APP or SELINUX, the caller must make sure of that.
+ /// Number of returned values is limited to 10,000 (which is empirically roughly
+ /// what will fit in a Binder message).
pub fn list_past_alias(
&mut self,
domain: Domain,
@@ -2563,7 +2751,8 @@
AND state = ?
AND key_type = ?
{}
- ORDER BY alias ASC;",
+ ORDER BY alias ASC
+ LIMIT 10000;",
if start_past_alias.is_some() { " AND alias > ?" } else { "" }
);
@@ -2653,7 +2842,7 @@
// We could check key.domain == Domain::GRANT and fail early.
// But even if we load the access tuple by grant here, the permission
// check denies the attempt to create a grant by grant descriptor.
- let (key_id, access_key_descriptor, _) =
+ let access =
Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(ks_err!())?;
// Perform access control. It is vital that we return here if the permission
@@ -2661,14 +2850,14 @@
// This permission check checks if the caller has the grant permission
// for the given key and in addition to all of the permissions
// expressed in `access_vector`.
- check_permission(&access_key_descriptor, &access_vector)
+ check_permission(&access.descriptor, &access_vector)
.context(ks_err!("check_permission failed"))?;
let grant_id = if let Some(grant_id) = tx
.query_row(
"SELECT id FROM persistent.grant
WHERE keyentryid = ? AND grantee = ?;",
- params![key_id, grantee_uid],
+ params![access.key_id, grantee_uid],
|row| row.get(0),
)
.optional()
@@ -2687,7 +2876,7 @@
tx.execute(
"INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector)
VALUES (?, ?, ?, ?);",
- params![id, grantee_uid, key_id, i32::from(access_vector)],
+ params![id, grantee_uid, access.key_id, i32::from(access_vector)],
)
})
.context(ks_err!())?
@@ -2712,18 +2901,17 @@
self.with_transaction(Immediate("TX_ungrant"), |tx| {
// Load the key_id and complete the access control tuple.
// We ignore the access vector here because grants cannot be granted.
- let (key_id, access_key_descriptor, _) =
+ let access =
Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(ks_err!())?;
// Perform access control. We must return here if the permission
// was denied. So do not touch the '?' at the end of this line.
- check_permission(&access_key_descriptor)
- .context(ks_err!("check_permission failed."))?;
+ check_permission(&access.descriptor).context(ks_err!("check_permission failed."))?;
tx.execute(
"DELETE FROM persistent.grant
WHERE keyentryid = ? AND grantee = ?;",
- params![key_id, grantee_uid],
+ params![access.key_id, grantee_uid],
)
.context("Failed to delete grant.")?;
@@ -2863,2621 +3051,11 @@
let app_uids_vec: Vec<i64> = app_uids_affected_by_sid.into_iter().collect();
Ok(app_uids_vec)
}
-}
-#[cfg(test)]
-pub mod tests {
-
- use super::*;
- use crate::key_parameter::{
- Algorithm, BlockMode, Digest, EcCurve, HardwareAuthenticatorType, KeyOrigin, KeyParameter,
- KeyParameterValue, KeyPurpose, PaddingMode, SecurityLevel,
- };
- use crate::key_perm_set;
- use crate::permission::{KeyPerm, KeyPermSet};
- use crate::super_key::{SuperKeyManager, USER_AFTER_FIRST_UNLOCK_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType};
- use keystore2_test_utils::TempDir;
- use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- HardwareAuthToken::HardwareAuthToken,
- HardwareAuthenticatorType::HardwareAuthenticatorType as kmhw_authenticator_type,
- };
- use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
- Timestamp::Timestamp,
- };
- use std::cell::RefCell;
- use std::collections::BTreeMap;
- use std::fmt::Write;
- use std::sync::atomic::{AtomicU8, Ordering};
- use std::sync::Arc;
- use std::thread;
- use std::time::{Duration, SystemTime};
- use crate::utils::AesGcm;
- #[cfg(disabled)]
- use std::time::Instant;
-
- pub fn new_test_db() -> Result<KeystoreDB> {
- let conn = KeystoreDB::make_connection("file::memory:")?;
-
- let mut db = KeystoreDB { conn, gc: None, perboot: Arc::new(perboot::PerbootDB::new()) };
- db.with_transaction(Immediate("TX_new_test_db"), |tx| {
- KeystoreDB::init_tables(tx).context("Failed to initialize tables.").no_gc()
- })?;
- Ok(db)
- }
-
- fn rebind_alias(
- db: &mut KeystoreDB,
- newid: &KeyIdGuard,
- alias: &str,
- domain: Domain,
- namespace: i64,
- ) -> Result<bool> {
- db.with_transaction(Immediate("TX_rebind_alias"), |tx| {
- KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace, KeyType::Client).no_gc()
- })
- .context(ks_err!())
- }
-
- #[test]
- fn datetime() -> Result<()> {
- let conn = Connection::open_in_memory()?;
- conn.execute("CREATE TABLE test (ts DATETIME);", [])?;
- let now = SystemTime::now();
- let duration = Duration::from_secs(1000);
- let then = now.checked_sub(duration).unwrap();
- let soon = now.checked_add(duration).unwrap();
- conn.execute(
- "INSERT INTO test (ts) VALUES (?), (?), (?);",
- params![DateTime::try_from(now)?, DateTime::try_from(then)?, DateTime::try_from(soon)?],
- )?;
- let mut stmt = conn.prepare("SELECT ts FROM test ORDER BY ts ASC;")?;
- let mut rows = stmt.query([])?;
- assert_eq!(DateTime::try_from(then)?, rows.next()?.unwrap().get(0)?);
- assert_eq!(DateTime::try_from(now)?, rows.next()?.unwrap().get(0)?);
- assert_eq!(DateTime::try_from(soon)?, rows.next()?.unwrap().get(0)?);
- assert!(rows.next()?.is_none());
- assert!(DateTime::try_from(then)? < DateTime::try_from(now)?);
- assert!(DateTime::try_from(then)? < DateTime::try_from(soon)?);
- assert!(DateTime::try_from(now)? < DateTime::try_from(soon)?);
- Ok(())
- }
-
- // Ensure that we're using the "injected" random function, not the real one.
- #[test]
- fn test_mocked_random() {
- let rand1 = random();
- let rand2 = random();
- let rand3 = random();
- if rand1 == rand2 {
- assert_eq!(rand2 + 1, rand3);
- } else {
- assert_eq!(rand1 + 1, rand2);
- assert_eq!(rand2, rand3);
- }
- }
-
- // Test that we have the correct tables.
- #[test]
- fn test_tables() -> Result<()> {
- let db = new_test_db()?;
- let tables = db
- .conn
- .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
- .query_map(params![], |row| row.get(0))?
- .collect::<rusqlite::Result<Vec<String>>>()?;
- assert_eq!(tables.len(), 6);
- assert_eq!(tables[0], "blobentry");
- assert_eq!(tables[1], "blobmetadata");
- assert_eq!(tables[2], "grant");
- assert_eq!(tables[3], "keyentry");
- assert_eq!(tables[4], "keymetadata");
- assert_eq!(tables[5], "keyparameter");
- Ok(())
- }
-
- #[test]
- fn test_auth_token_table_invariant() -> Result<()> {
- let mut db = new_test_db()?;
- let auth_token1 = HardwareAuthToken {
- challenge: i64::MAX,
- userId: 200,
- authenticatorId: 200,
- authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
- timestamp: Timestamp { milliSeconds: 500 },
- mac: String::from("mac").into_bytes(),
- };
- db.insert_auth_token(&auth_token1);
- let auth_tokens_returned = get_auth_tokens(&db);
- assert_eq!(auth_tokens_returned.len(), 1);
-
- // insert another auth token with the same values for the columns in the UNIQUE constraint
- // of the auth token table and different value for timestamp
- let auth_token2 = HardwareAuthToken {
- challenge: i64::MAX,
- userId: 200,
- authenticatorId: 200,
- authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
- timestamp: Timestamp { milliSeconds: 600 },
- mac: String::from("mac").into_bytes(),
- };
-
- db.insert_auth_token(&auth_token2);
- let mut auth_tokens_returned = get_auth_tokens(&db);
- assert_eq!(auth_tokens_returned.len(), 1);
-
- if let Some(auth_token) = auth_tokens_returned.pop() {
- assert_eq!(auth_token.auth_token.timestamp.milliSeconds, 600);
- }
-
- // insert another auth token with the different values for the columns in the UNIQUE
- // constraint of the auth token table
- let auth_token3 = HardwareAuthToken {
- challenge: i64::MAX,
- userId: 201,
- authenticatorId: 200,
- authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
- timestamp: Timestamp { milliSeconds: 600 },
- mac: String::from("mac").into_bytes(),
- };
-
- db.insert_auth_token(&auth_token3);
- let auth_tokens_returned = get_auth_tokens(&db);
- assert_eq!(auth_tokens_returned.len(), 2);
-
- Ok(())
- }
-
- // utility function for test_auth_token_table_invariant()
- fn get_auth_tokens(db: &KeystoreDB) -> Vec<AuthTokenEntry> {
- db.perboot.get_all_auth_token_entries()
- }
-
- fn create_key_entry(
- db: &mut KeystoreDB,
- domain: &Domain,
- namespace: &i64,
- key_type: KeyType,
- km_uuid: &Uuid,
- ) -> Result<KeyIdGuard> {
- db.with_transaction(Immediate("TX_create_key_entry"), |tx| {
- KeystoreDB::create_key_entry_internal(tx, domain, namespace, key_type, km_uuid).no_gc()
- })
- }
-
- #[test]
- fn test_persistence_for_files() -> Result<()> {
- let temp_dir = TempDir::new("persistent_db_test")?;
- let mut db = KeystoreDB::new(temp_dir.path(), None)?;
-
- create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?;
- let entries = get_keyentry(&db)?;
- assert_eq!(entries.len(), 1);
-
- let db = KeystoreDB::new(temp_dir.path(), None)?;
-
- let entries_new = get_keyentry(&db)?;
- assert_eq!(entries, entries_new);
- Ok(())
- }
-
- #[test]
- fn test_create_key_entry() -> Result<()> {
- fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>, Uuid) {
- (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref(), ke.km_uuid.unwrap())
- }
-
- let mut db = new_test_db()?;
-
- create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?;
- create_key_entry(&mut db, &Domain::SELINUX, &101, KeyType::Client, &KEYSTORE_UUID)?;
-
- let entries = get_keyentry(&db)?;
- assert_eq!(entries.len(), 2);
- assert_eq!(extractor(&entries[0]), (Domain::APP, 100, None, KEYSTORE_UUID));
- assert_eq!(extractor(&entries[1]), (Domain::SELINUX, 101, None, KEYSTORE_UUID));
-
- // Test that we must pass in a valid Domain.
- check_result_is_error_containing_string(
- create_key_entry(&mut db, &Domain::GRANT, &102, KeyType::Client, &KEYSTORE_UUID),
- &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
- );
- check_result_is_error_containing_string(
- create_key_entry(&mut db, &Domain::BLOB, &103, KeyType::Client, &KEYSTORE_UUID),
- &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
- );
- check_result_is_error_containing_string(
- create_key_entry(&mut db, &Domain::KEY_ID, &104, KeyType::Client, &KEYSTORE_UUID),
- &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
- );
-
- Ok(())
- }
-
- #[test]
- fn test_rebind_alias() -> Result<()> {
- fn extractor(
- ke: &KeyEntryRow,
- ) -> (Option<Domain>, Option<i64>, Option<&str>, Option<Uuid>) {
- (ke.domain, ke.namespace, ke.alias.as_deref(), ke.km_uuid)
- }
-
- let mut db = new_test_db()?;
- create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
- create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
- let entries = get_keyentry(&db)?;
- assert_eq!(entries.len(), 2);
- assert_eq!(
- extractor(&entries[0]),
- (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))
- );
- assert_eq!(
- extractor(&entries[1]),
- (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))
- );
-
- // Test that the first call to rebind_alias sets the alias.
- rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[0].id), "foo", Domain::APP, 42)?;
- let entries = get_keyentry(&db)?;
- assert_eq!(entries.len(), 2);
- assert_eq!(
- extractor(&entries[0]),
- (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
- );
- assert_eq!(
- extractor(&entries[1]),
- (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))
- );
-
- // Test that the second call to rebind_alias also empties the old one.
- rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[1].id), "foo", Domain::APP, 42)?;
- let entries = get_keyentry(&db)?;
- assert_eq!(entries.len(), 2);
- assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID)));
- assert_eq!(
- extractor(&entries[1]),
- (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
- );
-
- // Test that we must pass in a valid Domain.
- check_result_is_error_containing_string(
- rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::GRANT, 42),
- &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
- );
- check_result_is_error_containing_string(
- rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::BLOB, 42),
- &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
- );
- check_result_is_error_containing_string(
- rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::KEY_ID, 42),
- &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
- );
-
- // Test that we correctly handle setting an alias for something that does not exist.
- check_result_is_error_containing_string(
- rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::SELINUX, 42),
- "Expected to update a single entry but instead updated 0",
- );
- // Test that we correctly abort the transaction in this case.
- let entries = get_keyentry(&db)?;
- assert_eq!(entries.len(), 2);
- assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID)));
- assert_eq!(
- extractor(&entries[1]),
- (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
- );
-
- Ok(())
- }
-
- #[test]
- fn test_grant_ungrant() -> Result<()> {
- const CALLER_UID: u32 = 15;
- const GRANTEE_UID: u32 = 12;
- const SELINUX_NAMESPACE: i64 = 7;
-
- let mut db = new_test_db()?;
- db.conn.execute(
- "INSERT INTO persistent.keyentry (id, key_type, domain, namespace, alias, state, km_uuid)
- VALUES (1, 0, 0, 15, 'key', 1, ?), (2, 0, 2, 7, 'yek', 1, ?);",
- params![KEYSTORE_UUID, KEYSTORE_UUID],
- )?;
- let app_key = KeyDescriptor {
- domain: super::Domain::APP,
- nspace: 0,
- alias: Some("key".to_string()),
- blob: None,
- };
- const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo];
- const PVEC2: KeyPermSet = key_perm_set![KeyPerm::Use];
-
- // Reset totally predictable random number generator in case we
- // are not the first test running on this thread.
- reset_random();
- let next_random = 0i64;
-
- let app_granted_key = db
- .grant(&app_key, CALLER_UID, GRANTEE_UID, PVEC1, |k, a| {
- assert_eq!(*a, PVEC1);
- assert_eq!(
- *k,
- KeyDescriptor {
- domain: super::Domain::APP,
- // namespace must be set to the caller_uid.
- nspace: CALLER_UID as i64,
- alias: Some("key".to_string()),
- blob: None,
- }
- );
- Ok(())
- })
- .unwrap();
-
- assert_eq!(
- app_granted_key,
- KeyDescriptor {
- domain: super::Domain::GRANT,
- // The grantid is next_random due to the mock random number generator.
- nspace: next_random,
- alias: None,
- blob: None,
- }
- );
-
- let selinux_key = KeyDescriptor {
- domain: super::Domain::SELINUX,
- nspace: SELINUX_NAMESPACE,
- alias: Some("yek".to_string()),
- blob: None,
- };
-
- let selinux_granted_key = db
- .grant(&selinux_key, CALLER_UID, 12, PVEC1, |k, a| {
- assert_eq!(*a, PVEC1);
- assert_eq!(
- *k,
- KeyDescriptor {
- domain: super::Domain::SELINUX,
- // namespace must be the supplied SELinux
- // namespace.
- nspace: SELINUX_NAMESPACE,
- alias: Some("yek".to_string()),
- blob: None,
- }
- );
- Ok(())
- })
- .unwrap();
-
- assert_eq!(
- selinux_granted_key,
- KeyDescriptor {
- domain: super::Domain::GRANT,
- // The grantid is next_random + 1 due to the mock random number generator.
- nspace: next_random + 1,
- alias: None,
- blob: None,
- }
- );
-
- // This should update the existing grant with PVEC2.
- let selinux_granted_key = db
- .grant(&selinux_key, CALLER_UID, 12, PVEC2, |k, a| {
- assert_eq!(*a, PVEC2);
- assert_eq!(
- *k,
- KeyDescriptor {
- domain: super::Domain::SELINUX,
- // namespace must be the supplied SELinux
- // namespace.
- nspace: SELINUX_NAMESPACE,
- alias: Some("yek".to_string()),
- blob: None,
- }
- );
- Ok(())
- })
- .unwrap();
-
- assert_eq!(
- selinux_granted_key,
- KeyDescriptor {
- domain: super::Domain::GRANT,
- // Same grant id as before. The entry was only updated.
- nspace: next_random + 1,
- alias: None,
- blob: None,
- }
- );
-
- {
- // Limiting scope of stmt, because it borrows db.
- let mut stmt = db
- .conn
- .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
- let mut rows = stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>([], |row| {
- Ok((row.get(0)?, row.get(1)?, row.get(2)?, KeyPermSet::from(row.get::<_, i32>(3)?)))
- })?;
-
- let r = rows.next().unwrap().unwrap();
- assert_eq!(r, (next_random, GRANTEE_UID, 1, PVEC1));
- let r = rows.next().unwrap().unwrap();
- assert_eq!(r, (next_random + 1, GRANTEE_UID, 2, PVEC2));
- assert!(rows.next().is_none());
- }
-
- debug_dump_keyentry_table(&mut db)?;
- println!("app_key {:?}", app_key);
- println!("selinux_key {:?}", selinux_key);
-
- db.ungrant(&app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
- db.ungrant(&selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
-
- Ok(())
- }
-
- static TEST_KEY_BLOB: &[u8] = b"my test blob";
- static TEST_CERT_BLOB: &[u8] = b"my test cert";
- static TEST_CERT_CHAIN_BLOB: &[u8] = b"my test cert_chain";
-
- #[test]
- fn test_set_blob() -> Result<()> {
- let key_id = KEY_ID_LOCK.get(3000);
- let mut db = new_test_db()?;
- let mut blob_metadata = BlobMetaData::new();
- blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
- db.set_blob(
- &key_id,
- SubComponentType::KEY_BLOB,
- Some(TEST_KEY_BLOB),
- Some(&blob_metadata),
- )?;
- db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
- db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
- drop(key_id);
-
- let mut stmt = db.conn.prepare(
- "SELECT subcomponent_type, keyentryid, blob, id FROM persistent.blobentry
- ORDER BY subcomponent_type ASC;",
- )?;
- let mut rows = stmt
- .query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>([], |row| {
- Ok(((row.get(0)?, row.get(1)?, row.get(2)?), row.get(3)?))
- })?;
- let (r, id) = rows.next().unwrap().unwrap();
- assert_eq!(r, (SubComponentType::KEY_BLOB, 3000, TEST_KEY_BLOB.to_vec()));
- let (r, _) = rows.next().unwrap().unwrap();
- assert_eq!(r, (SubComponentType::CERT, 3000, TEST_CERT_BLOB.to_vec()));
- let (r, _) = rows.next().unwrap().unwrap();
- assert_eq!(r, (SubComponentType::CERT_CHAIN, 3000, TEST_CERT_CHAIN_BLOB.to_vec()));
-
- drop(rows);
- drop(stmt);
-
- assert_eq!(
- db.with_transaction(Immediate("TX_test"), |tx| {
- BlobMetaData::load_from_db(id, tx).no_gc()
- })
- .expect("Should find blob metadata."),
- blob_metadata
- );
- Ok(())
- }
-
- static TEST_ALIAS: &str = "my super duper key";
-
- #[test]
- fn test_insert_and_load_full_keyentry_domain_app() -> Result<()> {
- let mut db = new_test_db()?;
- let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_domain_app")?
- .0;
- let (_key_guard, key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- 1,
- |_k, _av| Ok(()),
- )
- .unwrap();
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
- db.unbind_key(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- 1,
- |_, _| Ok(()),
- )
- .unwrap();
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- 1,
- |_k, _av| Ok(()),
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- #[test]
- fn test_insert_and_load_certificate_entry_domain_app() -> Result<()> {
- let mut db = new_test_db()?;
-
- db.store_new_certificate(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 1,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- TEST_CERT_BLOB,
- &KEYSTORE_UUID,
- )
- .expect("Trying to insert cert.");
-
- let (_key_guard, mut key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 1,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::PUBLIC,
- 1,
- |_k, _av| Ok(()),
- )
- .expect("Trying to read certificate entry.");
-
- assert!(key_entry.pure_cert());
- assert!(key_entry.cert().is_none());
- assert_eq!(key_entry.take_cert_chain(), Some(TEST_CERT_BLOB.to_vec()));
-
- db.unbind_key(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 1,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- 1,
- |_, _| Ok(()),
- )
- .unwrap();
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 1,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- 1,
- |_k, _av| Ok(()),
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- #[test]
- fn test_insert_and_load_full_keyentry_domain_selinux() -> Result<()> {
- let mut db = new_test_db()?;
- let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_domain_selinux")?
- .0;
- let (_key_guard, key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::SELINUX,
- nspace: 1,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- 1,
- |_k, _av| Ok(()),
- )
- .unwrap();
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
- db.unbind_key(
- &KeyDescriptor {
- domain: Domain::SELINUX,
- nspace: 1,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- 1,
- |_, _| Ok(()),
- )
- .unwrap();
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &KeyDescriptor {
- domain: Domain::SELINUX,
- nspace: 1,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- 1,
- |_k, _av| Ok(()),
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- #[test]
- fn test_insert_and_load_full_keyentry_domain_key_id() -> Result<()> {
- let mut db = new_test_db()?;
- let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_domain_key_id")?
- .0;
- let (_, key_entry) = db
- .load_key_entry(
- &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- 1,
- |_k, _av| Ok(()),
- )
- .unwrap();
-
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
- db.unbind_key(
- &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
- KeyType::Client,
- 1,
- |_, _| Ok(()),
- )
- .unwrap();
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- 1,
- |_k, _av| Ok(()),
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- #[test]
- fn test_check_and_update_key_usage_count_with_limited_use_key() -> Result<()> {
- let mut db = new_test_db()?;
- let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(123))
- .context("test_check_and_update_key_usage_count_with_limited_use_key")?
- .0;
- // Update the usage count of the limited use key.
- db.check_and_update_key_usage_count(key_id)?;
-
- let (_key_guard, key_entry) = db.load_key_entry(
- &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- 1,
- |_k, _av| Ok(()),
- )?;
-
- // The usage count is decremented now.
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, Some(122)));
-
- Ok(())
- }
-
- #[test]
- fn test_check_and_update_key_usage_count_with_exhausted_limited_use_key() -> Result<()> {
- let mut db = new_test_db()?;
- let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(1))
- .context("test_check_and_update_key_usage_count_with_exhausted_limited_use_key")?
- .0;
- // Update the usage count of the limited use key.
- db.check_and_update_key_usage_count(key_id).expect(concat!(
- "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ",
- "This should succeed."
- ));
-
- // Try to update the exhausted limited use key.
- let e = db.check_and_update_key_usage_count(key_id).expect_err(concat!(
- "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ",
- "This should fail."
- ));
- assert_eq!(
- &KsError::Km(ErrorCode::INVALID_KEY_BLOB),
- e.root_cause().downcast_ref::<KsError>().unwrap()
- );
-
- Ok(())
- }
-
- #[test]
- fn test_insert_and_load_full_keyentry_from_grant() -> Result<()> {
- let mut db = new_test_db()?;
- let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_from_grant")?
- .0;
-
- let granted_key = db
- .grant(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- 1,
- 2,
- key_perm_set![KeyPerm::Use],
- |_k, _av| Ok(()),
- )
- .unwrap();
-
- debug_dump_grant_table(&mut db)?;
-
- let (_key_guard, key_entry) = db
- .load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| {
- assert_eq!(Domain::GRANT, k.domain);
- assert!(av.unwrap().includes(KeyPerm::Use));
- Ok(())
- })
- .unwrap();
-
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
- db.unbind_key(&granted_key, KeyType::Client, 2, |_, _| Ok(())).unwrap();
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &granted_key,
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- 2,
- |_k, _av| Ok(()),
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- // This test attempts to load a key by key id while the caller is not the owner
- // but a grant exists for the given key and the caller.
- #[test]
- fn test_insert_and_load_full_keyentry_from_grant_by_key_id() -> Result<()> {
- let mut db = new_test_db()?;
- const OWNER_UID: u32 = 1u32;
- const GRANTEE_UID: u32 = 2u32;
- const SOMEONE_ELSE_UID: u32 = 3u32;
- let key_id = make_test_key_entry(&mut db, Domain::APP, OWNER_UID as i64, TEST_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?
- .0;
-
- db.grant(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- OWNER_UID,
- GRANTEE_UID,
- key_perm_set![KeyPerm::Use],
- |_k, _av| Ok(()),
- )
- .unwrap();
-
- debug_dump_grant_table(&mut db)?;
-
- let id_descriptor =
- KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, ..Default::default() };
-
- let (_, key_entry) = db
- .load_key_entry(
- &id_descriptor,
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- GRANTEE_UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(OWNER_UID as i64, k.nspace);
- assert!(av.unwrap().includes(KeyPerm::Use));
- Ok(())
- },
- )
- .unwrap();
-
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
- let (_, key_entry) = db
- .load_key_entry(
- &id_descriptor,
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- SOMEONE_ELSE_UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(OWNER_UID as i64, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap();
-
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
- db.unbind_key(&id_descriptor, KeyType::Client, OWNER_UID, |_, _| Ok(())).unwrap();
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &id_descriptor,
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- GRANTEE_UID,
- |_k, _av| Ok(()),
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- // Creates a key migrates it to a different location and then tries to access it by the old
- // and new location.
- #[test]
- fn test_migrate_key_app_to_app() -> Result<()> {
- let mut db = new_test_db()?;
- const SOURCE_UID: u32 = 1u32;
- const DESTINATION_UID: u32 = 2u32;
- static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
- static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
- let key_id_guard =
- make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
-
- let source_descriptor: KeyDescriptor = KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(SOURCE_ALIAS.to_string()),
- blob: None,
- };
-
- let destination_descriptor: KeyDescriptor = KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(DESTINATION_ALIAS.to_string()),
- blob: None,
- };
-
- let key_id = key_id_guard.id();
-
- db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| {
- Ok(())
- })
- .unwrap();
-
- let (_, key_entry) = db
- .load_key_entry(
- &destination_descriptor,
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- DESTINATION_UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(DESTINATION_UID as i64, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap();
-
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &source_descriptor,
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- SOURCE_UID,
- |_k, _av| Ok(()),
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- // Creates a key migrates it to a different location and then tries to access it by the old
- // and new location.
- #[test]
- fn test_migrate_key_app_to_selinux() -> Result<()> {
- let mut db = new_test_db()?;
- const SOURCE_UID: u32 = 1u32;
- const DESTINATION_UID: u32 = 2u32;
- const DESTINATION_NAMESPACE: i64 = 1000i64;
- static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
- static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
- let key_id_guard =
- make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
-
- let source_descriptor: KeyDescriptor = KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(SOURCE_ALIAS.to_string()),
- blob: None,
- };
-
- let destination_descriptor: KeyDescriptor = KeyDescriptor {
- domain: Domain::SELINUX,
- nspace: DESTINATION_NAMESPACE,
- alias: Some(DESTINATION_ALIAS.to_string()),
- blob: None,
- };
-
- let key_id = key_id_guard.id();
-
- db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| {
- Ok(())
- })
- .unwrap();
-
- let (_, key_entry) = db
- .load_key_entry(
- &destination_descriptor,
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- DESTINATION_UID,
- |k, av| {
- assert_eq!(Domain::SELINUX, k.domain);
- assert_eq!(DESTINATION_NAMESPACE, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap();
-
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &source_descriptor,
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- SOURCE_UID,
- |_k, _av| Ok(()),
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- // Creates two keys and tries to migrate the first to the location of the second which
- // is expected to fail.
- #[test]
- fn test_migrate_key_destination_occupied() -> Result<()> {
- let mut db = new_test_db()?;
- const SOURCE_UID: u32 = 1u32;
- const DESTINATION_UID: u32 = 2u32;
- static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
- static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
- let key_id_guard =
- make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
- make_test_key_entry(&mut db, Domain::APP, DESTINATION_UID as i64, DESTINATION_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
-
- let destination_descriptor: KeyDescriptor = KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(DESTINATION_ALIAS.to_string()),
- blob: None,
- };
-
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::INVALID_ARGUMENT)),
- db.migrate_key_namespace(
- key_id_guard,
- &destination_descriptor,
- DESTINATION_UID,
- |_k| Ok(())
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
-
- Ok(())
- }
-
- #[test]
- fn test_upgrade_0_to_1() {
- const ALIAS1: &str = "test_upgrade_0_to_1_1";
- const ALIAS2: &str = "test_upgrade_0_to_1_2";
- const ALIAS3: &str = "test_upgrade_0_to_1_3";
- const UID: u32 = 33;
- let temp_dir = Arc::new(TempDir::new("test_upgrade_0_to_1").unwrap());
- let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
- let key_id_untouched1 =
- make_test_key_entry(&mut db, Domain::APP, UID as i64, ALIAS1, None).unwrap().id();
- let key_id_untouched2 =
- make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS2, false).unwrap().id();
- let key_id_deleted =
- make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS3, true).unwrap().id();
-
- let (_, key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(ALIAS1.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(UID as i64, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap();
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None));
- let (_, key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(ALIAS2.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(UID as i64, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap();
- assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false));
- let (_, key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(ALIAS3.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(UID as i64, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap();
- assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_deleted, true));
-
- db.with_transaction(Immediate("TX_test"), |tx| KeystoreDB::from_0_to_1(tx).no_gc())
- .unwrap();
-
- let (_, key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(ALIAS1.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(UID as i64, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap();
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None));
- let (_, key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(ALIAS2.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(UID as i64, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap();
- assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false));
- assert_eq!(
- Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
- db.load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(ALIAS3.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- UID,
- |k, av| {
- assert_eq!(Domain::APP, k.domain);
- assert_eq!(UID as i64, k.nspace);
- assert!(av.is_none());
- Ok(())
- },
- )
- .unwrap_err()
- .root_cause()
- .downcast_ref::<KsError>()
- );
- }
-
- static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key";
-
- #[test]
- fn test_insert_and_load_full_keyentry_domain_app_concurrently() -> Result<()> {
- let handle = {
- let temp_dir = Arc::new(TempDir::new("id_lock_test")?);
- let temp_dir_clone = temp_dir.clone();
- let mut db = KeystoreDB::new(temp_dir.path(), None)?;
- let key_id = make_test_key_entry(&mut db, Domain::APP, 33, KEY_LOCK_TEST_ALIAS, None)
- .context("test_insert_and_load_full_keyentry_domain_app")?
- .0;
- let (_key_guard, key_entry) = db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- 33,
- |_k, _av| Ok(()),
- )
- .unwrap();
- assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
- let state = Arc::new(AtomicU8::new(1));
- let state2 = state.clone();
-
- // Spawning a second thread that attempts to acquire the key id lock
- // for the same key as the primary thread. The primary thread then
- // waits, thereby forcing the secondary thread into the second stage
- // of acquiring the lock (see KEY ID LOCK 2/2 above).
- // The test succeeds if the secondary thread observes the transition
- // of `state` from 1 to 2, despite having a whole second to overtake
- // the primary thread.
- let handle = thread::spawn(move || {
- let temp_dir = temp_dir_clone;
- let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
- assert!(db
- .load_key_entry(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
- blob: None,
- },
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- 33,
- |_k, _av| Ok(()),
- )
- .is_ok());
- // We should only see a 2 here because we can only return
- // from load_key_entry when the `_key_guard` expires,
- // which happens at the end of the scope.
- assert_eq!(2, state2.load(Ordering::Relaxed));
- });
-
- thread::sleep(std::time::Duration::from_millis(1000));
-
- assert_eq!(Ok(1), state.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed));
-
- // Return the handle from this scope so we can join with the
- // secondary thread after the key id lock has expired.
- handle
- // This is where the `_key_guard` goes out of scope,
- // which is the reason for concurrent load_key_entry on the same key
- // to unblock.
- };
- // Join with the secondary thread and unwrap, to propagate failing asserts to the
- // main test thread. We will not see failing asserts in secondary threads otherwise.
- handle.join().unwrap();
- Ok(())
- }
-
- #[test]
- fn test_database_busy_error_code() {
- let temp_dir =
- TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir.");
-
- let mut db1 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database1.");
- let mut db2 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database2.");
-
- let _tx1 = db1
- .conn
- .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
- .expect("Failed to create first transaction.");
-
- let error = db2
- .conn
- .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
- .context("Transaction begin failed.")
- .expect_err("This should fail.");
- let root_cause = error.root_cause();
- if let Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. }) =
- root_cause.downcast_ref::<rusqlite::ffi::Error>()
- {
- return;
- }
- panic!(
- "Unexpected error {:?} \n{:?} \n{:?}",
- error,
- root_cause,
- root_cause.downcast_ref::<rusqlite::ffi::Error>()
- )
- }
-
- #[cfg(disabled)]
- #[test]
- fn test_large_number_of_concurrent_db_manipulations() -> Result<()> {
- let temp_dir = Arc::new(
- TempDir::new("test_large_number_of_concurrent_db_manipulations_")
- .expect("Failed to create temp dir."),
- );
-
- let test_begin = Instant::now();
-
- const KEY_COUNT: u32 = 500u32;
- let mut db =
- new_test_db_with_gc(temp_dir.path(), |_, _| Ok(())).expect("Failed to open database.");
- const OPEN_DB_COUNT: u32 = 50u32;
-
- let mut actual_key_count = KEY_COUNT;
- // First insert KEY_COUNT keys.
- for count in 0..KEY_COUNT {
- if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) {
- actual_key_count = count;
- break;
- }
- let alias = format!("test_alias_{}", count);
- make_test_key_entry(&mut db, Domain::APP, 1, &alias, None)
- .expect("Failed to make key entry.");
- }
-
- // Insert more keys from a different thread and into a different namespace.
- let temp_dir1 = temp_dir.clone();
- let handle1 = thread::spawn(move || {
- let mut db = new_test_db_with_gc(temp_dir1.path(), |_, _| Ok(()))
- .expect("Failed to open database.");
-
- for count in 0..actual_key_count {
- if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
- return;
- }
- let alias = format!("test_alias_{}", count);
- make_test_key_entry(&mut db, Domain::APP, 2, &alias, None)
- .expect("Failed to make key entry.");
- }
-
- // then unbind them again.
- for count in 0..actual_key_count {
- if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
- return;
- }
- let key = KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(format!("test_alias_{}", count)),
- blob: None,
- };
- db.unbind_key(&key, KeyType::Client, 2, |_, _| Ok(())).expect("Unbind Failed.");
- }
- });
-
- // And start unbinding the first set of keys.
- let temp_dir2 = temp_dir.clone();
- let handle2 = thread::spawn(move || {
- let mut db = new_test_db_with_gc(temp_dir2.path(), |_, _| Ok(()))
- .expect("Failed to open database.");
-
- for count in 0..actual_key_count {
- if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
- return;
- }
- let key = KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(format!("test_alias_{}", count)),
- blob: None,
- };
- db.unbind_key(&key, KeyType::Client, 1, |_, _| Ok(())).expect("Unbind Failed.");
- }
- });
-
- // While a lot of inserting and deleting is going on we have to open database connections
- // successfully and use them.
- // This clone is not redundant, because temp_dir needs to be kept alive until db goes
- // out of scope.
- #[allow(clippy::redundant_clone)]
- let temp_dir4 = temp_dir.clone();
- let handle4 = thread::spawn(move || {
- for count in 0..OPEN_DB_COUNT {
- if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
- return;
- }
- let mut db = new_test_db_with_gc(temp_dir4.path(), |_, _| Ok(()))
- .expect("Failed to open database.");
-
- let alias = format!("test_alias_{}", count);
- make_test_key_entry(&mut db, Domain::APP, 3, &alias, None)
- .expect("Failed to make key entry.");
- let key = KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(alias),
- blob: None,
- };
- db.unbind_key(&key, KeyType::Client, 3, |_, _| Ok(())).expect("Unbind Failed.");
- }
- });
-
- handle1.join().expect("Thread 1 panicked.");
- handle2.join().expect("Thread 2 panicked.");
- handle4.join().expect("Thread 4 panicked.");
-
- Ok(())
- }
-
- #[test]
- fn list() -> Result<()> {
- let temp_dir = TempDir::new("list_test")?;
- let mut db = KeystoreDB::new(temp_dir.path(), None)?;
- static LIST_O_ENTRIES: &[(Domain, i64, &str)] = &[
- (Domain::APP, 1, "test1"),
- (Domain::APP, 1, "test2"),
- (Domain::APP, 1, "test3"),
- (Domain::APP, 1, "test4"),
- (Domain::APP, 1, "test5"),
- (Domain::APP, 1, "test6"),
- (Domain::APP, 1, "test7"),
- (Domain::APP, 2, "test1"),
- (Domain::APP, 2, "test2"),
- (Domain::APP, 2, "test3"),
- (Domain::APP, 2, "test4"),
- (Domain::APP, 2, "test5"),
- (Domain::APP, 2, "test6"),
- (Domain::APP, 2, "test8"),
- (Domain::SELINUX, 100, "test1"),
- (Domain::SELINUX, 100, "test2"),
- (Domain::SELINUX, 100, "test3"),
- (Domain::SELINUX, 100, "test4"),
- (Domain::SELINUX, 100, "test5"),
- (Domain::SELINUX, 100, "test6"),
- (Domain::SELINUX, 100, "test9"),
- ];
-
- let list_o_keys: Vec<(i64, i64)> = LIST_O_ENTRIES
- .iter()
- .map(|(domain, ns, alias)| {
- let entry =
- make_test_key_entry(&mut db, *domain, *ns, alias, None).unwrap_or_else(|e| {
- panic!("Failed to insert {:?} {} {}. Error {:?}", domain, ns, alias, e)
- });
- (entry.id(), *ns)
- })
- .collect();
-
- for (domain, namespace) in
- &[(Domain::APP, 1i64), (Domain::APP, 2i64), (Domain::SELINUX, 100i64)]
- {
- let mut list_o_descriptors: Vec<KeyDescriptor> = LIST_O_ENTRIES
- .iter()
- .filter_map(|(domain, ns, alias)| match ns {
- ns if *ns == *namespace => Some(KeyDescriptor {
- domain: *domain,
- nspace: *ns,
- alias: Some(alias.to_string()),
- blob: None,
- }),
- _ => None,
- })
- .collect();
- list_o_descriptors.sort();
- let mut list_result = db.list_past_alias(*domain, *namespace, KeyType::Client, None)?;
- list_result.sort();
- assert_eq!(list_o_descriptors, list_result);
-
- let mut list_o_ids: Vec<i64> = list_o_descriptors
- .into_iter()
- .map(|d| {
- let (_, entry) = db
- .load_key_entry(
- &d,
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- *namespace as u32,
- |_, _| Ok(()),
- )
- .unwrap();
- entry.id()
- })
- .collect();
- list_o_ids.sort_unstable();
- let mut loaded_entries: Vec<i64> = list_o_keys
- .iter()
- .filter_map(|(id, ns)| match ns {
- ns if *ns == *namespace => Some(*id),
- _ => None,
- })
- .collect();
- loaded_entries.sort_unstable();
- assert_eq!(list_o_ids, loaded_entries);
- }
- assert_eq!(
- Vec::<KeyDescriptor>::new(),
- db.list_past_alias(Domain::SELINUX, 101, KeyType::Client, None)?
- );
-
- Ok(())
- }
-
- // Helpers
-
- // Checks that the given result is an error containing the given string.
- fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) {
- let error_str = format!(
- "{:#?}",
- result.err().unwrap_or_else(|| panic!("Expected the error: {}", target))
- );
- assert!(
- error_str.contains(target),
- "The string \"{}\" should contain \"{}\"",
- error_str,
- target
- );
- }
-
- #[derive(Debug, PartialEq)]
- struct KeyEntryRow {
- id: i64,
- key_type: KeyType,
- domain: Option<Domain>,
- namespace: Option<i64>,
- alias: Option<String>,
- state: KeyLifeCycle,
- km_uuid: Option<Uuid>,
- }
-
- fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> {
- db.conn
- .prepare("SELECT * FROM persistent.keyentry;")?
- .query_map([], |row| {
- Ok(KeyEntryRow {
- id: row.get(0)?,
- key_type: row.get(1)?,
- domain: row.get::<_, Option<_>>(2)?.map(Domain),
- namespace: row.get(3)?,
- alias: row.get(4)?,
- state: row.get(5)?,
- km_uuid: row.get(6)?,
- })
- })?
- .map(|r| r.context("Could not read keyentry row."))
- .collect::<Result<Vec<_>>>()
- }
-
- fn make_test_params(max_usage_count: Option<i32>) -> Vec<KeyParameter> {
- make_test_params_with_sids(max_usage_count, &[42])
- }
-
- // Note: The parameters and SecurityLevel associations are nonsensical. This
- // collection is only used to check if the parameters are preserved as expected by the
- // database.
- fn make_test_params_with_sids(
- max_usage_count: Option<i32>,
- user_secure_ids: &[i64],
- ) -> Vec<KeyParameter> {
- let mut params = vec![
- KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::TRUSTED_ENVIRONMENT),
- KeyParameter::new(
- KeyParameterValue::KeyPurpose(KeyPurpose::SIGN),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::Algorithm(Algorithm::RSA),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::TRUSTED_ENVIRONMENT),
- KeyParameter::new(
- KeyParameterValue::BlockMode(BlockMode::ECB),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::BlockMode(BlockMode::GCM),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(KeyParameterValue::Digest(Digest::NONE), SecurityLevel::STRONGBOX),
- KeyParameter::new(
- KeyParameterValue::Digest(Digest::MD5),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::Digest(Digest::SHA_2_224),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::Digest(Digest::SHA_2_256),
- SecurityLevel::STRONGBOX,
- ),
- KeyParameter::new(
- KeyParameterValue::PaddingMode(PaddingMode::NONE),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::PaddingMode(PaddingMode::RSA_OAEP),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS),
- SecurityLevel::STRONGBOX,
- ),
- KeyParameter::new(
- KeyParameterValue::PaddingMode(PaddingMode::RSA_PKCS1_1_5_SIGN),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::TRUSTED_ENVIRONMENT),
- KeyParameter::new(KeyParameterValue::MinMacLength(256), SecurityLevel::STRONGBOX),
- KeyParameter::new(
- KeyParameterValue::EcCurve(EcCurve::P_224),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::STRONGBOX),
- KeyParameter::new(
- KeyParameterValue::EcCurve(EcCurve::P_384),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::EcCurve(EcCurve::P_521),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::RSAPublicExponent(3),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::IncludeUniqueID,
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(KeyParameterValue::BootLoaderOnly, SecurityLevel::STRONGBOX),
- KeyParameter::new(KeyParameterValue::RollbackResistance, SecurityLevel::STRONGBOX),
- KeyParameter::new(
- KeyParameterValue::ActiveDateTime(1234567890),
- SecurityLevel::STRONGBOX,
- ),
- KeyParameter::new(
- KeyParameterValue::OriginationExpireDateTime(1234567890),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::UsageExpireDateTime(1234567890),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::MinSecondsBetweenOps(1234567890),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::MaxUsesPerBoot(1234567890),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(KeyParameterValue::UserID(1), SecurityLevel::STRONGBOX),
- KeyParameter::new(
- KeyParameterValue::NoAuthRequired,
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::PASSWORD),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(KeyParameterValue::AuthTimeout(1234567890), SecurityLevel::SOFTWARE),
- KeyParameter::new(KeyParameterValue::AllowWhileOnBody, SecurityLevel::SOFTWARE),
- KeyParameter::new(
- KeyParameterValue::TrustedUserPresenceRequired,
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::TrustedConfirmationRequired,
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::UnlockedDeviceRequired,
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::ApplicationID(vec![1u8, 2u8, 3u8, 4u8]),
- SecurityLevel::SOFTWARE,
- ),
- KeyParameter::new(
- KeyParameterValue::ApplicationData(vec![4u8, 3u8, 2u8, 1u8]),
- SecurityLevel::SOFTWARE,
- ),
- KeyParameter::new(
- KeyParameterValue::CreationDateTime(12345677890),
- SecurityLevel::SOFTWARE,
- ),
- KeyParameter::new(
- KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::RootOfTrust(vec![3u8, 2u8, 1u8, 4u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(KeyParameterValue::OSVersion(1), SecurityLevel::TRUSTED_ENVIRONMENT),
- KeyParameter::new(KeyParameterValue::OSPatchLevel(2), SecurityLevel::SOFTWARE),
- KeyParameter::new(
- KeyParameterValue::UniqueID(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::SOFTWARE,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationChallenge(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationApplicationID(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdBrand(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdDevice(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdProduct(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdSerial(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdIMEI(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdSecondIMEI(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdMEID(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdManufacturer(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AttestationIdModel(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::VendorPatchLevel(3),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::BootPatchLevel(4),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::AssociatedData(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::Nonce(vec![4u8, 3u8, 1u8, 2u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::MacLength(256),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::ResetSinceIdRotation,
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- KeyParameter::new(
- KeyParameterValue::ConfirmationToken(vec![5u8, 5u8, 5u8, 5u8]),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ),
- ];
- if let Some(value) = max_usage_count {
- params.push(KeyParameter::new(
- KeyParameterValue::UsageCountLimit(value),
- SecurityLevel::SOFTWARE,
- ));
- }
-
- for sid in user_secure_ids.iter() {
- params.push(KeyParameter::new(
- KeyParameterValue::UserSecureID(*sid),
- SecurityLevel::STRONGBOX,
- ));
- }
- params
- }
-
- pub fn make_test_key_entry(
- db: &mut KeystoreDB,
- domain: Domain,
- namespace: i64,
- alias: &str,
- max_usage_count: Option<i32>,
- ) -> Result<KeyIdGuard> {
- make_test_key_entry_with_sids(db, domain, namespace, alias, max_usage_count, &[42])
- }
-
- pub fn make_test_key_entry_with_sids(
- db: &mut KeystoreDB,
- domain: Domain,
- namespace: i64,
- alias: &str,
- max_usage_count: Option<i32>,
- sids: &[i64],
- ) -> Result<KeyIdGuard> {
- let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
- let mut blob_metadata = BlobMetaData::new();
- blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
- blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
- blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
- blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
- blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-
- db.set_blob(
- &key_id,
- SubComponentType::KEY_BLOB,
- Some(TEST_KEY_BLOB),
- Some(&blob_metadata),
- )?;
- db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
- db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
-
- let params = make_test_params_with_sids(max_usage_count, sids);
- db.insert_keyparameter(&key_id, ¶ms)?;
-
- let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
- db.insert_key_metadata(&key_id, &metadata)?;
- rebind_alias(db, &key_id, alias, domain, namespace)?;
- Ok(key_id)
- }
-
- fn make_test_key_entry_test_vector(key_id: i64, max_usage_count: Option<i32>) -> KeyEntry {
- let params = make_test_params(max_usage_count);
-
- let mut blob_metadata = BlobMetaData::new();
- blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
- blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
- blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
- blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
- blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-
- let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
-
- KeyEntry {
- id: key_id,
- key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
- cert: Some(TEST_CERT_BLOB.to_vec()),
- cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
- km_uuid: KEYSTORE_UUID,
- parameters: params,
- metadata,
- pure_cert: false,
- }
- }
-
- pub fn make_bootlevel_key_entry(
- db: &mut KeystoreDB,
- domain: Domain,
- namespace: i64,
- alias: &str,
- logical_only: bool,
- ) -> Result<KeyIdGuard> {
- let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
- let mut blob_metadata = BlobMetaData::new();
- if !logical_only {
- blob_metadata.add(BlobMetaEntry::MaxBootLevel(3));
- }
- blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-
- db.set_blob(
- &key_id,
- SubComponentType::KEY_BLOB,
- Some(TEST_KEY_BLOB),
- Some(&blob_metadata),
- )?;
- db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
- db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
-
- let mut params = make_test_params(None);
- params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE));
-
- db.insert_keyparameter(&key_id, ¶ms)?;
-
- let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
- db.insert_key_metadata(&key_id, &metadata)?;
- rebind_alias(db, &key_id, alias, domain, namespace)?;
- Ok(key_id)
- }
-
- // Creates an app key that is marked as being superencrypted by the given
- // super key ID and that has the given authentication and unlocked device
- // parameters. This does not actually superencrypt the key blob.
- fn make_superencrypted_key_entry(
- db: &mut KeystoreDB,
- namespace: i64,
- alias: &str,
- requires_authentication: bool,
- requires_unlocked_device: bool,
- super_key_id: i64,
- ) -> Result<KeyIdGuard> {
- let domain = Domain::APP;
- let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
-
- let mut blob_metadata = BlobMetaData::new();
- blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
- blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
- db.set_blob(
- &key_id,
- SubComponentType::KEY_BLOB,
- Some(TEST_KEY_BLOB),
- Some(&blob_metadata),
- )?;
-
- let mut params = vec![];
- if requires_unlocked_device {
- params.push(KeyParameter::new(
- KeyParameterValue::UnlockedDeviceRequired,
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ));
- }
- if requires_authentication {
- params.push(KeyParameter::new(
- KeyParameterValue::UserSecureID(42),
- SecurityLevel::TRUSTED_ENVIRONMENT,
- ));
- }
- db.insert_keyparameter(&key_id, ¶ms)?;
-
- let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
- db.insert_key_metadata(&key_id, &metadata)?;
-
- rebind_alias(db, &key_id, alias, domain, namespace)?;
- Ok(key_id)
- }
-
- fn make_bootlevel_test_key_entry_test_vector(key_id: i64, logical_only: bool) -> KeyEntry {
- let mut params = make_test_params(None);
- params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE));
-
- let mut blob_metadata = BlobMetaData::new();
- if !logical_only {
- blob_metadata.add(BlobMetaEntry::MaxBootLevel(3));
- }
- blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-
- let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
-
- KeyEntry {
- id: key_id,
- key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
- cert: Some(TEST_CERT_BLOB.to_vec()),
- cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
- km_uuid: KEYSTORE_UUID,
- parameters: params,
- metadata,
- pure_cert: false,
- }
- }
-
- fn debug_dump_keyentry_table(db: &mut KeystoreDB) -> Result<()> {
- let mut stmt = db.conn.prepare(
- "SELECT id, key_type, domain, namespace, alias, state, km_uuid FROM persistent.keyentry;",
- )?;
- let rows = stmt.query_map::<(i64, KeyType, i32, i64, String, KeyLifeCycle, Uuid), _, _>(
- [],
- |row| {
- Ok((
- row.get(0)?,
- row.get(1)?,
- row.get(2)?,
- row.get(3)?,
- row.get(4)?,
- row.get(5)?,
- row.get(6)?,
- ))
- },
- )?;
-
- println!("Key entry table rows:");
- for r in rows {
- let (id, key_type, domain, namespace, alias, state, km_uuid) = r.unwrap();
- println!(
- " id: {} KeyType: {:?} Domain: {} Namespace: {} Alias: {} State: {:?} KmUuid: {:?}",
- id, key_type, domain, namespace, alias, state, km_uuid
- );
- }
- Ok(())
- }
-
- fn debug_dump_grant_table(db: &mut KeystoreDB) -> Result<()> {
- let mut stmt = db
- .conn
- .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
- let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>([], |row| {
- Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
- })?;
-
- println!("Grant table rows:");
- for r in rows {
- let (id, gt, ki, av) = r.unwrap();
- println!(" id: {} grantee: {} key_id: {} access_vector: {}", id, gt, ki, av);
- }
- Ok(())
- }
-
- // Use a custom random number generator that repeats each number once.
- // This allows us to test repeated elements.
-
- thread_local! {
- static RANDOM_COUNTER: RefCell<i64> = const { RefCell::new(0) };
- }
-
- fn reset_random() {
- RANDOM_COUNTER.with(|counter| {
- *counter.borrow_mut() = 0;
- })
- }
-
- pub fn random() -> i64 {
- RANDOM_COUNTER.with(|counter| {
- let result = *counter.borrow() / 2;
- *counter.borrow_mut() += 1;
- result
- })
- }
-
- #[test]
- fn test_unbind_keys_for_user() -> Result<()> {
- let mut db = new_test_db()?;
- db.unbind_keys_for_user(1)?;
-
- make_test_key_entry(&mut db, Domain::APP, 210000, TEST_ALIAS, None)?;
- make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?;
- db.unbind_keys_for_user(2)?;
-
- assert_eq!(1, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len());
- assert_eq!(0, db.list_past_alias(Domain::APP, 210000, KeyType::Client, None)?.len());
-
- db.unbind_keys_for_user(1)?;
- assert_eq!(0, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len());
-
- Ok(())
- }
-
- #[test]
- fn test_unbind_keys_for_user_removes_superkeys() -> Result<()> {
- let mut db = new_test_db()?;
- let super_key = keystore2_crypto::generate_aes256_key()?;
- let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
- let (encrypted_super_key, metadata) =
- SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
-
- let key_name_enc = SuperKeyType {
- alias: "test_super_key_1",
- algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
- name: "test_super_key_1",
- };
-
- let key_name_nonenc = SuperKeyType {
- alias: "test_super_key_2",
- algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
- name: "test_super_key_2",
- };
-
- // Install two super keys.
- db.store_super_key(
- 1,
- &key_name_nonenc,
- &super_key,
- &BlobMetaData::new(),
- &KeyMetaData::new(),
- )?;
- db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?;
-
- // Check that both can be found in the database.
- assert!(db.load_super_key(&key_name_enc, 1)?.is_some());
- assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some());
-
- // Install the same keys for a different user.
- db.store_super_key(
- 2,
- &key_name_nonenc,
- &super_key,
- &BlobMetaData::new(),
- &KeyMetaData::new(),
- )?;
- db.store_super_key(2, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?;
-
- // Check that the second pair of keys can be found in the database.
- assert!(db.load_super_key(&key_name_enc, 2)?.is_some());
- assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some());
-
- // Delete all keys for user 1.
- db.unbind_keys_for_user(1)?;
-
- // All of user 1's keys should be gone.
- assert!(db.load_super_key(&key_name_enc, 1)?.is_none());
- assert!(db.load_super_key(&key_name_nonenc, 1)?.is_none());
-
- // User 2's keys should not have been touched.
- assert!(db.load_super_key(&key_name_enc, 2)?.is_some());
- assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some());
-
- Ok(())
- }
-
- fn app_key_exists(db: &mut KeystoreDB, nspace: i64, alias: &str) -> Result<bool> {
- db.key_exists(Domain::APP, nspace, alias, KeyType::Client)
- }
-
- // Tests the unbind_auth_bound_keys_for_user() function.
- #[test]
- fn test_unbind_auth_bound_keys_for_user() -> Result<()> {
- let mut db = new_test_db()?;
- let user_id = 1;
- let nspace: i64 = (user_id * AID_USER_OFFSET).into();
- let other_user_id = 2;
- let other_user_nspace: i64 = (other_user_id * AID_USER_OFFSET).into();
- let super_key_type = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY;
-
- // Create a superencryption key.
- let super_key = keystore2_crypto::generate_aes256_key()?;
- let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
- let (encrypted_super_key, blob_metadata) =
- SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
- db.store_super_key(
- user_id,
- super_key_type,
- &encrypted_super_key,
- &blob_metadata,
- &KeyMetaData::new(),
- )?;
- let super_key_id = db.load_super_key(super_key_type, user_id)?.unwrap().0 .0;
-
- // Store 4 superencrypted app keys, one for each possible combination of
- // (authentication required, unlocked device required).
- make_superencrypted_key_entry(&mut db, nspace, "noauth_noud", false, false, super_key_id)?;
- make_superencrypted_key_entry(&mut db, nspace, "noauth_ud", false, true, super_key_id)?;
- make_superencrypted_key_entry(&mut db, nspace, "auth_noud", true, false, super_key_id)?;
- make_superencrypted_key_entry(&mut db, nspace, "auth_ud", true, true, super_key_id)?;
- assert!(app_key_exists(&mut db, nspace, "noauth_noud")?);
- assert!(app_key_exists(&mut db, nspace, "noauth_ud")?);
- assert!(app_key_exists(&mut db, nspace, "auth_noud")?);
- assert!(app_key_exists(&mut db, nspace, "auth_ud")?);
-
- // Also store a key for a different user that requires authentication.
- make_superencrypted_key_entry(
- &mut db,
- other_user_nspace,
- "auth_ud",
- true,
- true,
- super_key_id,
- )?;
-
- db.unbind_auth_bound_keys_for_user(user_id)?;
-
- // Verify that only the user's app keys that require authentication were
- // deleted. Keys that require an unlocked device but not authentication
- // should *not* have been deleted, nor should the super key have been
- // deleted, nor should other users' keys have been deleted.
- assert!(db.load_super_key(super_key_type, user_id)?.is_some());
- assert!(app_key_exists(&mut db, nspace, "noauth_noud")?);
- assert!(app_key_exists(&mut db, nspace, "noauth_ud")?);
- assert!(!app_key_exists(&mut db, nspace, "auth_noud")?);
- assert!(!app_key_exists(&mut db, nspace, "auth_ud")?);
- assert!(app_key_exists(&mut db, other_user_nspace, "auth_ud")?);
-
- Ok(())
- }
-
- #[test]
- fn test_store_super_key() -> Result<()> {
- let mut db = new_test_db()?;
- let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
- let super_key = keystore2_crypto::generate_aes256_key()?;
- let secret_bytes = b"keystore2 is great.";
- let (encrypted_secret, iv, tag) =
- keystore2_crypto::aes_gcm_encrypt(secret_bytes, &super_key)?;
-
- let (encrypted_super_key, metadata) =
- SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
- db.store_super_key(
- 1,
- &USER_AFTER_FIRST_UNLOCK_SUPER_KEY,
- &encrypted_super_key,
- &metadata,
- &KeyMetaData::new(),
- )?;
-
- // Check if super key exists.
- assert!(db.key_exists(
- Domain::APP,
- 1,
- USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias,
- KeyType::Super
- )?);
-
- let (_, key_entry) = db.load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, 1)?.unwrap();
- let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry(
- USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm,
- key_entry,
- &pw,
- None,
- )?;
-
- let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?;
- assert_eq!(secret_bytes, &*decrypted_secret_bytes);
-
- Ok(())
- }
-
- fn get_valid_statsd_storage_types() -> Vec<MetricsStorage> {
- vec![
- MetricsStorage::KEY_ENTRY,
- MetricsStorage::KEY_ENTRY_ID_INDEX,
- MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX,
- MetricsStorage::BLOB_ENTRY,
- MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX,
- MetricsStorage::KEY_PARAMETER,
- MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX,
- MetricsStorage::KEY_METADATA,
- MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX,
- MetricsStorage::GRANT,
- MetricsStorage::AUTH_TOKEN,
- MetricsStorage::BLOB_METADATA,
- MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX,
- ]
- }
-
- /// Perform a simple check to ensure that we can query all the storage types
- /// that are supported by the DB. Check for reasonable values.
- #[test]
- fn test_query_all_valid_table_sizes() -> Result<()> {
- const PAGE_SIZE: i32 = 4096;
-
- let mut db = new_test_db()?;
-
- for t in get_valid_statsd_storage_types() {
- let stat = db.get_storage_stat(t)?;
- // AuthToken can be less than a page since it's in a btree, not sqlite
- // TODO(b/187474736) stop using if-let here
- if let MetricsStorage::AUTH_TOKEN = t {
- } else {
- assert!(stat.size >= PAGE_SIZE);
- }
- assert!(stat.size >= stat.unused_size);
- }
-
- Ok(())
- }
-
- fn get_storage_stats_map(db: &mut KeystoreDB) -> BTreeMap<i32, StorageStats> {
- get_valid_statsd_storage_types()
- .into_iter()
- .map(|t| (t.0, db.get_storage_stat(t).unwrap()))
- .collect()
- }
-
- fn assert_storage_increased(
- db: &mut KeystoreDB,
- increased_storage_types: Vec<MetricsStorage>,
- baseline: &mut BTreeMap<i32, StorageStats>,
- ) {
- for storage in increased_storage_types {
- // Verify the expected storage increased.
- let new = db.get_storage_stat(storage).unwrap();
- let old = &baseline[&storage.0];
- assert!(new.size >= old.size, "{}: {} >= {}", storage.0, new.size, old.size);
- assert!(
- new.unused_size <= old.unused_size,
- "{}: {} <= {}",
- storage.0,
- new.unused_size,
- old.unused_size
- );
-
- // Update the baseline with the new value so that it succeeds in the
- // later comparison.
- baseline.insert(storage.0, new);
- }
-
- // Get an updated map of the storage and verify there were no unexpected changes.
- let updated_stats = get_storage_stats_map(db);
- assert_eq!(updated_stats.len(), baseline.len());
-
- for &k in baseline.keys() {
- let stringify = |map: &BTreeMap<i32, StorageStats>| -> String {
- let mut s = String::new();
- for &k in map.keys() {
- writeln!(&mut s, " {}: {}, {}", &k, map[&k].size, map[&k].unused_size)
- .expect("string concat failed");
- }
- s
- };
-
- assert!(
- updated_stats[&k].size == baseline[&k].size
- && updated_stats[&k].unused_size == baseline[&k].unused_size,
- "updated_stats:\n{}\nbaseline:\n{}",
- stringify(&updated_stats),
- stringify(baseline)
- );
- }
- }
-
- #[test]
- fn test_verify_key_table_size_reporting() -> Result<()> {
- let mut db = new_test_db()?;
- let mut working_stats = get_storage_stats_map(&mut db);
-
- let key_id = create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
- assert_storage_increased(
- &mut db,
- vec![
- MetricsStorage::KEY_ENTRY,
- MetricsStorage::KEY_ENTRY_ID_INDEX,
- MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX,
- ],
- &mut working_stats,
- );
-
- let mut blob_metadata = BlobMetaData::new();
- blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
- db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), None)?;
- assert_storage_increased(
- &mut db,
- vec![
- MetricsStorage::BLOB_ENTRY,
- MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX,
- MetricsStorage::BLOB_METADATA,
- MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX,
- ],
- &mut working_stats,
- );
-
- let params = make_test_params(None);
- db.insert_keyparameter(&key_id, ¶ms)?;
- assert_storage_increased(
- &mut db,
- vec![MetricsStorage::KEY_PARAMETER, MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX],
- &mut working_stats,
- );
-
- let mut metadata = KeyMetaData::new();
- metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
- db.insert_key_metadata(&key_id, &metadata)?;
- assert_storage_increased(
- &mut db,
- vec![MetricsStorage::KEY_METADATA, MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX],
- &mut working_stats,
- );
-
- let mut sum = 0;
- for stat in working_stats.values() {
- sum += stat.size;
- }
- let total = db.get_storage_stat(MetricsStorage::DATABASE)?.size;
- assert!(sum <= total, "Expected sum <= total. sum: {}, total: {}", sum, total);
-
- Ok(())
- }
-
- #[test]
- fn test_verify_auth_table_size_reporting() -> Result<()> {
- let mut db = new_test_db()?;
- let mut working_stats = get_storage_stats_map(&mut db);
- db.insert_auth_token(&HardwareAuthToken {
- challenge: 123,
- userId: 456,
- authenticatorId: 789,
- authenticatorType: kmhw_authenticator_type::ANY,
- timestamp: Timestamp { milliSeconds: 10 },
- mac: b"mac".to_vec(),
- });
- assert_storage_increased(&mut db, vec![MetricsStorage::AUTH_TOKEN], &mut working_stats);
- Ok(())
- }
-
- #[test]
- fn test_verify_grant_table_size_reporting() -> Result<()> {
- const OWNER: i64 = 1;
- let mut db = new_test_db()?;
- make_test_key_entry(&mut db, Domain::APP, OWNER, TEST_ALIAS, None)?;
-
- let mut working_stats = get_storage_stats_map(&mut db);
- db.grant(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(TEST_ALIAS.to_string()),
- blob: None,
- },
- OWNER as u32,
- 123,
- key_perm_set![KeyPerm::Use],
- |_, _| Ok(()),
- )?;
-
- assert_storage_increased(&mut db, vec![MetricsStorage::GRANT], &mut working_stats);
-
- Ok(())
- }
-
- #[test]
- fn find_auth_token_entry_returns_latest() -> Result<()> {
- let mut db = new_test_db()?;
- db.insert_auth_token(&HardwareAuthToken {
- challenge: 123,
- userId: 456,
- authenticatorId: 789,
- authenticatorType: kmhw_authenticator_type::ANY,
- timestamp: Timestamp { milliSeconds: 10 },
- mac: b"mac0".to_vec(),
- });
- std::thread::sleep(std::time::Duration::from_millis(1));
- db.insert_auth_token(&HardwareAuthToken {
- challenge: 123,
- userId: 457,
- authenticatorId: 789,
- authenticatorType: kmhw_authenticator_type::ANY,
- timestamp: Timestamp { milliSeconds: 12 },
- mac: b"mac1".to_vec(),
- });
- std::thread::sleep(std::time::Duration::from_millis(1));
- db.insert_auth_token(&HardwareAuthToken {
- challenge: 123,
- userId: 458,
- authenticatorId: 789,
- authenticatorType: kmhw_authenticator_type::ANY,
- timestamp: Timestamp { milliSeconds: 3 },
- mac: b"mac2".to_vec(),
- });
- // All three entries are in the database
- assert_eq!(db.perboot.auth_tokens_len(), 3);
- // It selected the most recent timestamp
- assert_eq!(db.find_auth_token_entry(|_| true).unwrap().auth_token.mac, b"mac2".to_vec());
- Ok(())
- }
-
- #[test]
- fn test_load_key_descriptor() -> Result<()> {
- let mut db = new_test_db()?;
- let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)?.0;
-
- let key = db.load_key_descriptor(key_id)?.unwrap();
-
- assert_eq!(key.domain, Domain::APP);
- assert_eq!(key.nspace, 1);
- assert_eq!(key.alias, Some(TEST_ALIAS.to_string()));
-
- // No such id
- assert_eq!(db.load_key_descriptor(key_id + 1)?, None);
- Ok(())
- }
-
- #[test]
- fn test_get_list_app_uids_for_sid() -> Result<()> {
- let uid: i32 = 1;
- let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64);
- let first_sid = 667;
- let second_sid = 669;
- let first_app_id: i64 = 123 + uid_offset;
- let second_app_id: i64 = 456 + uid_offset;
- let third_app_id: i64 = 789 + uid_offset;
- let unrelated_app_id: i64 = 1011 + uid_offset;
- let mut db = new_test_db()?;
- make_test_key_entry_with_sids(
- &mut db,
- Domain::APP,
- first_app_id,
- TEST_ALIAS,
- None,
- &[first_sid],
- )
- .context("test_get_list_app_uids_for_sid")?;
- make_test_key_entry_with_sids(
- &mut db,
- Domain::APP,
- second_app_id,
- "alias2",
- None,
- &[first_sid],
- )
- .context("test_get_list_app_uids_for_sid")?;
- make_test_key_entry_with_sids(
- &mut db,
- Domain::APP,
- second_app_id,
- TEST_ALIAS,
- None,
- &[second_sid],
- )
- .context("test_get_list_app_uids_for_sid")?;
- make_test_key_entry_with_sids(
- &mut db,
- Domain::APP,
- third_app_id,
- "alias3",
- None,
- &[second_sid],
- )
- .context("test_get_list_app_uids_for_sid")?;
- make_test_key_entry_with_sids(
- &mut db,
- Domain::APP,
- unrelated_app_id,
- TEST_ALIAS,
- None,
- &[],
- )
- .context("test_get_list_app_uids_for_sid")?;
-
- let mut first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?;
- first_sid_apps.sort();
- assert_eq!(first_sid_apps, vec![first_app_id, second_app_id]);
- let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?;
- second_sid_apps.sort();
- assert_eq!(second_sid_apps, vec![second_app_id, third_app_id]);
- Ok(())
- }
-
- #[test]
- fn test_get_list_app_uids_with_multiple_sids() -> Result<()> {
- let uid: i32 = 1;
- let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64);
- let first_sid = 667;
- let second_sid = 669;
- let third_sid = 772;
- let first_app_id: i64 = 123 + uid_offset;
- let second_app_id: i64 = 456 + uid_offset;
- let mut db = new_test_db()?;
- make_test_key_entry_with_sids(
- &mut db,
- Domain::APP,
- first_app_id,
- TEST_ALIAS,
- None,
- &[first_sid, second_sid],
- )
- .context("test_get_list_app_uids_for_sid")?;
- make_test_key_entry_with_sids(
- &mut db,
- Domain::APP,
- second_app_id,
- "alias2",
- None,
- &[second_sid, third_sid],
- )
- .context("test_get_list_app_uids_for_sid")?;
-
- let first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?;
- assert_eq!(first_sid_apps, vec![first_app_id]);
-
- let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?;
- second_sid_apps.sort();
- assert_eq!(second_sid_apps, vec![first_app_id, second_app_id]);
-
- let third_sid_apps = db.get_app_uids_affected_by_sid(uid, third_sid)?;
- assert_eq!(third_sid_apps, vec![second_app_id]);
- Ok(())
+ /// Retrieve a database PRAGMA config value.
+ pub fn pragma<T: FromSql>(&mut self, name: &str) -> Result<T> {
+ self.conn
+ .query_row(&format!("PRAGMA persistent.{name}"), (), |row| row.get(0))
+ .context(format!("failed to read pragma {name}"))
}
}
diff --git a/keystore2/src/database/perboot.rs b/keystore2/src/database/perboot.rs
index 4727015..a1890a6 100644
--- a/keystore2/src/database/perboot.rs
+++ b/keystore2/src/database/perboot.rs
@@ -19,9 +19,9 @@
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
};
-use lazy_static::lazy_static;
use std::collections::HashSet;
use std::sync::Arc;
+use std::sync::LazyLock;
use std::sync::RwLock;
#[derive(PartialEq, PartialOrd, Ord, Eq, Hash)]
@@ -70,11 +70,9 @@
auth_tokens: RwLock<HashSet<AuthTokenEntryWrap>>,
}
-lazy_static! {
- /// The global instance of the perboot DB. Located here rather than in globals
- /// in order to restrict access to the database module.
- pub static ref PERBOOT_DB: Arc<PerbootDB> = Arc::new(PerbootDB::new());
-}
+/// The global instance of the perboot DB. Located here rather than in globals
+/// in order to restrict access to the database module.
+pub static PERBOOT_DB: LazyLock<Arc<PerbootDB>> = LazyLock::new(|| Arc::new(PerbootDB::new()));
impl PerbootDB {
/// Construct a new perboot database. Currently just uses default values.
diff --git a/keystore2/src/database/tests.rs b/keystore2/src/database/tests.rs
new file mode 100644
index 0000000..4ada694
--- /dev/null
+++ b/keystore2/src/database/tests.rs
@@ -0,0 +1,2875 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Database tests.
+
+use super::*;
+use crate::key_parameter::{
+ Algorithm, BlockMode, Digest, EcCurve, HardwareAuthenticatorType, KeyOrigin, KeyParameter,
+ KeyParameterValue, KeyPurpose, PaddingMode, SecurityLevel,
+};
+use crate::key_perm_set;
+use crate::permission::{KeyPerm, KeyPermSet};
+use crate::super_key::{SuperKeyManager, USER_AFTER_FIRST_UNLOCK_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType};
+use keystore2_test_utils::TempDir;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ HardwareAuthToken::HardwareAuthToken,
+ HardwareAuthenticatorType::HardwareAuthenticatorType as kmhw_authenticator_type,
+};
+use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
+ Timestamp::Timestamp,
+};
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::fmt::Write;
+use std::sync::atomic::{AtomicU8, Ordering};
+use std::sync::Arc;
+use std::thread;
+use std::time::{Duration, SystemTime};
+use crate::utils::AesGcm;
+#[cfg(disabled)]
+use std::time::Instant;
+
+pub fn new_test_db() -> Result<KeystoreDB> {
+ new_test_db_at("file::memory:")
+}
+
+fn new_test_db_at(path: &str) -> Result<KeystoreDB> {
+ let conn = KeystoreDB::make_connection(path)?;
+
+ let mut db = KeystoreDB { conn, gc: None, perboot: Arc::new(perboot::PerbootDB::new()) };
+ db.with_transaction(Immediate("TX_new_test_db"), |tx| {
+ KeystoreDB::init_tables(tx).context("Failed to initialize tables.").no_gc()
+ })?;
+ Ok(db)
+}
+
+fn rebind_alias(
+ db: &mut KeystoreDB,
+ newid: &KeyIdGuard,
+ alias: &str,
+ domain: Domain,
+ namespace: i64,
+) -> Result<bool> {
+ db.with_transaction(Immediate("TX_rebind_alias"), |tx| {
+ KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace, KeyType::Client).no_gc()
+ })
+ .context(ks_err!())
+}
+
+#[test]
+fn datetime() -> Result<()> {
+ let conn = Connection::open_in_memory()?;
+ conn.execute("CREATE TABLE test (ts DATETIME);", [])?;
+ let now = SystemTime::now();
+ let duration = Duration::from_secs(1000);
+ let then = now.checked_sub(duration).unwrap();
+ let soon = now.checked_add(duration).unwrap();
+ conn.execute(
+ "INSERT INTO test (ts) VALUES (?), (?), (?);",
+ params![DateTime::try_from(now)?, DateTime::try_from(then)?, DateTime::try_from(soon)?],
+ )?;
+ let mut stmt = conn.prepare("SELECT ts FROM test ORDER BY ts ASC;")?;
+ let mut rows = stmt.query([])?;
+ assert_eq!(DateTime::try_from(then)?, rows.next()?.unwrap().get(0)?);
+ assert_eq!(DateTime::try_from(now)?, rows.next()?.unwrap().get(0)?);
+ assert_eq!(DateTime::try_from(soon)?, rows.next()?.unwrap().get(0)?);
+ assert!(rows.next()?.is_none());
+ assert!(DateTime::try_from(then)? < DateTime::try_from(now)?);
+ assert!(DateTime::try_from(then)? < DateTime::try_from(soon)?);
+ assert!(DateTime::try_from(now)? < DateTime::try_from(soon)?);
+ Ok(())
+}
+
+// Ensure that we're using the "injected" random function, not the real one.
+#[test]
+fn test_mocked_random() {
+ let rand1 = random();
+ let rand2 = random();
+ let rand3 = random();
+ if rand1 == rand2 {
+ assert_eq!(rand2 + 1, rand3);
+ } else {
+ assert_eq!(rand1 + 1, rand2);
+ assert_eq!(rand2, rand3);
+ }
+}
+
+// Test that we have the correct tables.
+#[test]
+fn test_tables() -> Result<()> {
+ let db = new_test_db()?;
+ let tables = db
+ .conn
+ .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
+ .query_map(params![], |row| row.get(0))?
+ .collect::<rusqlite::Result<Vec<String>>>()?;
+ assert_eq!(tables.len(), 6);
+ assert_eq!(tables[0], "blobentry");
+ assert_eq!(tables[1], "blobmetadata");
+ assert_eq!(tables[2], "grant");
+ assert_eq!(tables[3], "keyentry");
+ assert_eq!(tables[4], "keymetadata");
+ assert_eq!(tables[5], "keyparameter");
+ Ok(())
+}
+
+#[test]
+fn test_auth_token_table_invariant() -> Result<()> {
+ let mut db = new_test_db()?;
+ let auth_token1 = HardwareAuthToken {
+ challenge: i64::MAX,
+ userId: 200,
+ authenticatorId: 200,
+ authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
+ timestamp: Timestamp { milliSeconds: 500 },
+ mac: String::from("mac").into_bytes(),
+ };
+ db.insert_auth_token(&auth_token1);
+ let auth_tokens_returned = get_auth_tokens(&db);
+ assert_eq!(auth_tokens_returned.len(), 1);
+
+ // insert another auth token with the same values for the columns in the UNIQUE constraint
+ // of the auth token table and different value for timestamp
+ let auth_token2 = HardwareAuthToken {
+ challenge: i64::MAX,
+ userId: 200,
+ authenticatorId: 200,
+ authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
+ timestamp: Timestamp { milliSeconds: 600 },
+ mac: String::from("mac").into_bytes(),
+ };
+
+ db.insert_auth_token(&auth_token2);
+ let mut auth_tokens_returned = get_auth_tokens(&db);
+ assert_eq!(auth_tokens_returned.len(), 1);
+
+ if let Some(auth_token) = auth_tokens_returned.pop() {
+ assert_eq!(auth_token.auth_token.timestamp.milliSeconds, 600);
+ }
+
+ // insert another auth token with the different values for the columns in the UNIQUE
+ // constraint of the auth token table
+ let auth_token3 = HardwareAuthToken {
+ challenge: i64::MAX,
+ userId: 201,
+ authenticatorId: 200,
+ authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
+ timestamp: Timestamp { milliSeconds: 600 },
+ mac: String::from("mac").into_bytes(),
+ };
+
+ db.insert_auth_token(&auth_token3);
+ let auth_tokens_returned = get_auth_tokens(&db);
+ assert_eq!(auth_tokens_returned.len(), 2);
+
+ Ok(())
+}
+
+// utility function for test_auth_token_table_invariant()
+fn get_auth_tokens(db: &KeystoreDB) -> Vec<AuthTokenEntry> {
+ db.perboot.get_all_auth_token_entries()
+}
+
+fn create_key_entry(
+ db: &mut KeystoreDB,
+ domain: &Domain,
+ namespace: &i64,
+ key_type: KeyType,
+ km_uuid: &Uuid,
+) -> Result<KeyIdGuard> {
+ db.with_transaction(Immediate("TX_create_key_entry"), |tx| {
+ KeystoreDB::create_key_entry_internal(tx, domain, namespace, key_type, km_uuid).no_gc()
+ })
+}
+
+#[test]
+fn test_persistence_for_files() -> Result<()> {
+ let temp_dir = TempDir::new("persistent_db_test")?;
+ let mut db = KeystoreDB::new(temp_dir.path(), None)?;
+
+ create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?;
+ let entries = get_keyentry(&db)?;
+ assert_eq!(entries.len(), 1);
+
+ let db = KeystoreDB::new(temp_dir.path(), None)?;
+
+ let entries_new = get_keyentry(&db)?;
+ assert_eq!(entries, entries_new);
+ Ok(())
+}
+
+#[test]
+fn test_create_key_entry() -> Result<()> {
+ fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>, Uuid) {
+ (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref(), ke.km_uuid.unwrap())
+ }
+
+ let mut db = new_test_db()?;
+
+ create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?;
+ create_key_entry(&mut db, &Domain::SELINUX, &101, KeyType::Client, &KEYSTORE_UUID)?;
+
+ let entries = get_keyentry(&db)?;
+ assert_eq!(entries.len(), 2);
+ assert_eq!(extractor(&entries[0]), (Domain::APP, 100, None, KEYSTORE_UUID));
+ assert_eq!(extractor(&entries[1]), (Domain::SELINUX, 101, None, KEYSTORE_UUID));
+
+ // Test that we must pass in a valid Domain.
+ check_result_is_error_containing_string(
+ create_key_entry(&mut db, &Domain::GRANT, &102, KeyType::Client, &KEYSTORE_UUID),
+ &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
+ );
+ check_result_is_error_containing_string(
+ create_key_entry(&mut db, &Domain::BLOB, &103, KeyType::Client, &KEYSTORE_UUID),
+ &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
+ );
+ check_result_is_error_containing_string(
+ create_key_entry(&mut db, &Domain::KEY_ID, &104, KeyType::Client, &KEYSTORE_UUID),
+ &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_rebind_alias() -> Result<()> {
+ fn extractor(ke: &KeyEntryRow) -> (Option<Domain>, Option<i64>, Option<&str>, Option<Uuid>) {
+ (ke.domain, ke.namespace, ke.alias.as_deref(), ke.km_uuid)
+ }
+
+ let mut db = new_test_db()?;
+ create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
+ create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
+ let entries = get_keyentry(&db)?;
+ assert_eq!(entries.len(), 2);
+ assert_eq!(extractor(&entries[0]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)));
+ assert_eq!(extractor(&entries[1]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)));
+
+ // Test that the first call to rebind_alias sets the alias.
+ rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[0].id), "foo", Domain::APP, 42)?;
+ let entries = get_keyentry(&db)?;
+ assert_eq!(entries.len(), 2);
+ assert_eq!(
+ extractor(&entries[0]),
+ (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
+ );
+ assert_eq!(extractor(&entries[1]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)));
+
+ // Test that the second call to rebind_alias also empties the old one.
+ rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[1].id), "foo", Domain::APP, 42)?;
+ let entries = get_keyentry(&db)?;
+ assert_eq!(entries.len(), 2);
+ assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID)));
+ assert_eq!(
+ extractor(&entries[1]),
+ (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
+ );
+
+ // Test that we must pass in a valid Domain.
+ check_result_is_error_containing_string(
+ rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::GRANT, 42),
+ &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
+ );
+ check_result_is_error_containing_string(
+ rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::BLOB, 42),
+ &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
+ );
+ check_result_is_error_containing_string(
+ rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::KEY_ID, 42),
+ &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
+ );
+
+ // Test that we correctly handle setting an alias for something that does not exist.
+ check_result_is_error_containing_string(
+ rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::SELINUX, 42),
+ "Expected to update a single entry but instead updated 0",
+ );
+ // Test that we correctly abort the transaction in this case.
+ let entries = get_keyentry(&db)?;
+ assert_eq!(entries.len(), 2);
+ assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID)));
+ assert_eq!(
+ extractor(&entries[1]),
+ (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_grant_ungrant() -> Result<()> {
+ const CALLER_UID: u32 = 15;
+ const GRANTEE_UID: u32 = 12;
+ const SELINUX_NAMESPACE: i64 = 7;
+
+ let mut db = new_test_db()?;
+ db.conn.execute(
+ "INSERT INTO persistent.keyentry (id, key_type, domain, namespace, alias, state, km_uuid)
+ VALUES (1, 0, 0, 15, 'key', 1, ?), (2, 0, 2, 7, 'yek', 1, ?);",
+ params![KEYSTORE_UUID, KEYSTORE_UUID],
+ )?;
+ let app_key = KeyDescriptor {
+ domain: super::Domain::APP,
+ nspace: 0,
+ alias: Some("key".to_string()),
+ blob: None,
+ };
+ const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo];
+ const PVEC2: KeyPermSet = key_perm_set![KeyPerm::Use];
+
+ // Reset totally predictable random number generator in case we
+ // are not the first test running on this thread.
+ reset_random();
+ let next_random = 0i64;
+
+ let app_granted_key = db
+ .grant(&app_key, CALLER_UID, GRANTEE_UID, PVEC1, |k, a| {
+ assert_eq!(*a, PVEC1);
+ assert_eq!(
+ *k,
+ KeyDescriptor {
+ domain: super::Domain::APP,
+ // namespace must be set to the caller_uid.
+ nspace: CALLER_UID as i64,
+ alias: Some("key".to_string()),
+ blob: None,
+ }
+ );
+ Ok(())
+ })
+ .unwrap();
+
+ assert_eq!(
+ app_granted_key,
+ KeyDescriptor {
+ domain: super::Domain::GRANT,
+ // The grantid is next_random due to the mock random number generator.
+ nspace: next_random,
+ alias: None,
+ blob: None,
+ }
+ );
+
+ let selinux_key = KeyDescriptor {
+ domain: super::Domain::SELINUX,
+ nspace: SELINUX_NAMESPACE,
+ alias: Some("yek".to_string()),
+ blob: None,
+ };
+
+ let selinux_granted_key = db
+ .grant(&selinux_key, CALLER_UID, 12, PVEC1, |k, a| {
+ assert_eq!(*a, PVEC1);
+ assert_eq!(
+ *k,
+ KeyDescriptor {
+ domain: super::Domain::SELINUX,
+ // namespace must be the supplied SELinux
+ // namespace.
+ nspace: SELINUX_NAMESPACE,
+ alias: Some("yek".to_string()),
+ blob: None,
+ }
+ );
+ Ok(())
+ })
+ .unwrap();
+
+ assert_eq!(
+ selinux_granted_key,
+ KeyDescriptor {
+ domain: super::Domain::GRANT,
+ // The grantid is next_random + 1 due to the mock random number generator.
+ nspace: next_random + 1,
+ alias: None,
+ blob: None,
+ }
+ );
+
+ // This should update the existing grant with PVEC2.
+ let selinux_granted_key = db
+ .grant(&selinux_key, CALLER_UID, 12, PVEC2, |k, a| {
+ assert_eq!(*a, PVEC2);
+ assert_eq!(
+ *k,
+ KeyDescriptor {
+ domain: super::Domain::SELINUX,
+ // namespace must be the supplied SELinux
+ // namespace.
+ nspace: SELINUX_NAMESPACE,
+ alias: Some("yek".to_string()),
+ blob: None,
+ }
+ );
+ Ok(())
+ })
+ .unwrap();
+
+ assert_eq!(
+ selinux_granted_key,
+ KeyDescriptor {
+ domain: super::Domain::GRANT,
+ // Same grant id as before. The entry was only updated.
+ nspace: next_random + 1,
+ alias: None,
+ blob: None,
+ }
+ );
+
+ {
+ // Limiting scope of stmt, because it borrows db.
+ let mut stmt = db
+ .conn
+ .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
+ let mut rows = stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>([], |row| {
+ Ok((row.get(0)?, row.get(1)?, row.get(2)?, KeyPermSet::from(row.get::<_, i32>(3)?)))
+ })?;
+
+ let r = rows.next().unwrap().unwrap();
+ assert_eq!(r, (next_random, GRANTEE_UID, 1, PVEC1));
+ let r = rows.next().unwrap().unwrap();
+ assert_eq!(r, (next_random + 1, GRANTEE_UID, 2, PVEC2));
+ assert!(rows.next().is_none());
+ }
+
+ debug_dump_keyentry_table(&mut db)?;
+ println!("app_key {:?}", app_key);
+ println!("selinux_key {:?}", selinux_key);
+
+ db.ungrant(&app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
+ db.ungrant(&selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
+
+ Ok(())
+}
+
+static TEST_KEY_BLOB: &[u8] = b"my test blob";
+static TEST_CERT_BLOB: &[u8] = b"my test cert";
+static TEST_CERT_CHAIN_BLOB: &[u8] = b"my test cert_chain";
+
+#[test]
+fn test_set_blob() -> Result<()> {
+ let key_id = KEY_ID_LOCK.get(3000);
+ let mut db = new_test_db()?;
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+ db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?;
+ db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
+ db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
+ drop(key_id);
+
+ let mut stmt = db.conn.prepare(
+ "SELECT subcomponent_type, keyentryid, blob, id FROM persistent.blobentry
+ ORDER BY subcomponent_type ASC;",
+ )?;
+ let mut rows = stmt.query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>([], |row| {
+ Ok(((row.get(0)?, row.get(1)?, row.get(2)?), row.get(3)?))
+ })?;
+ let (r, id) = rows.next().unwrap().unwrap();
+ assert_eq!(r, (SubComponentType::KEY_BLOB, 3000, TEST_KEY_BLOB.to_vec()));
+ let (r, _) = rows.next().unwrap().unwrap();
+ assert_eq!(r, (SubComponentType::CERT, 3000, TEST_CERT_BLOB.to_vec()));
+ let (r, _) = rows.next().unwrap().unwrap();
+ assert_eq!(r, (SubComponentType::CERT_CHAIN, 3000, TEST_CERT_CHAIN_BLOB.to_vec()));
+
+ drop(rows);
+ drop(stmt);
+
+ assert_eq!(
+ db.with_transaction(Immediate("TX_test"), |tx| {
+ BlobMetaData::load_from_db(id, tx).no_gc()
+ })
+ .expect("Should find blob metadata."),
+ blob_metadata
+ );
+ Ok(())
+}
+
+static TEST_ALIAS: &str = "my super duper key";
+
+#[test]
+fn test_insert_and_load_full_keyentry_domain_app() -> Result<()> {
+ let mut db = new_test_db()?;
+ let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_domain_app")?
+ .0;
+ let (_key_guard, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ 1,
+ |_k, _av| Ok(()),
+ )
+ .unwrap();
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ db.unbind_key(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ 1,
+ |_, _| Ok(()),
+ )
+ .unwrap();
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ 1,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_insert_and_load_certificate_entry_domain_app() -> Result<()> {
+ let mut db = new_test_db()?;
+
+ db.store_new_certificate(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 1,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ TEST_CERT_BLOB,
+ &KEYSTORE_UUID,
+ )
+ .expect("Trying to insert cert.");
+
+ let (_key_guard, mut key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 1,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::PUBLIC,
+ 1,
+ |_k, _av| Ok(()),
+ )
+ .expect("Trying to read certificate entry.");
+
+ assert!(key_entry.pure_cert());
+ assert!(key_entry.cert().is_none());
+ assert_eq!(key_entry.take_cert_chain(), Some(TEST_CERT_BLOB.to_vec()));
+
+ db.unbind_key(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 1,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ 1,
+ |_, _| Ok(()),
+ )
+ .unwrap();
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 1,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ 1,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_insert_and_load_full_keyentry_domain_selinux() -> Result<()> {
+ let mut db = new_test_db()?;
+ let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_domain_selinux")?
+ .0;
+ let (_key_guard, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: 1,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ 1,
+ |_k, _av| Ok(()),
+ )
+ .unwrap();
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ db.unbind_key(
+ &KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: 1,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ 1,
+ |_, _| Ok(()),
+ )
+ .unwrap();
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: 1,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ 1,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_insert_and_load_full_keyentry_domain_key_id() -> Result<()> {
+ let mut db = new_test_db()?;
+ let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_domain_key_id")?
+ .0;
+ let (_, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ 1,
+ |_k, _av| Ok(()),
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ db.unbind_key(
+ &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+ KeyType::Client,
+ 1,
+ |_, _| Ok(()),
+ )
+ .unwrap();
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ 1,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_check_and_update_key_usage_count_with_limited_use_key() -> Result<()> {
+ let mut db = new_test_db()?;
+ let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(123))
+ .context("test_check_and_update_key_usage_count_with_limited_use_key")?
+ .0;
+ // Update the usage count of the limited use key.
+ db.check_and_update_key_usage_count(key_id)?;
+
+ let (_key_guard, key_entry) = db.load_key_entry(
+ &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ 1,
+ |_k, _av| Ok(()),
+ )?;
+
+ // The usage count is decremented now.
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, Some(122)));
+
+ Ok(())
+}
+
+#[test]
+fn test_check_and_update_key_usage_count_with_exhausted_limited_use_key() -> Result<()> {
+ let mut db = new_test_db()?;
+ let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(1))
+ .context("test_check_and_update_key_usage_count_with_exhausted_limited_use_key")?
+ .0;
+ // Update the usage count of the limited use key.
+ db.check_and_update_key_usage_count(key_id).expect(concat!(
+ "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ",
+ "This should succeed."
+ ));
+
+ // Try to update the exhausted limited use key.
+ let e = db.check_and_update_key_usage_count(key_id).expect_err(concat!(
+ "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ",
+ "This should fail."
+ ));
+ assert_eq!(
+ &KsError::Km(ErrorCode::INVALID_KEY_BLOB),
+ e.root_cause().downcast_ref::<KsError>().unwrap()
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_insert_and_load_full_keyentry_from_grant() -> Result<()> {
+ let mut db = new_test_db()?;
+ let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant")?
+ .0;
+
+ let granted_key = db
+ .grant(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ 1,
+ 2,
+ key_perm_set![KeyPerm::Use],
+ |_k, _av| Ok(()),
+ )
+ .unwrap();
+
+ debug_dump_grant_table(&mut db)?;
+
+ let (_key_guard, key_entry) = db
+ .load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| {
+ assert_eq!(Domain::GRANT, k.domain);
+ assert!(av.unwrap().includes(KeyPerm::Use));
+ Ok(())
+ })
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ db.unbind_key(&granted_key, KeyType::Client, 2, |_, _| Ok(())).unwrap();
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::NONE, 2, |_k, _av| Ok(
+ ()
+ ),)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+// This test attempts to load a key by key id while the caller is not the owner
+// but a grant exists for the given key and the caller.
+#[test]
+fn test_insert_and_load_full_keyentry_from_grant_by_key_id() -> Result<()> {
+ let mut db = new_test_db()?;
+ const OWNER_UID: u32 = 1u32;
+ const GRANTEE_UID: u32 = 2u32;
+ const SOMEONE_ELSE_UID: u32 = 3u32;
+ let key_id = make_test_key_entry(&mut db, Domain::APP, OWNER_UID as i64, TEST_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?
+ .0;
+
+ db.grant(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ OWNER_UID,
+ GRANTEE_UID,
+ key_perm_set![KeyPerm::Use],
+ |_k, _av| Ok(()),
+ )
+ .unwrap();
+
+ debug_dump_grant_table(&mut db)?;
+
+ let id_descriptor =
+ KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, ..Default::default() };
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &id_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ GRANTEE_UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(OWNER_UID as i64, k.nspace);
+ assert!(av.unwrap().includes(KeyPerm::Use));
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &id_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ SOMEONE_ELSE_UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(OWNER_UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ db.unbind_key(&id_descriptor, KeyType::Client, OWNER_UID, |_, _| Ok(())).unwrap();
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &id_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ GRANTEE_UID,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+// Creates a key migrates it to a different location and then tries to access it by the old
+// and new location.
+#[test]
+fn test_migrate_key_app_to_app() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let source_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(SOURCE_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let key_id = key_id_guard.id();
+
+ db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok(()))
+ .unwrap();
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &destination_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ DESTINATION_UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(DESTINATION_UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &source_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ SOURCE_UID,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+// Creates a key migrates it to a different location and then tries to access it by the old
+// and new location.
+#[test]
+fn test_migrate_key_app_to_selinux() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ const DESTINATION_NAMESPACE: i64 = 1000i64;
+ static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let source_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(SOURCE_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: DESTINATION_NAMESPACE,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let key_id = key_id_guard.id();
+
+ db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok(()))
+ .unwrap();
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &destination_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ DESTINATION_UID,
+ |k, av| {
+ assert_eq!(Domain::SELINUX, k.domain);
+ assert_eq!(DESTINATION_NAMESPACE, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &source_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ SOURCE_UID,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+// Creates two keys and tries to migrate the first to the location of the second which
+// is expected to fail.
+#[test]
+fn test_migrate_key_destination_occupied() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+ make_test_key_entry(&mut db, Domain::APP, DESTINATION_UID as i64, DESTINATION_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::INVALID_ARGUMENT)),
+ db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok(
+ ()
+ ))
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_upgrade_0_to_1() {
+ const ALIAS1: &str = "test_upgrade_0_to_1_1";
+ const ALIAS2: &str = "test_upgrade_0_to_1_2";
+ const ALIAS3: &str = "test_upgrade_0_to_1_3";
+ const UID: u32 = 33;
+ let temp_dir = Arc::new(TempDir::new("test_upgrade_0_to_1").unwrap());
+ let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
+ let key_id_untouched1 =
+ make_test_key_entry(&mut db, Domain::APP, UID as i64, ALIAS1, None).unwrap().id();
+ let key_id_untouched2 =
+ make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS2, false).unwrap().id();
+ let key_id_deleted =
+ make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS3, true).unwrap().id();
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(ALIAS1.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None));
+ let (_, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(ALIAS2.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+ assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false));
+ let (_, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(ALIAS3.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+ assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_deleted, true));
+
+ db.with_transaction(Immediate("TX_test"), |tx| KeystoreDB::from_0_to_1(tx).no_gc()).unwrap();
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(ALIAS1.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None));
+ let (_, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(ALIAS2.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+ assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false));
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(ALIAS3.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+}
+
+static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key";
+
+#[test]
+fn test_insert_and_load_full_keyentry_domain_app_concurrently() -> Result<()> {
+ let handle = {
+ let temp_dir = Arc::new(TempDir::new("id_lock_test")?);
+ let temp_dir_clone = temp_dir.clone();
+ let mut db = KeystoreDB::new(temp_dir.path(), None)?;
+ let key_id = make_test_key_entry(&mut db, Domain::APP, 33, KEY_LOCK_TEST_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_domain_app")?
+ .0;
+ let (_key_guard, key_entry) = db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ 33,
+ |_k, _av| Ok(()),
+ )
+ .unwrap();
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+ let state = Arc::new(AtomicU8::new(1));
+ let state2 = state.clone();
+
+ // Spawning a second thread that attempts to acquire the key id lock
+ // for the same key as the primary thread. The primary thread then
+ // waits, thereby forcing the secondary thread into the second stage
+ // of acquiring the lock (see KEY ID LOCK 2/2 above).
+ // The test succeeds if the secondary thread observes the transition
+ // of `state` from 1 to 2, despite having a whole second to overtake
+ // the primary thread.
+ let handle = thread::spawn(move || {
+ let temp_dir = temp_dir_clone;
+ let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
+ assert!(db
+ .load_key_entry(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ 33,
+ |_k, _av| Ok(()),
+ )
+ .is_ok());
+ // We should only see a 2 here because we can only return
+ // from load_key_entry when the `_key_guard` expires,
+ // which happens at the end of the scope.
+ assert_eq!(2, state2.load(Ordering::Relaxed));
+ });
+
+ thread::sleep(std::time::Duration::from_millis(1000));
+
+ assert_eq!(Ok(1), state.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed));
+
+ // Return the handle from this scope so we can join with the
+ // secondary thread after the key id lock has expired.
+ handle
+ // This is where the `_key_guard` goes out of scope,
+ // which is the reason for concurrent load_key_entry on the same key
+ // to unblock.
+ };
+ // Join with the secondary thread and unwrap, to propagate failing asserts to the
+ // main test thread. We will not see failing asserts in secondary threads otherwise.
+ handle.join().unwrap();
+ Ok(())
+}
+
+#[test]
+fn test_database_busy_error_code() {
+ let temp_dir =
+ TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir.");
+
+ let mut db1 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database1.");
+ let mut db2 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database2.");
+
+ let _tx1 = db1
+ .conn
+ .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
+ .expect("Failed to create first transaction.");
+
+ let error = db2
+ .conn
+ .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
+ .context("Transaction begin failed.")
+ .expect_err("This should fail.");
+ let root_cause = error.root_cause();
+ if let Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. }) =
+ root_cause.downcast_ref::<rusqlite::ffi::Error>()
+ {
+ return;
+ }
+ panic!(
+ "Unexpected error {:?} \n{:?} \n{:?}",
+ error,
+ root_cause,
+ root_cause.downcast_ref::<rusqlite::ffi::Error>()
+ )
+}
+
+#[cfg(disabled)]
+#[test]
+fn test_large_number_of_concurrent_db_manipulations() -> Result<()> {
+ let temp_dir = Arc::new(
+ TempDir::new("test_large_number_of_concurrent_db_manipulations_")
+ .expect("Failed to create temp dir."),
+ );
+
+ let test_begin = Instant::now();
+
+ const KEY_COUNT: u32 = 500u32;
+ let mut db =
+ new_test_db_with_gc(temp_dir.path(), |_, _| Ok(())).expect("Failed to open database.");
+ const OPEN_DB_COUNT: u32 = 50u32;
+
+ let mut actual_key_count = KEY_COUNT;
+ // First insert KEY_COUNT keys.
+ for count in 0..KEY_COUNT {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) {
+ actual_key_count = count;
+ break;
+ }
+ let alias = format!("test_alias_{}", count);
+ make_test_key_entry(&mut db, Domain::APP, 1, &alias, None)
+ .expect("Failed to make key entry.");
+ }
+
+ // Insert more keys from a different thread and into a different namespace.
+ let temp_dir1 = temp_dir.clone();
+ let handle1 = thread::spawn(move || {
+ let mut db =
+ new_test_db_with_gc(temp_dir1.path(), |_, _| Ok(())).expect("Failed to open database.");
+
+ for count in 0..actual_key_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let alias = format!("test_alias_{}", count);
+ make_test_key_entry(&mut db, Domain::APP, 2, &alias, None)
+ .expect("Failed to make key entry.");
+ }
+
+ // then unbind them again.
+ for count in 0..actual_key_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let key = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(format!("test_alias_{}", count)),
+ blob: None,
+ };
+ db.unbind_key(&key, KeyType::Client, 2, |_, _| Ok(())).expect("Unbind Failed.");
+ }
+ });
+
+ // And start unbinding the first set of keys.
+ let temp_dir2 = temp_dir.clone();
+ let handle2 = thread::spawn(move || {
+ let mut db =
+ new_test_db_with_gc(temp_dir2.path(), |_, _| Ok(())).expect("Failed to open database.");
+
+ for count in 0..actual_key_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let key = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(format!("test_alias_{}", count)),
+ blob: None,
+ };
+ db.unbind_key(&key, KeyType::Client, 1, |_, _| Ok(())).expect("Unbind Failed.");
+ }
+ });
+
+ // While a lot of inserting and deleting is going on we have to open database connections
+ // successfully and use them.
+ // This clone is not redundant, because temp_dir needs to be kept alive until db goes
+ // out of scope.
+ #[allow(clippy::redundant_clone)]
+ let temp_dir4 = temp_dir.clone();
+ let handle4 = thread::spawn(move || {
+ for count in 0..OPEN_DB_COUNT {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let mut db = new_test_db_with_gc(temp_dir4.path(), |_, _| Ok(()))
+ .expect("Failed to open database.");
+
+ let alias = format!("test_alias_{}", count);
+ make_test_key_entry(&mut db, Domain::APP, 3, &alias, None)
+ .expect("Failed to make key entry.");
+ let key =
+ KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None };
+ db.unbind_key(&key, KeyType::Client, 3, |_, _| Ok(())).expect("Unbind Failed.");
+ }
+ });
+
+ handle1.join().expect("Thread 1 panicked.");
+ handle2.join().expect("Thread 2 panicked.");
+ handle4.join().expect("Thread 4 panicked.");
+
+ Ok(())
+}
+
+#[test]
+fn list() -> Result<()> {
+ let temp_dir = TempDir::new("list_test")?;
+ let mut db = KeystoreDB::new(temp_dir.path(), None)?;
+ static LIST_O_ENTRIES: &[(Domain, i64, &str)] = &[
+ (Domain::APP, 1, "test1"),
+ (Domain::APP, 1, "test2"),
+ (Domain::APP, 1, "test3"),
+ (Domain::APP, 1, "test4"),
+ (Domain::APP, 1, "test5"),
+ (Domain::APP, 1, "test6"),
+ (Domain::APP, 1, "test7"),
+ (Domain::APP, 2, "test1"),
+ (Domain::APP, 2, "test2"),
+ (Domain::APP, 2, "test3"),
+ (Domain::APP, 2, "test4"),
+ (Domain::APP, 2, "test5"),
+ (Domain::APP, 2, "test6"),
+ (Domain::APP, 2, "test8"),
+ (Domain::SELINUX, 100, "test1"),
+ (Domain::SELINUX, 100, "test2"),
+ (Domain::SELINUX, 100, "test3"),
+ (Domain::SELINUX, 100, "test4"),
+ (Domain::SELINUX, 100, "test5"),
+ (Domain::SELINUX, 100, "test6"),
+ (Domain::SELINUX, 100, "test9"),
+ ];
+
+ let list_o_keys: Vec<(i64, i64)> = LIST_O_ENTRIES
+ .iter()
+ .map(|(domain, ns, alias)| {
+ let entry =
+ make_test_key_entry(&mut db, *domain, *ns, alias, None).unwrap_or_else(|e| {
+ panic!("Failed to insert {:?} {} {}. Error {:?}", domain, ns, alias, e)
+ });
+ (entry.id(), *ns)
+ })
+ .collect();
+
+ for (domain, namespace) in
+ &[(Domain::APP, 1i64), (Domain::APP, 2i64), (Domain::SELINUX, 100i64)]
+ {
+ let mut list_o_descriptors: Vec<KeyDescriptor> = LIST_O_ENTRIES
+ .iter()
+ .filter_map(|(domain, ns, alias)| match ns {
+ ns if *ns == *namespace => Some(KeyDescriptor {
+ domain: *domain,
+ nspace: *ns,
+ alias: Some(alias.to_string()),
+ blob: None,
+ }),
+ _ => None,
+ })
+ .collect();
+ list_o_descriptors.sort();
+ let mut list_result = db.list_past_alias(*domain, *namespace, KeyType::Client, None)?;
+ list_result.sort();
+ assert_eq!(list_o_descriptors, list_result);
+
+ let mut list_o_ids: Vec<i64> = list_o_descriptors
+ .into_iter()
+ .map(|d| {
+ let (_, entry) = db
+ .load_key_entry(
+ &d,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ *namespace as u32,
+ |_, _| Ok(()),
+ )
+ .unwrap();
+ entry.id()
+ })
+ .collect();
+ list_o_ids.sort_unstable();
+ let mut loaded_entries: Vec<i64> = list_o_keys
+ .iter()
+ .filter_map(|(id, ns)| match ns {
+ ns if *ns == *namespace => Some(*id),
+ _ => None,
+ })
+ .collect();
+ loaded_entries.sort_unstable();
+ assert_eq!(list_o_ids, loaded_entries);
+ }
+ assert_eq!(
+ Vec::<KeyDescriptor>::new(),
+ db.list_past_alias(Domain::SELINUX, 101, KeyType::Client, None)?
+ );
+
+ Ok(())
+}
+
+// Helpers
+
+// Checks that the given result is an error containing the given string.
+fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) {
+ let error_str =
+ format!("{:#?}", result.err().unwrap_or_else(|| panic!("Expected the error: {}", target)));
+ assert!(
+ error_str.contains(target),
+ "The string \"{}\" should contain \"{}\"",
+ error_str,
+ target
+ );
+}
+
+#[derive(Debug, PartialEq)]
+struct KeyEntryRow {
+ id: i64,
+ key_type: KeyType,
+ domain: Option<Domain>,
+ namespace: Option<i64>,
+ alias: Option<String>,
+ state: KeyLifeCycle,
+ km_uuid: Option<Uuid>,
+}
+
+fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> {
+ db.conn
+ .prepare("SELECT * FROM persistent.keyentry;")?
+ .query_map([], |row| {
+ Ok(KeyEntryRow {
+ id: row.get(0)?,
+ key_type: row.get(1)?,
+ domain: row.get::<_, Option<_>>(2)?.map(Domain),
+ namespace: row.get(3)?,
+ alias: row.get(4)?,
+ state: row.get(5)?,
+ km_uuid: row.get(6)?,
+ })
+ })?
+ .map(|r| r.context("Could not read keyentry row."))
+ .collect::<Result<Vec<_>>>()
+}
+
+fn make_test_params(max_usage_count: Option<i32>) -> Vec<KeyParameter> {
+ make_test_params_with_sids(max_usage_count, &[42])
+}
+
+// Note: The parameters and SecurityLevel associations are nonsensical. This
+// collection is only used to check if the parameters are preserved as expected by the
+// database.
+fn make_test_params_with_sids(
+ max_usage_count: Option<i32>,
+ user_secure_ids: &[i64],
+) -> Vec<KeyParameter> {
+ let mut params = vec![
+ KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(
+ KeyParameterValue::KeyPurpose(KeyPurpose::SIGN),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::Algorithm(Algorithm::RSA),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(
+ KeyParameterValue::BlockMode(BlockMode::ECB),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::BlockMode(BlockMode::GCM),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::Digest(Digest::NONE), SecurityLevel::STRONGBOX),
+ KeyParameter::new(
+ KeyParameterValue::Digest(Digest::MD5),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::Digest(Digest::SHA_2_224),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::Digest(Digest::SHA_2_256), SecurityLevel::STRONGBOX),
+ KeyParameter::new(
+ KeyParameterValue::PaddingMode(PaddingMode::NONE),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::PaddingMode(PaddingMode::RSA_OAEP),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS),
+ SecurityLevel::STRONGBOX,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::PaddingMode(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(KeyParameterValue::MinMacLength(256), SecurityLevel::STRONGBOX),
+ KeyParameter::new(
+ KeyParameterValue::EcCurve(EcCurve::P_224),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::STRONGBOX),
+ KeyParameter::new(
+ KeyParameterValue::EcCurve(EcCurve::P_384),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::EcCurve(EcCurve::P_521),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::RSAPublicExponent(3),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::IncludeUniqueID, SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(KeyParameterValue::BootLoaderOnly, SecurityLevel::STRONGBOX),
+ KeyParameter::new(KeyParameterValue::RollbackResistance, SecurityLevel::STRONGBOX),
+ KeyParameter::new(KeyParameterValue::ActiveDateTime(1234567890), SecurityLevel::STRONGBOX),
+ KeyParameter::new(
+ KeyParameterValue::OriginationExpireDateTime(1234567890),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::UsageExpireDateTime(1234567890),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::MinSecondsBetweenOps(1234567890),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::MaxUsesPerBoot(1234567890),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::UserID(1), SecurityLevel::STRONGBOX),
+ KeyParameter::new(KeyParameterValue::NoAuthRequired, SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(
+ KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::PASSWORD),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::AuthTimeout(1234567890), SecurityLevel::SOFTWARE),
+ KeyParameter::new(KeyParameterValue::AllowWhileOnBody, SecurityLevel::SOFTWARE),
+ KeyParameter::new(
+ KeyParameterValue::TrustedUserPresenceRequired,
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::TrustedConfirmationRequired,
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::UnlockedDeviceRequired,
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::ApplicationID(vec![1u8, 2u8, 3u8, 4u8]),
+ SecurityLevel::SOFTWARE,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::ApplicationData(vec![4u8, 3u8, 2u8, 1u8]),
+ SecurityLevel::SOFTWARE,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::CreationDateTime(12345677890),
+ SecurityLevel::SOFTWARE,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::RootOfTrust(vec![3u8, 2u8, 1u8, 4u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::OSVersion(1), SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(KeyParameterValue::OSPatchLevel(2), SecurityLevel::SOFTWARE),
+ KeyParameter::new(
+ KeyParameterValue::UniqueID(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::SOFTWARE,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationChallenge(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationApplicationID(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdBrand(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdDevice(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdProduct(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdSerial(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdIMEI(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdSecondIMEI(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdMEID(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdManufacturer(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::AttestationIdModel(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::VendorPatchLevel(3),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::BootPatchLevel(4), SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(
+ KeyParameterValue::AssociatedData(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::Nonce(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::MacLength(256), SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(
+ KeyParameterValue::ResetSinceIdRotation,
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::ConfirmationToken(vec![5u8, 5u8, 5u8, 5u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ ];
+ if let Some(value) = max_usage_count {
+ params.push(KeyParameter::new(
+ KeyParameterValue::UsageCountLimit(value),
+ SecurityLevel::SOFTWARE,
+ ));
+ }
+
+ for sid in user_secure_ids.iter() {
+ params.push(KeyParameter::new(
+ KeyParameterValue::UserSecureID(*sid),
+ SecurityLevel::STRONGBOX,
+ ));
+ }
+ params
+}
+
+pub fn make_test_key_entry(
+ db: &mut KeystoreDB,
+ domain: Domain,
+ namespace: i64,
+ alias: &str,
+ max_usage_count: Option<i32>,
+) -> Result<KeyIdGuard> {
+ make_test_key_entry_with_sids(db, domain, namespace, alias, max_usage_count, &[42])
+}
+
+pub fn make_test_key_entry_with_sids(
+ db: &mut KeystoreDB,
+ domain: Domain,
+ namespace: i64,
+ alias: &str,
+ max_usage_count: Option<i32>,
+ sids: &[i64],
+) -> Result<KeyIdGuard> {
+ let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+ blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
+ blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
+ blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+ db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?;
+ db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
+ db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
+
+ let params = make_test_params_with_sids(max_usage_count, sids);
+ db.insert_keyparameter(&key_id, ¶ms)?;
+
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+ db.insert_key_metadata(&key_id, &metadata)?;
+ rebind_alias(db, &key_id, alias, domain, namespace)?;
+ Ok(key_id)
+}
+
+fn make_test_key_entry_test_vector(key_id: i64, max_usage_count: Option<i32>) -> KeyEntry {
+ let params = make_test_params(max_usage_count);
+
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+ blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
+ blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
+ blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+
+ KeyEntry {
+ id: key_id,
+ key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
+ cert: Some(TEST_CERT_BLOB.to_vec()),
+ cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
+ km_uuid: KEYSTORE_UUID,
+ parameters: params,
+ metadata,
+ pure_cert: false,
+ }
+}
+
+pub fn make_bootlevel_key_entry(
+ db: &mut KeystoreDB,
+ domain: Domain,
+ namespace: i64,
+ alias: &str,
+ logical_only: bool,
+) -> Result<KeyIdGuard> {
+ let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
+ let mut blob_metadata = BlobMetaData::new();
+ if !logical_only {
+ blob_metadata.add(BlobMetaEntry::MaxBootLevel(3));
+ }
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+ db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?;
+ db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
+ db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
+
+ let mut params = make_test_params(None);
+ params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE));
+
+ db.insert_keyparameter(&key_id, ¶ms)?;
+
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+ db.insert_key_metadata(&key_id, &metadata)?;
+ rebind_alias(db, &key_id, alias, domain, namespace)?;
+ Ok(key_id)
+}
+
+// Creates an app key that is marked as being superencrypted by the given
+// super key ID and that has the given authentication and unlocked device
+// parameters. This does not actually superencrypt the key blob.
+fn make_superencrypted_key_entry(
+ db: &mut KeystoreDB,
+ namespace: i64,
+ alias: &str,
+ requires_authentication: bool,
+ requires_unlocked_device: bool,
+ super_key_id: i64,
+) -> Result<KeyIdGuard> {
+ let domain = Domain::APP;
+ let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
+
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+ blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+ db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?;
+
+ let mut params = vec![];
+ if requires_unlocked_device {
+ params.push(KeyParameter::new(
+ KeyParameterValue::UnlockedDeviceRequired,
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ));
+ }
+ if requires_authentication {
+ params.push(KeyParameter::new(
+ KeyParameterValue::UserSecureID(42),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ));
+ }
+ db.insert_keyparameter(&key_id, ¶ms)?;
+
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+ db.insert_key_metadata(&key_id, &metadata)?;
+
+ rebind_alias(db, &key_id, alias, domain, namespace)?;
+ Ok(key_id)
+}
+
+fn make_bootlevel_test_key_entry_test_vector(key_id: i64, logical_only: bool) -> KeyEntry {
+ let mut params = make_test_params(None);
+ params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE));
+
+ let mut blob_metadata = BlobMetaData::new();
+ if !logical_only {
+ blob_metadata.add(BlobMetaEntry::MaxBootLevel(3));
+ }
+ blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+
+ KeyEntry {
+ id: key_id,
+ key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
+ cert: Some(TEST_CERT_BLOB.to_vec()),
+ cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
+ km_uuid: KEYSTORE_UUID,
+ parameters: params,
+ metadata,
+ pure_cert: false,
+ }
+}
+
+fn debug_dump_keyentry_table(db: &mut KeystoreDB) -> Result<()> {
+ let mut stmt = db.conn.prepare(
+ "SELECT id, key_type, domain, namespace, alias, state, km_uuid FROM persistent.keyentry;",
+ )?;
+ let rows =
+ stmt.query_map::<(i64, KeyType, i32, i64, String, KeyLifeCycle, Uuid), _, _>([], |row| {
+ Ok((
+ row.get(0)?,
+ row.get(1)?,
+ row.get(2)?,
+ row.get(3)?,
+ row.get(4)?,
+ row.get(5)?,
+ row.get(6)?,
+ ))
+ })?;
+
+ println!("Key entry table rows:");
+ for r in rows {
+ let (id, key_type, domain, namespace, alias, state, km_uuid) = r.unwrap();
+ println!(
+ " id: {} KeyType: {:?} Domain: {} Namespace: {} Alias: {} State: {:?} KmUuid: {:?}",
+ id, key_type, domain, namespace, alias, state, km_uuid
+ );
+ }
+ Ok(())
+}
+
+fn debug_dump_grant_table(db: &mut KeystoreDB) -> Result<()> {
+ let mut stmt =
+ db.conn.prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
+ let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>([], |row| {
+ Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
+ })?;
+
+ println!("Grant table rows:");
+ for r in rows {
+ let (id, gt, ki, av) = r.unwrap();
+ println!(" id: {} grantee: {} key_id: {} access_vector: {}", id, gt, ki, av);
+ }
+ Ok(())
+}
+
+// Use a custom random number generator that repeats each number once.
+// This allows us to test repeated elements.
+
+thread_local! {
+ static RANDOM_COUNTER: RefCell<i64> = const { RefCell::new(0) };
+}
+
+fn reset_random() {
+ RANDOM_COUNTER.with(|counter| {
+ *counter.borrow_mut() = 0;
+ })
+}
+
+pub fn random() -> i64 {
+ RANDOM_COUNTER.with(|counter| {
+ let result = *counter.borrow() / 2;
+ *counter.borrow_mut() += 1;
+ result
+ })
+}
+
+#[test]
+fn test_unbind_keys_for_user() -> Result<()> {
+ let mut db = new_test_db()?;
+ db.unbind_keys_for_user(1)?;
+
+ make_test_key_entry(&mut db, Domain::APP, 210000, TEST_ALIAS, None)?;
+ make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?;
+ db.unbind_keys_for_user(2)?;
+
+ assert_eq!(1, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len());
+ assert_eq!(0, db.list_past_alias(Domain::APP, 210000, KeyType::Client, None)?.len());
+
+ db.unbind_keys_for_user(1)?;
+ assert_eq!(0, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len());
+
+ Ok(())
+}
+
+#[test]
+fn test_unbind_keys_for_user_removes_superkeys() -> Result<()> {
+ let mut db = new_test_db()?;
+ let super_key = keystore2_crypto::generate_aes256_key()?;
+ let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
+ let (encrypted_super_key, metadata) = SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
+
+ let key_name_enc = SuperKeyType {
+ alias: "test_super_key_1",
+ algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
+ name: "test_super_key_1",
+ };
+
+ let key_name_nonenc = SuperKeyType {
+ alias: "test_super_key_2",
+ algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
+ name: "test_super_key_2",
+ };
+
+ // Install two super keys.
+ db.store_super_key(1, &key_name_nonenc, &super_key, &BlobMetaData::new(), &KeyMetaData::new())?;
+ db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?;
+
+ // Check that both can be found in the database.
+ assert!(db.load_super_key(&key_name_enc, 1)?.is_some());
+ assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some());
+
+ // Install the same keys for a different user.
+ db.store_super_key(2, &key_name_nonenc, &super_key, &BlobMetaData::new(), &KeyMetaData::new())?;
+ db.store_super_key(2, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?;
+
+ // Check that the second pair of keys can be found in the database.
+ assert!(db.load_super_key(&key_name_enc, 2)?.is_some());
+ assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some());
+
+ // Delete all keys for user 1.
+ db.unbind_keys_for_user(1)?;
+
+ // All of user 1's keys should be gone.
+ assert!(db.load_super_key(&key_name_enc, 1)?.is_none());
+ assert!(db.load_super_key(&key_name_nonenc, 1)?.is_none());
+
+ // User 2's keys should not have been touched.
+ assert!(db.load_super_key(&key_name_enc, 2)?.is_some());
+ assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some());
+
+ Ok(())
+}
+
+fn app_key_exists(db: &mut KeystoreDB, nspace: i64, alias: &str) -> Result<bool> {
+ db.key_exists(Domain::APP, nspace, alias, KeyType::Client)
+}
+
+// Tests the unbind_auth_bound_keys_for_user() function.
+#[test]
+fn test_unbind_auth_bound_keys_for_user() -> Result<()> {
+ let mut db = new_test_db()?;
+ let user_id = 1;
+ let nspace: i64 = (user_id * AID_USER_OFFSET).into();
+ let other_user_id = 2;
+ let other_user_nspace: i64 = (other_user_id * AID_USER_OFFSET).into();
+ let super_key_type = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY;
+
+ // Create a superencryption key.
+ let super_key = keystore2_crypto::generate_aes256_key()?;
+ let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
+ let (encrypted_super_key, blob_metadata) =
+ SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
+ db.store_super_key(
+ user_id,
+ super_key_type,
+ &encrypted_super_key,
+ &blob_metadata,
+ &KeyMetaData::new(),
+ )?;
+ let super_key_id = db.load_super_key(super_key_type, user_id)?.unwrap().0 .0;
+
+ // Store 4 superencrypted app keys, one for each possible combination of
+ // (authentication required, unlocked device required).
+ make_superencrypted_key_entry(&mut db, nspace, "noauth_noud", false, false, super_key_id)?;
+ make_superencrypted_key_entry(&mut db, nspace, "noauth_ud", false, true, super_key_id)?;
+ make_superencrypted_key_entry(&mut db, nspace, "auth_noud", true, false, super_key_id)?;
+ make_superencrypted_key_entry(&mut db, nspace, "auth_ud", true, true, super_key_id)?;
+ assert!(app_key_exists(&mut db, nspace, "noauth_noud")?);
+ assert!(app_key_exists(&mut db, nspace, "noauth_ud")?);
+ assert!(app_key_exists(&mut db, nspace, "auth_noud")?);
+ assert!(app_key_exists(&mut db, nspace, "auth_ud")?);
+
+ // Also store a key for a different user that requires authentication.
+ make_superencrypted_key_entry(&mut db, other_user_nspace, "auth_ud", true, true, super_key_id)?;
+
+ db.unbind_auth_bound_keys_for_user(user_id)?;
+
+ // Verify that only the user's app keys that require authentication were
+ // deleted. Keys that require an unlocked device but not authentication
+ // should *not* have been deleted, nor should the super key have been
+ // deleted, nor should other users' keys have been deleted.
+ assert!(db.load_super_key(super_key_type, user_id)?.is_some());
+ assert!(app_key_exists(&mut db, nspace, "noauth_noud")?);
+ assert!(app_key_exists(&mut db, nspace, "noauth_ud")?);
+ assert!(!app_key_exists(&mut db, nspace, "auth_noud")?);
+ assert!(!app_key_exists(&mut db, nspace, "auth_ud")?);
+ assert!(app_key_exists(&mut db, other_user_nspace, "auth_ud")?);
+
+ Ok(())
+}
+
+#[test]
+fn test_store_super_key() -> Result<()> {
+ let mut db = new_test_db()?;
+ let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
+ let super_key = keystore2_crypto::generate_aes256_key()?;
+ let secret_bytes = b"keystore2 is great.";
+ let (encrypted_secret, iv, tag) = keystore2_crypto::aes_gcm_encrypt(secret_bytes, &super_key)?;
+
+ let (encrypted_super_key, metadata) = SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
+ db.store_super_key(
+ 1,
+ &USER_AFTER_FIRST_UNLOCK_SUPER_KEY,
+ &encrypted_super_key,
+ &metadata,
+ &KeyMetaData::new(),
+ )?;
+
+ // Check if super key exists.
+ assert!(db.key_exists(
+ Domain::APP,
+ 1,
+ USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias,
+ KeyType::Super
+ )?);
+
+ let (_, key_entry) = db.load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, 1)?.unwrap();
+ let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry(
+ USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm,
+ key_entry,
+ &pw,
+ None,
+ )?;
+
+ let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?;
+ assert_eq!(secret_bytes, &*decrypted_secret_bytes);
+
+ Ok(())
+}
+
+fn get_valid_statsd_storage_types() -> Vec<MetricsStorage> {
+ vec![
+ MetricsStorage::KEY_ENTRY,
+ MetricsStorage::KEY_ENTRY_ID_INDEX,
+ MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX,
+ MetricsStorage::BLOB_ENTRY,
+ MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX,
+ MetricsStorage::KEY_PARAMETER,
+ MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX,
+ MetricsStorage::KEY_METADATA,
+ MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX,
+ MetricsStorage::GRANT,
+ MetricsStorage::AUTH_TOKEN,
+ MetricsStorage::BLOB_METADATA,
+ MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX,
+ ]
+}
+
+/// Perform a simple check to ensure that we can query all the storage types
+/// that are supported by the DB. Check for reasonable values.
+#[test]
+fn test_query_all_valid_table_sizes() -> Result<()> {
+ const PAGE_SIZE: i32 = 4096;
+
+ let mut db = new_test_db()?;
+
+ for t in get_valid_statsd_storage_types() {
+ let stat = db.get_storage_stat(t)?;
+ // AuthToken can be less than a page since it's in a btree, not sqlite
+ // TODO(b/187474736) stop using if-let here
+ if let MetricsStorage::AUTH_TOKEN = t {
+ } else {
+ assert!(stat.size >= PAGE_SIZE);
+ }
+ assert!(stat.size >= stat.unused_size);
+ }
+
+ Ok(())
+}
+
+fn get_storage_stats_map(db: &mut KeystoreDB) -> BTreeMap<i32, StorageStats> {
+ get_valid_statsd_storage_types()
+ .into_iter()
+ .map(|t| (t.0, db.get_storage_stat(t).unwrap()))
+ .collect()
+}
+
+fn assert_storage_increased(
+ db: &mut KeystoreDB,
+ increased_storage_types: Vec<MetricsStorage>,
+ baseline: &mut BTreeMap<i32, StorageStats>,
+) {
+ for storage in increased_storage_types {
+ // Verify the expected storage increased.
+ let new = db.get_storage_stat(storage).unwrap();
+ let old = &baseline[&storage.0];
+ assert!(new.size >= old.size, "{}: {} >= {}", storage.0, new.size, old.size);
+ assert!(
+ new.unused_size <= old.unused_size,
+ "{}: {} <= {}",
+ storage.0,
+ new.unused_size,
+ old.unused_size
+ );
+
+ // Update the baseline with the new value so that it succeeds in the
+ // later comparison.
+ baseline.insert(storage.0, new);
+ }
+
+ // Get an updated map of the storage and verify there were no unexpected changes.
+ let updated_stats = get_storage_stats_map(db);
+ assert_eq!(updated_stats.len(), baseline.len());
+
+ for &k in baseline.keys() {
+ let stringify = |map: &BTreeMap<i32, StorageStats>| -> String {
+ let mut s = String::new();
+ for &k in map.keys() {
+ writeln!(&mut s, " {}: {}, {}", &k, map[&k].size, map[&k].unused_size)
+ .expect("string concat failed");
+ }
+ s
+ };
+
+ assert!(
+ updated_stats[&k].size == baseline[&k].size
+ && updated_stats[&k].unused_size == baseline[&k].unused_size,
+ "updated_stats:\n{}\nbaseline:\n{}",
+ stringify(&updated_stats),
+ stringify(baseline)
+ );
+ }
+}
+
+#[test]
+fn test_verify_key_table_size_reporting() -> Result<()> {
+ let mut db = new_test_db()?;
+ let mut working_stats = get_storage_stats_map(&mut db);
+
+ let key_id = create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
+ assert_storage_increased(
+ &mut db,
+ vec![
+ MetricsStorage::KEY_ENTRY,
+ MetricsStorage::KEY_ENTRY_ID_INDEX,
+ MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX,
+ ],
+ &mut working_stats,
+ );
+
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+ db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), None)?;
+ assert_storage_increased(
+ &mut db,
+ vec![
+ MetricsStorage::BLOB_ENTRY,
+ MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX,
+ MetricsStorage::BLOB_METADATA,
+ MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX,
+ ],
+ &mut working_stats,
+ );
+
+ let params = make_test_params(None);
+ db.insert_keyparameter(&key_id, ¶ms)?;
+ assert_storage_increased(
+ &mut db,
+ vec![MetricsStorage::KEY_PARAMETER, MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX],
+ &mut working_stats,
+ );
+
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+ db.insert_key_metadata(&key_id, &metadata)?;
+ assert_storage_increased(
+ &mut db,
+ vec![MetricsStorage::KEY_METADATA, MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX],
+ &mut working_stats,
+ );
+
+ let mut sum = 0;
+ for stat in working_stats.values() {
+ sum += stat.size;
+ }
+ let total = db.get_storage_stat(MetricsStorage::DATABASE)?.size;
+ assert!(sum <= total, "Expected sum <= total. sum: {}, total: {}", sum, total);
+
+ Ok(())
+}
+
+#[test]
+fn test_verify_auth_table_size_reporting() -> Result<()> {
+ let mut db = new_test_db()?;
+ let mut working_stats = get_storage_stats_map(&mut db);
+ db.insert_auth_token(&HardwareAuthToken {
+ challenge: 123,
+ userId: 456,
+ authenticatorId: 789,
+ authenticatorType: kmhw_authenticator_type::ANY,
+ timestamp: Timestamp { milliSeconds: 10 },
+ mac: b"mac".to_vec(),
+ });
+ assert_storage_increased(&mut db, vec![MetricsStorage::AUTH_TOKEN], &mut working_stats);
+ Ok(())
+}
+
+#[test]
+fn test_verify_grant_table_size_reporting() -> Result<()> {
+ const OWNER: i64 = 1;
+ let mut db = new_test_db()?;
+ make_test_key_entry(&mut db, Domain::APP, OWNER, TEST_ALIAS, None)?;
+
+ let mut working_stats = get_storage_stats_map(&mut db);
+ db.grant(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ OWNER as u32,
+ 123,
+ key_perm_set![KeyPerm::Use],
+ |_, _| Ok(()),
+ )?;
+
+ assert_storage_increased(&mut db, vec![MetricsStorage::GRANT], &mut working_stats);
+
+ Ok(())
+}
+
+#[test]
+fn find_auth_token_entry_returns_latest() -> Result<()> {
+ let mut db = new_test_db()?;
+ db.insert_auth_token(&HardwareAuthToken {
+ challenge: 123,
+ userId: 456,
+ authenticatorId: 789,
+ authenticatorType: kmhw_authenticator_type::ANY,
+ timestamp: Timestamp { milliSeconds: 10 },
+ mac: b"mac0".to_vec(),
+ });
+ std::thread::sleep(std::time::Duration::from_millis(1));
+ db.insert_auth_token(&HardwareAuthToken {
+ challenge: 123,
+ userId: 457,
+ authenticatorId: 789,
+ authenticatorType: kmhw_authenticator_type::ANY,
+ timestamp: Timestamp { milliSeconds: 12 },
+ mac: b"mac1".to_vec(),
+ });
+ std::thread::sleep(std::time::Duration::from_millis(1));
+ db.insert_auth_token(&HardwareAuthToken {
+ challenge: 123,
+ userId: 458,
+ authenticatorId: 789,
+ authenticatorType: kmhw_authenticator_type::ANY,
+ timestamp: Timestamp { milliSeconds: 3 },
+ mac: b"mac2".to_vec(),
+ });
+ // All three entries are in the database
+ assert_eq!(db.perboot.auth_tokens_len(), 3);
+ // It selected the most recent timestamp
+ assert_eq!(db.find_auth_token_entry(|_| true).unwrap().auth_token.mac, b"mac2".to_vec());
+ Ok(())
+}
+
+fn blob_count(db: &mut KeystoreDB, sc_type: SubComponentType) -> usize {
+ db.with_transaction(TransactionBehavior::Deferred, |tx| {
+ tx.query_row(
+ "SELECT COUNT(*) FROM persistent.blobentry
+ WHERE subcomponent_type = ?;",
+ params![sc_type],
+ |row| row.get(0),
+ )
+ .context(ks_err!("Failed to count number of {sc_type:?} blobs"))
+ .no_gc()
+ })
+ .unwrap()
+}
+
+fn blob_count_in_state(db: &mut KeystoreDB, sc_type: SubComponentType, state: BlobState) -> usize {
+ db.with_transaction(TransactionBehavior::Deferred, |tx| {
+ tx.query_row(
+ "SELECT COUNT(*) FROM persistent.blobentry
+ WHERE subcomponent_type = ? AND state = ?;",
+ params![sc_type, state],
+ |row| row.get(0),
+ )
+ .context(ks_err!("Failed to count number of {sc_type:?} blobs"))
+ .no_gc()
+ })
+ .unwrap()
+}
+
+#[test]
+fn test_blobentry_gc() -> Result<()> {
+ let mut db = new_test_db()?;
+ let _key_id1 = make_test_key_entry(&mut db, Domain::APP, 1, "key1", None)?.0;
+ let key_guard2 = make_test_key_entry(&mut db, Domain::APP, 2, "key2", None)?;
+ let key_guard3 = make_test_key_entry(&mut db, Domain::APP, 3, "key3", None)?;
+ let key_id4 = make_test_key_entry(&mut db, Domain::APP, 4, "key4", None)?.0;
+ let key_id5 = make_test_key_entry(&mut db, Domain::APP, 5, "key5", None)?.0;
+
+ assert_eq!(5, blob_count(&mut db, SubComponentType::KEY_BLOB));
+ assert_eq!(5, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+ assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+ assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+ // Replace the keyblobs for keys 2 and 3. The previous blobs will still exist.
+ db.set_blob(&key_guard2, SubComponentType::KEY_BLOB, Some(&[1, 2, 3]), None)?;
+ db.set_blob(&key_guard3, SubComponentType::KEY_BLOB, Some(&[1, 2, 3]), None)?;
+
+ assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+ assert_eq!(5, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+ assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+ assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+ // Delete keys 4 and 5. The keyblobs aren't removed yet.
+ db.with_transaction(Immediate("TX_delete_test_keys"), |tx| {
+ KeystoreDB::mark_unreferenced(tx, key_id4)?;
+ KeystoreDB::mark_unreferenced(tx, key_id5)?;
+ Ok(()).no_gc()
+ })
+ .unwrap();
+
+ assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+ assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+ assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+ assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+ // First garbage collection should return all 4 blobentry rows that are no longer current for
+ // their key.
+ let superseded = db.handle_next_superseded_blobs(&[], 20).unwrap();
+ let superseded_ids: Vec<i64> = superseded.iter().map(|v| v.blob_id).collect();
+ assert_eq!(4, superseded.len());
+ assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+ assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+ assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+ assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+ // Feed the superseded blob IDs back in, to trigger removal of the old KEY_BLOB entries. As no
+ // new superseded KEY_BLOBs are found, the unreferenced CERT/CERT_CHAIN blobs are removed.
+ let superseded = db.handle_next_superseded_blobs(&superseded_ids, 20).unwrap();
+ let superseded_ids: Vec<i64> = superseded.iter().map(|v| v.blob_id).collect();
+ assert_eq!(0, superseded.len());
+ assert_eq!(3, blob_count(&mut db, SubComponentType::KEY_BLOB));
+ assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+ assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+ assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+ assert_eq!(3, blob_count(&mut db, SubComponentType::CERT));
+ assert_eq!(3, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+ // Nothing left to garbage collect.
+ let superseded = db.handle_next_superseded_blobs(&superseded_ids, 20).unwrap();
+ assert_eq!(0, superseded.len());
+ assert_eq!(3, blob_count(&mut db, SubComponentType::KEY_BLOB));
+ assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+ assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+ assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+ assert_eq!(3, blob_count(&mut db, SubComponentType::CERT));
+ assert_eq!(3, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+ Ok(())
+}
+
+#[test]
+fn test_upgrade_1_to_2() -> Result<()> {
+ let mut db = new_test_db()?;
+ let _key_id1 = make_test_key_entry(&mut db, Domain::APP, 1, "key1", None)?.0;
+ let key_guard2 = make_test_key_entry(&mut db, Domain::APP, 2, "key2", None)?;
+ let key_guard3 = make_test_key_entry(&mut db, Domain::APP, 3, "key3", None)?;
+ let key_id4 = make_test_key_entry(&mut db, Domain::APP, 4, "key4", None)?.0;
+ let key_id5 = make_test_key_entry(&mut db, Domain::APP, 5, "key5", None)?.0;
+
+ // Replace the keyblobs for keys 2 and 3. The previous blobs will still exist.
+ db.set_blob(&key_guard2, SubComponentType::KEY_BLOB, Some(&[1, 2, 3]), None)?;
+ db.set_blob(&key_guard3, SubComponentType::KEY_BLOB, Some(&[1, 2, 3]), None)?;
+
+ // Delete keys 4 and 5. The keyblobs aren't removed yet.
+ db.with_transaction(Immediate("TX_delete_test_keys"), |tx| {
+ KeystoreDB::mark_unreferenced(tx, key_id4)?;
+ KeystoreDB::mark_unreferenced(tx, key_id5)?;
+ Ok(()).no_gc()
+ })
+ .unwrap();
+ assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+ // Manually downgrade the database to the v1 schema.
+ db.with_transaction(Immediate("TX_downgrade_2_to_1"), |tx| {
+ tx.execute("DROP INDEX persistent.keyentry_state_index;", params!())?;
+ tx.execute("DROP INDEX persistent.blobentry_state_index;", params!())?;
+ tx.execute("ALTER TABLE persistent.blobentry DROP COLUMN state;", params!())?;
+ Ok(()).no_gc()
+ })?;
+
+ // Run the upgrade process.
+ let version = db.with_transaction(Immediate("TX_upgrade_1_to_2"), |tx| {
+ KeystoreDB::from_1_to_2(tx).no_gc()
+ })?;
+ assert_eq!(version, 2);
+
+ // Check blobs have acquired the right `state` values.
+ assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+ assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+ assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+ assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+ assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+ Ok(())
+}
+
+#[test]
+fn test_load_key_descriptor() -> Result<()> {
+ let mut db = new_test_db()?;
+ let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)?.0;
+
+ let key = db.load_key_descriptor(key_id)?.unwrap();
+
+ assert_eq!(key.domain, Domain::APP);
+ assert_eq!(key.nspace, 1);
+ assert_eq!(key.alias, Some(TEST_ALIAS.to_string()));
+
+ // No such id
+ assert_eq!(db.load_key_descriptor(key_id + 1)?, None);
+ Ok(())
+}
+
+#[test]
+fn test_get_list_app_uids_for_sid() -> Result<()> {
+ let uid: i32 = 1;
+ let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64);
+ let first_sid = 667;
+ let second_sid = 669;
+ let first_app_id: i64 = 123 + uid_offset;
+ let second_app_id: i64 = 456 + uid_offset;
+ let third_app_id: i64 = 789 + uid_offset;
+ let unrelated_app_id: i64 = 1011 + uid_offset;
+ let mut db = new_test_db()?;
+ make_test_key_entry_with_sids(
+ &mut db,
+ Domain::APP,
+ first_app_id,
+ TEST_ALIAS,
+ None,
+ &[first_sid],
+ )
+ .context("test_get_list_app_uids_for_sid")?;
+ make_test_key_entry_with_sids(
+ &mut db,
+ Domain::APP,
+ second_app_id,
+ "alias2",
+ None,
+ &[first_sid],
+ )
+ .context("test_get_list_app_uids_for_sid")?;
+ make_test_key_entry_with_sids(
+ &mut db,
+ Domain::APP,
+ second_app_id,
+ TEST_ALIAS,
+ None,
+ &[second_sid],
+ )
+ .context("test_get_list_app_uids_for_sid")?;
+ make_test_key_entry_with_sids(
+ &mut db,
+ Domain::APP,
+ third_app_id,
+ "alias3",
+ None,
+ &[second_sid],
+ )
+ .context("test_get_list_app_uids_for_sid")?;
+ make_test_key_entry_with_sids(&mut db, Domain::APP, unrelated_app_id, TEST_ALIAS, None, &[])
+ .context("test_get_list_app_uids_for_sid")?;
+
+ let mut first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?;
+ first_sid_apps.sort();
+ assert_eq!(first_sid_apps, vec![first_app_id, second_app_id]);
+ let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?;
+ second_sid_apps.sort();
+ assert_eq!(second_sid_apps, vec![second_app_id, third_app_id]);
+ Ok(())
+}
+
+#[test]
+fn test_get_list_app_uids_with_multiple_sids() -> Result<()> {
+ let uid: i32 = 1;
+ let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64);
+ let first_sid = 667;
+ let second_sid = 669;
+ let third_sid = 772;
+ let first_app_id: i64 = 123 + uid_offset;
+ let second_app_id: i64 = 456 + uid_offset;
+ let mut db = new_test_db()?;
+ make_test_key_entry_with_sids(
+ &mut db,
+ Domain::APP,
+ first_app_id,
+ TEST_ALIAS,
+ None,
+ &[first_sid, second_sid],
+ )
+ .context("test_get_list_app_uids_for_sid")?;
+ make_test_key_entry_with_sids(
+ &mut db,
+ Domain::APP,
+ second_app_id,
+ "alias2",
+ None,
+ &[second_sid, third_sid],
+ )
+ .context("test_get_list_app_uids_for_sid")?;
+
+ let first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?;
+ assert_eq!(first_sid_apps, vec![first_app_id]);
+
+ let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?;
+ second_sid_apps.sort();
+ assert_eq!(second_sid_apps, vec![first_app_id, second_app_id]);
+
+ let third_sid_apps = db.get_app_uids_affected_by_sid(uid, third_sid)?;
+ assert_eq!(third_sid_apps, vec![second_app_id]);
+ Ok(())
+}
+
+// Starting from `next_keyid`, add keys to the database until the count reaches
+// `key_count`. (`next_keyid` is assumed to indicate how many rows already exist.)
+fn db_populate_keys(db: &mut KeystoreDB, next_keyid: usize, key_count: usize) {
+ db.with_transaction(Immediate("test_keyentry"), |tx| {
+ for next_keyid in next_keyid..key_count {
+ tx.execute(
+ "INSERT into persistent.keyentry
+ (id, key_type, domain, namespace, alias, state, km_uuid)
+ VALUES(?, ?, ?, ?, ?, ?, ?);",
+ params![
+ next_keyid,
+ KeyType::Client,
+ Domain::APP.0 as u32,
+ 10001,
+ &format!("alias-{next_keyid}"),
+ KeyLifeCycle::Live,
+ KEYSTORE_UUID,
+ ],
+ )?;
+ tx.execute(
+ "INSERT INTO persistent.blobentry
+ (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
+ params![SubComponentType::KEY_BLOB, next_keyid, TEST_KEY_BLOB],
+ )?;
+ tx.execute(
+ "INSERT INTO persistent.blobentry
+ (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
+ params![SubComponentType::CERT, next_keyid, TEST_CERT_BLOB],
+ )?;
+ tx.execute(
+ "INSERT INTO persistent.blobentry
+ (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
+ params![SubComponentType::CERT_CHAIN, next_keyid, TEST_CERT_CHAIN_BLOB],
+ )?;
+ }
+ Ok(()).no_gc()
+ })
+ .unwrap()
+}
+
+/// Run the provided `test_fn` against the database at various increasing stages of
+/// database population.
+fn run_with_many_keys<F, T>(max_count: usize, test_fn: F) -> Result<()>
+where
+ F: Fn(&mut KeystoreDB) -> T,
+{
+ prep_and_run_with_many_keys(max_count, |_db| (), test_fn)
+}
+
+/// Run the provided `test_fn` against the database at various increasing stages of
+/// database population.
+fn prep_and_run_with_many_keys<F, T, P>(max_count: usize, prep_fn: P, test_fn: F) -> Result<()>
+where
+ F: Fn(&mut KeystoreDB) -> T,
+ P: Fn(&mut KeystoreDB),
+{
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_test")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+ // Put the test database on disk for a more realistic result.
+ let db_root = tempfile::Builder::new().prefix("ks2db-test-").tempdir().unwrap();
+ let mut db_path = db_root.path().to_owned();
+ db_path.push("ks2-test.sqlite");
+ let mut db = new_test_db_at(&db_path.to_string_lossy())?;
+
+ println!("\nNumber_of_keys,time_in_s");
+ let mut key_count = 10;
+ let mut next_keyid = 0;
+ while key_count < max_count {
+ db_populate_keys(&mut db, next_keyid, key_count);
+ assert_eq!(db_key_count(&mut db), key_count);
+
+ // Perform any test-specific preparation
+ prep_fn(&mut db);
+
+ // Time execution of the test function.
+ let start = std::time::Instant::now();
+ let _result = test_fn(&mut db);
+ println!("{key_count}, {}", start.elapsed().as_secs_f64());
+
+ next_keyid = key_count;
+ key_count *= 2;
+ }
+
+ Ok(())
+}
+
+fn db_key_count(db: &mut KeystoreDB) -> usize {
+ db.with_transaction(TransactionBehavior::Deferred, |tx| {
+ tx.query_row(
+ "SELECT COUNT(*) FROM persistent.keyentry
+ WHERE domain = ? AND state = ? AND key_type = ?;",
+ params![Domain::APP.0 as u32, KeyLifeCycle::Live, KeyType::Client],
+ |row| row.get::<usize, usize>(0),
+ )
+ .context(ks_err!("Failed to count number of keys."))
+ .no_gc()
+ })
+ .unwrap()
+}
+
+#[test]
+fn test_handle_superseded_with_many_keys() -> Result<()> {
+ run_with_many_keys(1_000_000, |db| db.handle_next_superseded_blobs(&[], 20))
+}
+
+#[test]
+fn test_get_storage_stats_with_many_keys() -> Result<()> {
+ use android_security_metrics::aidl::android::security::metrics::Storage::Storage as MetricsStorage;
+ run_with_many_keys(1_000_000, |db| {
+ db.get_storage_stat(MetricsStorage::DATABASE).unwrap();
+ db.get_storage_stat(MetricsStorage::KEY_ENTRY).unwrap();
+ db.get_storage_stat(MetricsStorage::KEY_ENTRY_ID_INDEX).unwrap();
+ db.get_storage_stat(MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX).unwrap();
+ db.get_storage_stat(MetricsStorage::BLOB_ENTRY).unwrap();
+ db.get_storage_stat(MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX).unwrap();
+ db.get_storage_stat(MetricsStorage::KEY_PARAMETER).unwrap();
+ db.get_storage_stat(MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX).unwrap();
+ db.get_storage_stat(MetricsStorage::KEY_METADATA).unwrap();
+ db.get_storage_stat(MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX).unwrap();
+ db.get_storage_stat(MetricsStorage::GRANT).unwrap();
+ db.get_storage_stat(MetricsStorage::AUTH_TOKEN).unwrap();
+ db.get_storage_stat(MetricsStorage::BLOB_METADATA).unwrap();
+ db.get_storage_stat(MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX).unwrap();
+ })
+}
+
+#[test]
+fn test_list_keys_with_many_keys() -> Result<()> {
+ run_with_many_keys(1_000_000, |db: &mut KeystoreDB| -> Result<()> {
+ // Behave equivalently to how clients list aliases.
+ let domain = Domain::APP;
+ let namespace = 10001;
+ let mut start_past: Option<String> = None;
+ let mut count = 0;
+ let mut batches = 0;
+ loop {
+ let keys = db
+ .list_past_alias(domain, namespace, KeyType::Client, start_past.as_deref())
+ .unwrap();
+ let batch_size = crate::utils::estimate_safe_amount_to_return(
+ domain,
+ namespace,
+ None,
+ &keys,
+ crate::utils::RESPONSE_SIZE_LIMIT,
+ );
+ let batch = &keys[..batch_size];
+ count += batch.len();
+ match batch.last() {
+ Some(key) => start_past.clone_from(&key.alias),
+ None => {
+ log::info!("got {count} keys in {batches} non-empty batches");
+ return Ok(());
+ }
+ }
+ batches += 1;
+ }
+ })
+}
+
+#[test]
+fn test_upgrade_1_to_2_with_many_keys() -> Result<()> {
+ prep_and_run_with_many_keys(
+ 1_000_000,
+ |db: &mut KeystoreDB| {
+ // Manually downgrade the database to the v1 schema.
+ db.with_transaction(Immediate("TX_downgrade_2_to_1"), |tx| {
+ tx.execute("DROP INDEX persistent.keyentry_state_index;", params!())?;
+ tx.execute("DROP INDEX persistent.blobentry_state_index;", params!())?;
+ tx.execute("ALTER TABLE persistent.blobentry DROP COLUMN state;", params!())?;
+ Ok(()).no_gc()
+ })
+ .unwrap();
+ },
+ |db: &mut KeystoreDB| -> Result<()> {
+ // Run the upgrade process.
+ db.with_transaction(Immediate("TX_upgrade_1_to_2"), |tx| {
+ KeystoreDB::from_1_to_2(tx).no_gc()
+ })?;
+ Ok(())
+ },
+ )
+}
diff --git a/keystore2/src/database/versioning.rs b/keystore2/src/database/versioning.rs
index 2c816f4..a047cf3 100644
--- a/keystore2/src/database/versioning.rs
+++ b/keystore2/src/database/versioning.rs
@@ -15,7 +15,7 @@
use anyhow::{anyhow, Context, Result};
use rusqlite::{params, OptionalExtension, Transaction};
-pub fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32> {
+fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32> {
tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.version (
id INTEGER PRIMARY KEY,
@@ -61,7 +61,7 @@
Ok(version)
}
-pub fn update_version(tx: &Transaction, new_version: u32) -> Result<()> {
+pub(crate) fn update_version(tx: &Transaction, new_version: u32) -> Result<()> {
let updated = tx
.execute("UPDATE persistent.version SET version = ? WHERE id = 0;", params![new_version])
.context("In update_version: Failed to update row.")?;
@@ -82,9 +82,11 @@
let mut db_version = create_or_get_version(tx, current_version)
.context("In upgrade_database: Failed to get database version.")?;
while db_version < current_version {
+ log::info!("Current DB version={db_version}, perform upgrade");
db_version = upgraders[db_version as usize](tx).with_context(|| {
format!("In upgrade_database: Trying to upgrade from db version {}.", db_version)
})?;
+ log::info!("DB upgrade successful, current DB version now={db_version}");
}
update_version(tx, db_version).context("In upgrade_database.")
}
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 7038323..d086dd2 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -545,8 +545,9 @@
|| (user_auth_type.is_none() && !user_secure_ids.is_empty())
{
return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!(
- "Auth required, but either auth type or secure ids \
- are not present."
+ "Auth required, but auth type {:?} + sids {:?} inconsistently specified",
+ user_auth_type,
+ user_secure_ids,
));
}
@@ -582,17 +583,36 @@
None => false, // not reachable due to earlier check
})
.ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED))
- .context(ks_err!("No suitable auth token found."))?;
+ .context(ks_err!(
+ "No suitable auth token for sids {:?} type {:?} received in last {}s found.",
+ user_secure_ids,
+ user_auth_type,
+ key_time_out
+ ))?;
let now = BootTime::now();
let token_age =
now.checked_sub(&hat.time_received()).ok_or_else(Error::sys).context(ks_err!(
- "Overflow while computing Auth token validity. \
- Validity cannot be established."
+ "Overflow while computing Auth token validity. Validity cannot be established."
))?;
if token_age.seconds() > key_time_out {
- return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED))
- .context(ks_err!("matching auth token is expired."));
+ return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!(
+ concat!(
+ "matching auth token (challenge={}, userId={}, authId={}, ",
+ "authType={:#x}, timestamp={}ms) rcved={:?} ",
+ "for sids {:?} type {:?} is expired ({}s old > timeout={}s)"
+ ),
+ hat.auth_token().challenge,
+ hat.auth_token().userId,
+ hat.auth_token().authenticatorId,
+ hat.auth_token().authenticatorType.0,
+ hat.auth_token().timestamp.milliSeconds,
+ hat.time_received(),
+ user_secure_ids,
+ user_auth_type,
+ token_age.seconds(),
+ key_time_out
+ ));
}
let state = if requires_timestamp {
DeferredAuthState::TimeStampRequired(hat.auth_token().clone())
@@ -633,16 +653,12 @@
/// Check if the device is locked for the given user. If there's no entry yet for the user,
/// we assume that the device is locked
fn is_device_locked(&self, user_id: i32) -> bool {
- // unwrap here because there's no way this mutex guard can be poisoned and
- // because there's no way to recover, even if it is poisoned.
let set = self.device_unlocked_set.lock().unwrap();
!set.contains(&user_id)
}
/// Sets the device locked status for the user. This method is called externally.
pub fn set_device_locked(&self, user_id: i32, device_locked_status: bool) {
- // unwrap here because there's no way this mutex guard can be poisoned and
- // because there's no way to recover, even if it is poisoned.
let mut set = self.device_unlocked_set.lock().unwrap();
if device_locked_status {
set.remove(&user_id);
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index cea4d6b..d57ba0c 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -34,10 +34,14 @@
ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode,
};
use keystore2_selinux as selinux;
+use postprocessor_client::Error as PostProcessorError;
use rkpd_client::Error as RkpdError;
use std::cmp::PartialEq;
use std::ffi::CString;
+#[cfg(test)]
+pub mod tests;
+
/// This is the main Keystore error type. It wraps the Keystore `ResponseCode` generated
/// from AIDL in the `Rc` variant and Keymint `ErrorCode` in the Km variant.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
@@ -100,6 +104,14 @@
}
}
+impl From<PostProcessorError> for Error {
+ fn from(e: PostProcessorError) -> Self {
+ match e {
+ PostProcessorError(s) => Error::BinderTransaction(s),
+ }
+ }
+}
+
/// Maps an `rkpd_client::Error` that is wrapped with an `anyhow::Error` to a keystore2 `Error`.
pub fn wrapped_rkpd_error_to_ks_error(e: &anyhow::Error) -> Error {
match e.downcast_ref::<RkpdError>() {
@@ -232,210 +244,3 @@
},
}
}
-
-#[cfg(test)]
-pub mod tests {
-
- use super::*;
- use android_system_keystore2::binder::{
- ExceptionCode, Result as BinderResult, Status as BinderStatus,
- };
- use anyhow::{anyhow, Context};
-
- fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
- Err(anyhow!(Error::Rc(rc))).context("nested nested rc")
- }
-
- fn nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
- nested_nested_rc(rc).context("nested rc")
- }
-
- fn nested_nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
- Err(anyhow!(Error::Km(ec))).context("nested nested ec")
- }
-
- fn nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
- nested_nested_ec(ec).context("nested ec")
- }
-
- fn nested_nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
- Ok(rc)
- }
-
- fn nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
- nested_nested_ok(rc).context("nested ok")
- }
-
- fn nested_nested_selinux_perm() -> anyhow::Result<()> {
- Err(anyhow!(selinux::Error::perm())).context("nested nexted selinux permission denied")
- }
-
- fn nested_selinux_perm() -> anyhow::Result<()> {
- nested_nested_selinux_perm().context("nested selinux permission denied")
- }
-
- #[derive(Debug, thiserror::Error)]
- enum TestError {
- #[error("TestError::Fail")]
- Fail = 0,
- }
-
- fn nested_nested_other_error() -> anyhow::Result<()> {
- Err(anyhow!(TestError::Fail)).context("nested nested other error")
- }
-
- fn nested_other_error() -> anyhow::Result<()> {
- nested_nested_other_error().context("nested other error")
- }
-
- fn binder_sse_error(sse: i32) -> BinderResult<()> {
- Err(BinderStatus::new_service_specific_error(sse, None))
- }
-
- fn binder_exception(ex: ExceptionCode) -> BinderResult<()> {
- Err(BinderStatus::new_exception(ex, None))
- }
-
- #[test]
- fn keystore_error_test() -> anyhow::Result<(), String> {
- android_logger::init_once(
- android_logger::Config::default()
- .with_tag("keystore_error_tests")
- .with_max_level(log::LevelFilter::Debug),
- );
- // All Error::Rc(x) get mapped on a service specific error
- // code of x.
- for rc in ResponseCode::LOCKED.0..ResponseCode::BACKEND_BUSY.0 {
- assert_eq!(
- Result::<(), i32>::Err(rc),
- nested_rc(ResponseCode(rc))
- .map_err(into_logged_binder)
- .map_err(|s| s.service_specific_error())
- );
- }
-
- // All Keystore Error::Km(x) get mapped on a service
- // specific error of x.
- for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
- assert_eq!(
- Result::<(), i32>::Err(ec),
- nested_ec(ErrorCode(ec))
- .map_err(into_logged_binder)
- .map_err(|s| s.service_specific_error())
- );
- }
-
- // All Keymint errors x received through a Binder Result get mapped on
- // a service specific error of x.
- for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
- assert_eq!(
- Result::<(), i32>::Err(ec),
- map_km_error(binder_sse_error(ec))
- .with_context(|| format!("Km error code: {}.", ec))
- .map_err(into_logged_binder)
- .map_err(|s| s.service_specific_error())
- );
- }
-
- // map_km_error creates an Error::Binder variant storing
- // ExceptionCode::SERVICE_SPECIFIC and the given
- // service specific error.
- let sse = map_km_error(binder_sse_error(1));
- assert_eq!(Err(Error::Binder(ExceptionCode::SERVICE_SPECIFIC, 1)), sse);
- // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
- assert_eq!(
- Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
- sse.context("Non negative service specific error.")
- .map_err(into_logged_binder)
- .map_err(|s| ResponseCode(s.service_specific_error()))
- );
-
- // map_km_error creates a Error::Binder variant storing the given exception code.
- let binder_exception = map_km_error(binder_exception(ExceptionCode::TRANSACTION_FAILED));
- assert_eq!(Err(Error::Binder(ExceptionCode::TRANSACTION_FAILED, 0)), binder_exception);
- // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
- assert_eq!(
- Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
- binder_exception
- .context("Binder Exception.")
- .map_err(into_logged_binder)
- .map_err(|s| ResponseCode(s.service_specific_error()))
- );
-
- // selinux::Error::Perm() needs to be mapped to ResponseCode::PERMISSION_DENIED
- assert_eq!(
- Result::<(), ResponseCode>::Err(ResponseCode::PERMISSION_DENIED),
- nested_selinux_perm()
- .map_err(into_logged_binder)
- .map_err(|s| ResponseCode(s.service_specific_error()))
- );
-
- // All other errors get mapped on System Error.
- assert_eq!(
- Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
- nested_other_error()
- .map_err(into_logged_binder)
- .map_err(|s| ResponseCode(s.service_specific_error()))
- );
-
- // Result::Ok variants get passed to the ok handler.
- assert_eq!(
- Ok(ResponseCode::LOCKED),
- nested_ok(ResponseCode::LOCKED).map_err(into_logged_binder)
- );
- assert_eq!(
- Ok(ResponseCode::SYSTEM_ERROR),
- nested_ok(ResponseCode::SYSTEM_ERROR).map_err(into_logged_binder)
- );
-
- Ok(())
- }
-
- //Helper function to test whether error cases are handled as expected.
- pub fn check_result_contains_error_string<T>(
- result: anyhow::Result<T>,
- expected_error_string: &str,
- ) {
- let error_str = format!(
- "{:#?}",
- result.err().unwrap_or_else(|| panic!("Expected the error: {}", expected_error_string))
- );
- assert!(
- error_str.contains(expected_error_string),
- "The string \"{}\" should contain \"{}\"",
- error_str,
- expected_error_string
- );
- }
-
- #[test]
- fn rkpd_error_is_in_sync_with_response_code() {
- let error_mapping = [
- (RkpdError::RequestCancelled, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
- (RkpdError::GetRegistrationFailed, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
- (
- RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_UNKNOWN),
- ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR,
- ),
- (
- RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PERMANENT),
- ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR,
- ),
- (
- RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY),
- ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY,
- ),
- (
- RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH),
- ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE,
- ),
- (RkpdError::StoreUpgradedKeyFailed, ResponseCode::SYSTEM_ERROR),
- (RkpdError::RetryableTimeout, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
- (RkpdError::Timeout, ResponseCode::SYSTEM_ERROR),
- ];
- for (rkpd_error, expected_response_code) in error_mapping {
- let e: Error = rkpd_error.into();
- assert_eq!(e, Error::Rc(expected_response_code));
- }
- }
-} // mod tests
diff --git a/keystore2/src/error/tests.rs b/keystore2/src/error/tests.rs
new file mode 100644
index 0000000..d50091b
--- /dev/null
+++ b/keystore2/src/error/tests.rs
@@ -0,0 +1,218 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Error handling tests.
+
+use super::*;
+use android_system_keystore2::binder::{
+ ExceptionCode, Result as BinderResult, Status as BinderStatus,
+};
+use anyhow::{anyhow, Context};
+
+fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
+ Err(anyhow!(Error::Rc(rc))).context("nested nested rc")
+}
+
+fn nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
+ nested_nested_rc(rc).context("nested rc")
+}
+
+fn nested_nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
+ Err(anyhow!(Error::Km(ec))).context("nested nested ec")
+}
+
+fn nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
+ nested_nested_ec(ec).context("nested ec")
+}
+
+fn nested_nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
+ Ok(rc)
+}
+
+fn nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
+ nested_nested_ok(rc).context("nested ok")
+}
+
+fn nested_nested_selinux_perm() -> anyhow::Result<()> {
+ Err(anyhow!(selinux::Error::perm())).context("nested nexted selinux permission denied")
+}
+
+fn nested_selinux_perm() -> anyhow::Result<()> {
+ nested_nested_selinux_perm().context("nested selinux permission denied")
+}
+
+#[derive(Debug, thiserror::Error)]
+enum TestError {
+ #[error("TestError::Fail")]
+ Fail = 0,
+}
+
+fn nested_nested_other_error() -> anyhow::Result<()> {
+ Err(anyhow!(TestError::Fail)).context("nested nested other error")
+}
+
+fn nested_other_error() -> anyhow::Result<()> {
+ nested_nested_other_error().context("nested other error")
+}
+
+fn binder_sse_error(sse: i32) -> BinderResult<()> {
+ Err(BinderStatus::new_service_specific_error(sse, None))
+}
+
+fn binder_exception(ex: ExceptionCode) -> BinderResult<()> {
+ Err(BinderStatus::new_exception(ex, None))
+}
+
+#[test]
+fn keystore_error_test() -> anyhow::Result<(), String> {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore_error_tests")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+ // All Error::Rc(x) get mapped on a service specific error
+ // code of x.
+ for rc in ResponseCode::LOCKED.0..ResponseCode::BACKEND_BUSY.0 {
+ assert_eq!(
+ Result::<(), i32>::Err(rc),
+ nested_rc(ResponseCode(rc))
+ .map_err(into_logged_binder)
+ .map_err(|s| s.service_specific_error())
+ );
+ }
+
+ // All Keystore Error::Km(x) get mapped on a service
+ // specific error of x.
+ for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
+ assert_eq!(
+ Result::<(), i32>::Err(ec),
+ nested_ec(ErrorCode(ec))
+ .map_err(into_logged_binder)
+ .map_err(|s| s.service_specific_error())
+ );
+ }
+
+ // All Keymint errors x received through a Binder Result get mapped on
+ // a service specific error of x.
+ for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
+ assert_eq!(
+ Result::<(), i32>::Err(ec),
+ map_km_error(binder_sse_error(ec))
+ .with_context(|| format!("Km error code: {}.", ec))
+ .map_err(into_logged_binder)
+ .map_err(|s| s.service_specific_error())
+ );
+ }
+
+ // map_km_error creates an Error::Binder variant storing
+ // ExceptionCode::SERVICE_SPECIFIC and the given
+ // service specific error.
+ let sse = map_km_error(binder_sse_error(1));
+ assert_eq!(Err(Error::Binder(ExceptionCode::SERVICE_SPECIFIC, 1)), sse);
+ // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
+ assert_eq!(
+ Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
+ sse.context("Non negative service specific error.")
+ .map_err(into_logged_binder)
+ .map_err(|s| ResponseCode(s.service_specific_error()))
+ );
+
+ // map_km_error creates a Error::Binder variant storing the given exception code.
+ let binder_exception = map_km_error(binder_exception(ExceptionCode::TRANSACTION_FAILED));
+ assert_eq!(Err(Error::Binder(ExceptionCode::TRANSACTION_FAILED, 0)), binder_exception);
+ // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
+ assert_eq!(
+ Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
+ binder_exception
+ .context("Binder Exception.")
+ .map_err(into_logged_binder)
+ .map_err(|s| ResponseCode(s.service_specific_error()))
+ );
+
+ // selinux::Error::Perm() needs to be mapped to ResponseCode::PERMISSION_DENIED
+ assert_eq!(
+ Result::<(), ResponseCode>::Err(ResponseCode::PERMISSION_DENIED),
+ nested_selinux_perm()
+ .map_err(into_logged_binder)
+ .map_err(|s| ResponseCode(s.service_specific_error()))
+ );
+
+ // All other errors get mapped on System Error.
+ assert_eq!(
+ Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
+ nested_other_error()
+ .map_err(into_logged_binder)
+ .map_err(|s| ResponseCode(s.service_specific_error()))
+ );
+
+ // Result::Ok variants get passed to the ok handler.
+ assert_eq!(
+ Ok(ResponseCode::LOCKED),
+ nested_ok(ResponseCode::LOCKED).map_err(into_logged_binder)
+ );
+ assert_eq!(
+ Ok(ResponseCode::SYSTEM_ERROR),
+ nested_ok(ResponseCode::SYSTEM_ERROR).map_err(into_logged_binder)
+ );
+
+ Ok(())
+}
+
+//Helper function to test whether error cases are handled as expected.
+pub fn check_result_contains_error_string<T>(
+ result: anyhow::Result<T>,
+ expected_error_string: &str,
+) {
+ let error_str = format!(
+ "{:#?}",
+ result.err().unwrap_or_else(|| panic!("Expected the error: {}", expected_error_string))
+ );
+ assert!(
+ error_str.contains(expected_error_string),
+ "The string \"{}\" should contain \"{}\"",
+ error_str,
+ expected_error_string
+ );
+}
+
+#[test]
+fn rkpd_error_is_in_sync_with_response_code() {
+ let error_mapping = [
+ (RkpdError::RequestCancelled, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
+ (RkpdError::GetRegistrationFailed, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
+ (
+ RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_UNKNOWN),
+ ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR,
+ ),
+ (
+ RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PERMANENT),
+ ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR,
+ ),
+ (
+ RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY),
+ ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY,
+ ),
+ (
+ RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH),
+ ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE,
+ ),
+ (RkpdError::StoreUpgradedKeyFailed, ResponseCode::SYSTEM_ERROR),
+ (RkpdError::RetryableTimeout, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
+ (RkpdError::Timeout, ResponseCode::SYSTEM_ERROR),
+ ];
+ for (rkpd_error, expected_response_code) in error_mapping {
+ let e: Error = rkpd_error.into();
+ assert_eq!(e, Error::Rc(expected_response_code));
+ }
+}
diff --git a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
index fb4c9ad..62167fb 100644
--- a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
+++ b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
@@ -26,7 +26,7 @@
hmac_sha256, parse_subject_from_certificate, Password, ZVec,
};
use keystore2_hal_names::get_hidl_instances;
-use keystore2_selinux::{check_access, getpidcon, setcon, Backend, Context, KeystoreKeyBackend};
+use keystore2_selinux::{check_access, setcon, Backend, Context, KeystoreKeyBackend};
use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target};
use std::{ffi::CString, sync::Arc};
@@ -108,9 +108,6 @@
Backend {
namespace: &'a str,
},
- GetPidCon {
- pid: i32,
- },
CheckAccess {
source: &'a [u8],
target: &'a [u8],
@@ -216,9 +213,6 @@
let _res = backend.lookup(namespace);
}
}
- FuzzCommand::GetPidCon { pid } => {
- let _res = getpidcon(pid);
- }
FuzzCommand::CheckAccess { source, target, tclass, perm } => {
let source = get_valid_cstring_data(source);
let target = get_valid_cstring_data(target);
diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs
index f2341e3..9741671 100644
--- a/keystore2/src/gc.rs
+++ b/keystore2/src/gc.rs
@@ -22,6 +22,7 @@
use crate::{
async_task,
database::{KeystoreDB, SupersededBlob, Uuid},
+ globals,
super_key::SuperKeyManager,
};
use anyhow::{Context, Result};
@@ -135,6 +136,17 @@
/// Processes one key and then schedules another attempt until it runs out of blobs to delete.
fn step(&mut self) {
self.notified.store(0, Ordering::Relaxed);
+ if !globals::boot_completed() {
+ // Garbage collection involves a operation (`IKeyMintDevice::deleteKey()`) that cannot
+ // be rolled back in some cases (specifically, when the key is rollback-resistant), even
+ // if the Keystore database is restored to the version of an earlier userdata filesystem
+ // checkpoint.
+ //
+ // This means that we should not perform GC until boot has fully completed, and any
+ // in-progress OTA is definitely not going to be rolled back.
+ log::info!("skip GC as boot not completed");
+ return;
+ }
if let Err(e) = self.process_one_key() {
log::error!("Error trying to delete blob entry. {:?}", e);
}
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index c7b495d..9ee2a1e 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -23,7 +23,7 @@
use crate::legacy_blob::LegacyBlobLoader;
use crate::legacy_importer::LegacyImporter;
use crate::super_key::SuperKeyManager;
-use crate::utils::watchdog as wd;
+use crate::utils::{retry_get_interface, watchdog as wd};
use crate::{
database::KeystoreDB,
database::Uuid,
@@ -46,8 +46,11 @@
use anyhow::{Context, Result};
use binder::FromIBinder;
use binder::{get_declared_instances, is_declared};
-use lazy_static::lazy_static;
-use std::sync::{Arc, Mutex, RwLock};
+use rustutils::system_properties::PropertyWatcher;
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc, LazyLock, Mutex, RwLock,
+};
use std::{cell::RefCell, sync::Once};
use std::{collections::HashMap, path::Path, path::PathBuf};
@@ -62,21 +65,25 @@
/// is run only once, as long as the ASYNC_TASK instance is the same. So only one additional
/// database connection is created for the garbage collector worker.
pub fn create_thread_local_db() -> KeystoreDB {
- let db_path = DB_PATH.read().expect("Could not get the database directory.");
+ let db_path = DB_PATH.read().expect("Could not get the database directory");
- let mut db = KeystoreDB::new(&db_path, Some(GC.clone())).expect("Failed to open database.");
+ let result = KeystoreDB::new(&db_path, Some(GC.clone()));
+ let mut db = match result {
+ Ok(db) => db,
+ Err(e) => {
+ log::error!("Failed to open Keystore database at {db_path:?}: {e:?}");
+ log::error!("Has /data been mounted correctly?");
+ panic!("Failed to open database for Keystore, cannot continue: {e:?}")
+ }
+ };
DB_INIT.call_once(|| {
log::info!("Touching Keystore 2.0 database for this first time since boot.");
log::info!("Calling cleanup leftovers.");
- let n = db.cleanup_leftovers().expect("Failed to cleanup database on startup.");
+ let n = db.cleanup_leftovers().expect("Failed to cleanup database on startup");
if n != 0 {
log::info!(
- concat!(
- "Cleaned up {} failed entries. ",
- "This indicates keystore crashed during key generation."
- ),
- n
+ "Cleaned up {n} failed entries, indicating keystore crash on key generation"
);
}
});
@@ -88,8 +95,7 @@
/// same database multiple times is safe as long as each connection is
/// used by only one thread. So we store one database connection per
/// thread in this thread local key.
- pub static DB: RefCell<KeystoreDB> =
- RefCell::new(create_thread_local_db());
+ pub static DB: RefCell<KeystoreDB> = RefCell::new(create_thread_local_db());
}
struct DevicesMap<T: FromIBinder + ?Sized> {
@@ -136,45 +142,54 @@
}
}
-lazy_static! {
- /// The path where keystore stores all its keys.
- pub static ref DB_PATH: RwLock<PathBuf> = RwLock::new(
- Path::new("/data/misc/keystore").to_path_buf());
- /// Runtime database of unwrapped super keys.
- pub static ref SUPER_KEY: Arc<RwLock<SuperKeyManager>> = Default::default();
- /// Map of KeyMint devices.
- static ref KEY_MINT_DEVICES: Mutex<DevicesMap<dyn IKeyMintDevice>> = Default::default();
- /// Timestamp service.
- static ref TIME_STAMP_DEVICE: Mutex<Option<Strong<dyn ISecureClock>>> = Default::default();
- /// A single on-demand worker thread that handles deferred tasks with two different
- /// priorities.
- pub static ref ASYNC_TASK: Arc<AsyncTask> = Default::default();
- /// Singleton for enforcements.
- pub static ref ENFORCEMENTS: Enforcements = Default::default();
- /// LegacyBlobLoader is initialized and exists globally.
- /// The same directory used by the database is used by the LegacyBlobLoader as well.
- pub static ref LEGACY_BLOB_LOADER: Arc<LegacyBlobLoader> = Arc::new(LegacyBlobLoader::new(
- &DB_PATH.read().expect("Could not get the database path for legacy blob loader.")));
- /// Legacy migrator. Atomically migrates legacy blobs to the database.
- pub static ref LEGACY_IMPORTER: Arc<LegacyImporter> =
- Arc::new(LegacyImporter::new(Arc::new(Default::default())));
- /// Background thread which handles logging via statsd and logd
- pub static ref LOGS_HANDLER: Arc<AsyncTask> = Default::default();
+/// The path where keystore stores all its keys.
+pub static DB_PATH: LazyLock<RwLock<PathBuf>> =
+ LazyLock::new(|| RwLock::new(Path::new("/data/misc/keystore").to_path_buf()));
+/// Runtime database of unwrapped super keys.
+pub static SUPER_KEY: LazyLock<Arc<RwLock<SuperKeyManager>>> = LazyLock::new(Default::default);
+/// Map of KeyMint devices.
+static KEY_MINT_DEVICES: LazyLock<Mutex<DevicesMap<dyn IKeyMintDevice>>> =
+ LazyLock::new(Default::default);
+/// Timestamp service.
+static TIME_STAMP_DEVICE: Mutex<Option<Strong<dyn ISecureClock>>> = Mutex::new(None);
+/// A single on-demand worker thread that handles deferred tasks with two different
+/// priorities.
+pub static ASYNC_TASK: LazyLock<Arc<AsyncTask>> = LazyLock::new(Default::default);
+/// Singleton for enforcements.
+pub static ENFORCEMENTS: LazyLock<Enforcements> = LazyLock::new(Default::default);
+/// LegacyBlobLoader is initialized and exists globally.
+/// The same directory used by the database is used by the LegacyBlobLoader as well.
+pub static LEGACY_BLOB_LOADER: LazyLock<Arc<LegacyBlobLoader>> = LazyLock::new(|| {
+ Arc::new(LegacyBlobLoader::new(
+ &DB_PATH.read().expect("Could not determine database path for legacy blob loader"),
+ ))
+});
+/// Legacy migrator. Atomically migrates legacy blobs to the database.
+pub static LEGACY_IMPORTER: LazyLock<Arc<LegacyImporter>> =
+ LazyLock::new(|| Arc::new(LegacyImporter::new(Arc::new(Default::default()))));
+/// Background thread which handles logging via statsd and logd
+pub static LOGS_HANDLER: LazyLock<Arc<AsyncTask>> = LazyLock::new(Default::default);
+/// DER-encoded module information returned by `getSupplementaryAttestationInfo(Tag.MODULE_HASH)`.
+pub static ENCODED_MODULE_INFO: RwLock<Option<Vec<u8>>> = RwLock::new(None);
- static ref GC: Arc<Gc> = Arc::new(Gc::new_init_with(ASYNC_TASK.clone(), || {
+static GC: LazyLock<Arc<Gc>> = LazyLock::new(|| {
+ Arc::new(Gc::new_init_with(ASYNC_TASK.clone(), || {
(
Box::new(|uuid, blob| {
let km_dev = get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?;
- let _wp = wd::watch("In invalidate key closure: calling deleteKey");
+ let _wp = wd::watch("invalidate key closure: calling IKeyMintDevice::deleteKey");
map_km_error(km_dev.deleteKey(blob))
.context(ks_err!("Trying to invalidate key blob."))
}),
- KeystoreDB::new(&DB_PATH.read().expect("Could not get the database directory."), None)
- .expect("Failed to open database."),
+ KeystoreDB::new(
+ &DB_PATH.read().expect("Could not determine database path for GC"),
+ None,
+ )
+ .expect("Failed to open database"),
SUPER_KEY.clone(),
)
- }));
-}
+ }))
+});
/// Determine the service name for a KeyMint device of the given security level
/// gotten by binder service from the device and determining what services
@@ -222,8 +237,12 @@
let (keymint, hal_version) = if let Some(service_name) = service_name {
let km: Strong<dyn IKeyMintDevice> =
- map_binder_status_code(binder::get_interface(&service_name))
- .context(ks_err!("Trying to connect to genuine KeyMint service."))?;
+ if SecurityLevel::TRUSTED_ENVIRONMENT == *security_level {
+ map_binder_status_code(retry_get_interface(&service_name))
+ } else {
+ map_binder_status_code(binder::get_interface(&service_name))
+ }
+ .context(ks_err!("Trying to connect to genuine KeyMint service."))?;
// Map the HAL version code for KeyMint to be <AIDL version> * 100, so
// - V1 is 100
// - V2 is 200
@@ -258,17 +277,8 @@
// If the KeyMint device is back-level, use a wrapper that intercepts and
// emulates things that are not supported by the hardware.
let keymint = match hal_version {
- Some(300) => {
- // Current KeyMint version: use as-is as v3 Keymint is current version
- log::info!(
- "KeyMint device is current version ({:?}) for security level: {:?}",
- hal_version,
- security_level
- );
- keymint
- }
- Some(200) => {
- // Previous KeyMint version: use as-is as we don't have any software emulation of v3-specific KeyMint features.
+ Some(400) | Some(300) | Some(200) => {
+ // KeyMint v2+: use as-is (we don't have any software emulation of v3 or v4-specific KeyMint features).
log::info!(
"KeyMint device is current version ({:?}) for security level: {:?}",
hal_version,
@@ -306,7 +316,7 @@
}
};
- let wp = wd::watch("In connect_keymint: calling getHardwareInfo()");
+ let wp = wd::watch("connect_keymint: calling IKeyMintDevice::getHardwareInfo()");
let mut hw_info =
map_km_error(keymint.getHardwareInfo()).context(ks_err!("Failed to get hardware info."))?;
drop(wp);
@@ -443,3 +453,40 @@
.ok_or(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
.context(ks_err!("Failed to get rpc for sec level {:?}", *security_level))
}
+
+/// Whether boot is complete.
+static BOOT_COMPLETED: AtomicBool = AtomicBool::new(false);
+
+/// Indicate whether boot is complete.
+///
+/// This in turn indicates whether it is safe to make permanent changes to state.
+pub fn boot_completed() -> bool {
+ BOOT_COMPLETED.load(Ordering::Acquire)
+}
+
+/// Monitor the system property for boot complete. This blocks and so needs to be run in a separate
+/// thread.
+pub fn await_boot_completed() {
+ // Use a fairly long watchdog timeout of 5 minutes here. This blocks until the device
+ // boots, which on a very slow device (e.g., emulator for a non-native architecture) can
+ // take minutes. Blocking here would be unexpected only if it never finishes.
+ let _wp = wd::watch_millis("await_boot_completed", 300_000);
+ log::info!("monitoring for sys.boot_completed=1");
+ while let Err(e) = watch_for_boot_completed() {
+ log::error!("failed to watch for boot_completed: {e:?}");
+ std::thread::sleep(std::time::Duration::from_secs(5));
+ }
+
+ BOOT_COMPLETED.store(true, Ordering::Release);
+ log::info!("wait_for_boot_completed done, triggering GC");
+
+ // Garbage collection may have been skipped until now, so trigger a check.
+ GC.notify_gc();
+}
+
+fn watch_for_boot_completed() -> Result<()> {
+ let mut w = PropertyWatcher::new("sys.boot_completed")
+ .context(ks_err!("PropertyWatcher::new failed"))?;
+ w.wait_for_value("1", None).context(ks_err!("Failed to wait for sys.boot_completed"))?;
+ Ok(())
+}
diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs
index bd45207..466fb50 100644
--- a/keystore2/src/key_parameter.rs
+++ b/keystore2/src/key_parameter.rs
@@ -111,6 +111,18 @@
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
+#[cfg(test)]
+mod generated_key_parameter_tests;
+
+#[cfg(test)]
+mod basic_tests;
+
+#[cfg(test)]
+mod storage_tests;
+
+#[cfg(test)]
+mod wire_tests;
+
/// This trait is used to associate a primitive to any type that can be stored inside a
/// KeyParameterValue, especially the AIDL enum types, e.g., keymint::{Algorithm, Digest, ...}.
/// This allows for simplifying the macro rules, e.g., for reading from the SQL database.
@@ -1091,490 +1103,3 @@
Authorization { securityLevel: self.security_level, keyParameter: self.value.into() }
}
}
-
-#[cfg(test)]
-mod generated_key_parameter_tests {
- use super::*;
- use android_hardware_security_keymint::aidl::android::hardware::security::keymint::TagType::TagType;
-
- fn get_field_by_tag_type(tag: Tag) -> KmKeyParameterValue {
- let tag_type = TagType((tag.0 as u32 & 0xF0000000) as i32);
- match tag {
- Tag::ALGORITHM => return KmKeyParameterValue::Algorithm(Default::default()),
- Tag::BLOCK_MODE => return KmKeyParameterValue::BlockMode(Default::default()),
- Tag::PADDING => return KmKeyParameterValue::PaddingMode(Default::default()),
- Tag::DIGEST => return KmKeyParameterValue::Digest(Default::default()),
- Tag::RSA_OAEP_MGF_DIGEST => return KmKeyParameterValue::Digest(Default::default()),
- Tag::EC_CURVE => return KmKeyParameterValue::EcCurve(Default::default()),
- Tag::ORIGIN => return KmKeyParameterValue::Origin(Default::default()),
- Tag::PURPOSE => return KmKeyParameterValue::KeyPurpose(Default::default()),
- Tag::USER_AUTH_TYPE => {
- return KmKeyParameterValue::HardwareAuthenticatorType(Default::default())
- }
- Tag::HARDWARE_TYPE => return KmKeyParameterValue::SecurityLevel(Default::default()),
- _ => {}
- }
- match tag_type {
- TagType::INVALID => return KmKeyParameterValue::Invalid(Default::default()),
- TagType::ENUM | TagType::ENUM_REP => {}
- TagType::UINT | TagType::UINT_REP => {
- return KmKeyParameterValue::Integer(Default::default())
- }
- TagType::ULONG | TagType::ULONG_REP => {
- return KmKeyParameterValue::LongInteger(Default::default())
- }
- TagType::DATE => return KmKeyParameterValue::DateTime(Default::default()),
- TagType::BOOL => return KmKeyParameterValue::BoolValue(Default::default()),
- TagType::BIGNUM | TagType::BYTES => {
- return KmKeyParameterValue::Blob(Default::default())
- }
- _ => {}
- }
- panic!("Unknown tag/tag_type: {:?} {:?}", tag, tag_type);
- }
-
- fn check_field_matches_tag_type(list_o_parameters: &[KmKeyParameter]) {
- for kp in list_o_parameters.iter() {
- match (&kp.value, get_field_by_tag_type(kp.tag)) {
- (&KmKeyParameterValue::Algorithm(_), KmKeyParameterValue::Algorithm(_))
- | (&KmKeyParameterValue::BlockMode(_), KmKeyParameterValue::BlockMode(_))
- | (&KmKeyParameterValue::PaddingMode(_), KmKeyParameterValue::PaddingMode(_))
- | (&KmKeyParameterValue::Digest(_), KmKeyParameterValue::Digest(_))
- | (&KmKeyParameterValue::EcCurve(_), KmKeyParameterValue::EcCurve(_))
- | (&KmKeyParameterValue::Origin(_), KmKeyParameterValue::Origin(_))
- | (&KmKeyParameterValue::KeyPurpose(_), KmKeyParameterValue::KeyPurpose(_))
- | (
- &KmKeyParameterValue::HardwareAuthenticatorType(_),
- KmKeyParameterValue::HardwareAuthenticatorType(_),
- )
- | (&KmKeyParameterValue::SecurityLevel(_), KmKeyParameterValue::SecurityLevel(_))
- | (&KmKeyParameterValue::Invalid(_), KmKeyParameterValue::Invalid(_))
- | (&KmKeyParameterValue::Integer(_), KmKeyParameterValue::Integer(_))
- | (&KmKeyParameterValue::LongInteger(_), KmKeyParameterValue::LongInteger(_))
- | (&KmKeyParameterValue::DateTime(_), KmKeyParameterValue::DateTime(_))
- | (&KmKeyParameterValue::BoolValue(_), KmKeyParameterValue::BoolValue(_))
- | (&KmKeyParameterValue::Blob(_), KmKeyParameterValue::Blob(_)) => {}
- (actual, expected) => panic!(
- "Tag {:?} associated with variant {:?} expected {:?}",
- kp.tag, actual, expected
- ),
- }
- }
- }
-
- #[test]
- fn key_parameter_value_field_matches_tag_type() {
- check_field_matches_tag_type(&KeyParameterValue::make_field_matches_tag_type_test_vector());
- }
-
- #[test]
- fn key_parameter_serialization_test() {
- let params = KeyParameterValue::make_key_parameter_defaults_vector();
- let mut out_buffer: Vec<u8> = Default::default();
- serde_cbor::to_writer(&mut out_buffer, ¶ms)
- .expect("Failed to serialize key parameters.");
- let deserialized_params: Vec<KeyParameter> =
- serde_cbor::from_reader(&mut out_buffer.as_slice())
- .expect("Failed to deserialize key parameters.");
- assert_eq!(params, deserialized_params);
- }
-}
-
-#[cfg(test)]
-mod basic_tests {
- use crate::key_parameter::*;
-
- // Test basic functionality of KeyParameter.
- #[test]
- fn test_key_parameter() {
- let key_parameter = KeyParameter::new(
- KeyParameterValue::Algorithm(Algorithm::RSA),
- SecurityLevel::STRONGBOX,
- );
-
- assert_eq!(key_parameter.get_tag(), Tag::ALGORITHM);
-
- assert_eq!(
- *key_parameter.key_parameter_value(),
- KeyParameterValue::Algorithm(Algorithm::RSA)
- );
-
- assert_eq!(*key_parameter.security_level(), SecurityLevel::STRONGBOX);
- }
-}
-
-/// The storage_tests module first tests the 'new_from_sql' method for KeyParameters of different
-/// data types and then tests 'to_sql' method for KeyParameters of those
-/// different data types. The five different data types for KeyParameter values are:
-/// i) enums of u32
-/// ii) u32
-/// iii) u64
-/// iv) Vec<u8>
-/// v) bool
-#[cfg(test)]
-mod storage_tests {
- use crate::error::*;
- use crate::key_parameter::*;
- use anyhow::Result;
- use rusqlite::types::ToSql;
- use rusqlite::{params, Connection};
-
- /// Test initializing a KeyParameter (with key parameter value corresponding to an enum of i32)
- /// from a database table row.
- #[test]
- fn test_new_from_sql_enum_i32() -> Result<()> {
- let db = init_db()?;
- insert_into_keyparameter(
- &db,
- 1,
- Tag::ALGORITHM.0,
- &Algorithm::RSA.0,
- SecurityLevel::STRONGBOX.0,
- )?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(Tag::ALGORITHM, key_param.get_tag());
- assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA));
- assert_eq!(*key_param.security_level(), SecurityLevel::STRONGBOX);
- Ok(())
- }
-
- /// Test initializing a KeyParameter (with key parameter value which is of i32)
- /// from a database table row.
- #[test]
- fn test_new_from_sql_i32() -> Result<()> {
- let db = init_db()?;
- insert_into_keyparameter(&db, 1, Tag::KEY_SIZE.0, &1024, SecurityLevel::STRONGBOX.0)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(Tag::KEY_SIZE, key_param.get_tag());
- assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::KeySize(1024));
- Ok(())
- }
-
- /// Test initializing a KeyParameter (with key parameter value which is of i64)
- /// from a database table row.
- #[test]
- fn test_new_from_sql_i64() -> Result<()> {
- let db = init_db()?;
- // max value for i64, just to test corner cases
- insert_into_keyparameter(
- &db,
- 1,
- Tag::RSA_PUBLIC_EXPONENT.0,
- &(i64::MAX),
- SecurityLevel::STRONGBOX.0,
- )?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(Tag::RSA_PUBLIC_EXPONENT, key_param.get_tag());
- assert_eq!(
- *key_param.key_parameter_value(),
- KeyParameterValue::RSAPublicExponent(i64::MAX)
- );
- Ok(())
- }
-
- /// Test initializing a KeyParameter (with key parameter value which is of bool)
- /// from a database table row.
- #[test]
- fn test_new_from_sql_bool() -> Result<()> {
- let db = init_db()?;
- insert_into_keyparameter(&db, 1, Tag::CALLER_NONCE.0, &Null, SecurityLevel::STRONGBOX.0)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(Tag::CALLER_NONCE, key_param.get_tag());
- assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::CallerNonce);
- Ok(())
- }
-
- /// Test initializing a KeyParameter (with key parameter value which is of Vec<u8>)
- /// from a database table row.
- #[test]
- fn test_new_from_sql_vec_u8() -> Result<()> {
- let db = init_db()?;
- let app_id = String::from("MyAppID");
- let app_id_bytes = app_id.into_bytes();
- insert_into_keyparameter(
- &db,
- 1,
- Tag::APPLICATION_ID.0,
- &app_id_bytes,
- SecurityLevel::STRONGBOX.0,
- )?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(Tag::APPLICATION_ID, key_param.get_tag());
- assert_eq!(
- *key_param.key_parameter_value(),
- KeyParameterValue::ApplicationID(app_id_bytes)
- );
- Ok(())
- }
-
- /// Test storing a KeyParameter (with key parameter value which corresponds to an enum of i32)
- /// in the database
- #[test]
- fn test_to_sql_enum_i32() -> Result<()> {
- let db = init_db()?;
- let kp = KeyParameter::new(
- KeyParameterValue::Algorithm(Algorithm::RSA),
- SecurityLevel::STRONGBOX,
- );
- store_keyparameter(&db, 1, &kp)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(kp.get_tag(), key_param.get_tag());
- assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
- assert_eq!(kp.security_level(), key_param.security_level());
- Ok(())
- }
-
- /// Test storing a KeyParameter (with key parameter value which is of i32) in the database
- #[test]
- fn test_to_sql_i32() -> Result<()> {
- let db = init_db()?;
- let kp = KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::STRONGBOX);
- store_keyparameter(&db, 1, &kp)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(kp.get_tag(), key_param.get_tag());
- assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
- assert_eq!(kp.security_level(), key_param.security_level());
- Ok(())
- }
-
- /// Test storing a KeyParameter (with key parameter value which is of i64) in the database
- #[test]
- fn test_to_sql_i64() -> Result<()> {
- let db = init_db()?;
- // max value for i64, just to test corner cases
- let kp = KeyParameter::new(
- KeyParameterValue::RSAPublicExponent(i64::MAX),
- SecurityLevel::STRONGBOX,
- );
- store_keyparameter(&db, 1, &kp)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(kp.get_tag(), key_param.get_tag());
- assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
- assert_eq!(kp.security_level(), key_param.security_level());
- Ok(())
- }
-
- /// Test storing a KeyParameter (with key parameter value which is of Vec<u8>) in the database
- #[test]
- fn test_to_sql_vec_u8() -> Result<()> {
- let db = init_db()?;
- let kp = KeyParameter::new(
- KeyParameterValue::ApplicationID(String::from("MyAppID").into_bytes()),
- SecurityLevel::STRONGBOX,
- );
- store_keyparameter(&db, 1, &kp)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(kp.get_tag(), key_param.get_tag());
- assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
- assert_eq!(kp.security_level(), key_param.security_level());
- Ok(())
- }
-
- /// Test storing a KeyParameter (with key parameter value which is of i32) in the database
- #[test]
- fn test_to_sql_bool() -> Result<()> {
- let db = init_db()?;
- let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX);
- store_keyparameter(&db, 1, &kp)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(kp.get_tag(), key_param.get_tag());
- assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
- assert_eq!(kp.security_level(), key_param.security_level());
- Ok(())
- }
-
- #[test]
- /// Test Tag::Invalid
- fn test_invalid_tag() -> Result<()> {
- let db = init_db()?;
- insert_into_keyparameter(&db, 1, 0, &123, 1)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(Tag::INVALID, key_param.get_tag());
- Ok(())
- }
-
- #[test]
- fn test_non_existing_enum_variant() -> Result<()> {
- let db = init_db()?;
- insert_into_keyparameter(&db, 1, 100, &123, 1)?;
- let key_param = query_from_keyparameter(&db)?;
- assert_eq!(Tag::INVALID, key_param.get_tag());
- Ok(())
- }
-
- #[test]
- fn test_invalid_conversion_from_sql() -> Result<()> {
- let db = init_db()?;
- insert_into_keyparameter(&db, 1, Tag::ALGORITHM.0, &Null, 1)?;
- tests::check_result_contains_error_string(
- query_from_keyparameter(&db),
- "Failed to read sql data for tag: ALGORITHM.",
- );
- Ok(())
- }
-
- /// Helper method to init database table for key parameter
- fn init_db() -> Result<Connection> {
- let db = Connection::open_in_memory().context("Failed to initialize sqlite connection.")?;
- db.execute("ATTACH DATABASE ? as 'persistent';", params![""])
- .context("Failed to attach databases.")?;
- db.execute(
- "CREATE TABLE IF NOT EXISTS persistent.keyparameter (
- keyentryid INTEGER,
- tag INTEGER,
- data ANY,
- security_level INTEGER);",
- [],
- )
- .context("Failed to initialize \"keyparameter\" table.")?;
- Ok(db)
- }
-
- /// Helper method to insert an entry into key parameter table, with individual parameters
- fn insert_into_keyparameter<T: ToSql>(
- db: &Connection,
- key_id: i64,
- tag: i32,
- value: &T,
- security_level: i32,
- ) -> Result<()> {
- db.execute(
- "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
- VALUES(?, ?, ?, ?);",
- params![key_id, tag, *value, security_level],
- )?;
- Ok(())
- }
-
- /// Helper method to store a key parameter instance.
- fn store_keyparameter(db: &Connection, key_id: i64, kp: &KeyParameter) -> Result<()> {
- db.execute(
- "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
- VALUES(?, ?, ?, ?);",
- params![key_id, kp.get_tag().0, kp.key_parameter_value(), kp.security_level().0],
- )?;
- Ok(())
- }
-
- /// Helper method to query a row from keyparameter table
- fn query_from_keyparameter(db: &Connection) -> Result<KeyParameter> {
- let mut stmt =
- db.prepare("SELECT tag, data, security_level FROM persistent.keyparameter")?;
- let mut rows = stmt.query([])?;
- let row = rows.next()?.unwrap();
- KeyParameter::new_from_sql(
- Tag(row.get(0)?),
- &SqlField::new(1, row),
- SecurityLevel(row.get(2)?),
- )
- }
-}
-
-/// The wire_tests module tests the 'convert_to_wire' and 'convert_from_wire' methods for
-/// KeyParameter, for the four different types used in KmKeyParameter, in addition to Invalid
-/// key parameter.
-/// i) bool
-/// ii) integer
-/// iii) longInteger
-/// iv) blob
-#[cfg(test)]
-mod wire_tests {
- use crate::key_parameter::*;
- /// unit tests for to conversions
- #[test]
- fn test_convert_to_wire_invalid() {
- let kp = KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::STRONGBOX);
- assert_eq!(
- KmKeyParameter { tag: Tag::INVALID, value: KmKeyParameterValue::Invalid(0) },
- kp.value.into()
- );
- }
- #[test]
- fn test_convert_to_wire_bool() {
- let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX);
- assert_eq!(
- KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) },
- kp.value.into()
- );
- }
- #[test]
- fn test_convert_to_wire_integer() {
- let kp = KeyParameter::new(
- KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT),
- SecurityLevel::STRONGBOX,
- );
- assert_eq!(
- KmKeyParameter {
- tag: Tag::PURPOSE,
- value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT)
- },
- kp.value.into()
- );
- }
- #[test]
- fn test_convert_to_wire_long_integer() {
- let kp =
- KeyParameter::new(KeyParameterValue::UserSecureID(i64::MAX), SecurityLevel::STRONGBOX);
- assert_eq!(
- KmKeyParameter {
- tag: Tag::USER_SECURE_ID,
- value: KmKeyParameterValue::LongInteger(i64::MAX)
- },
- kp.value.into()
- );
- }
- #[test]
- fn test_convert_to_wire_blob() {
- let kp = KeyParameter::new(
- KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()),
- SecurityLevel::STRONGBOX,
- );
- assert_eq!(
- KmKeyParameter {
- tag: Tag::CONFIRMATION_TOKEN,
- value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes())
- },
- kp.value.into()
- );
- }
-
- /// unit tests for from conversion
- #[test]
- fn test_convert_from_wire_invalid() {
- let aidl_kp = KmKeyParameter { tag: Tag::INVALID, ..Default::default() };
- assert_eq!(KeyParameterValue::Invalid, aidl_kp.into());
- }
- #[test]
- fn test_convert_from_wire_bool() {
- let aidl_kp =
- KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) };
- assert_eq!(KeyParameterValue::CallerNonce, aidl_kp.into());
- }
- #[test]
- fn test_convert_from_wire_integer() {
- let aidl_kp = KmKeyParameter {
- tag: Tag::PURPOSE,
- value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT),
- };
- assert_eq!(KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), aidl_kp.into());
- }
- #[test]
- fn test_convert_from_wire_long_integer() {
- let aidl_kp = KmKeyParameter {
- tag: Tag::USER_SECURE_ID,
- value: KmKeyParameterValue::LongInteger(i64::MAX),
- };
- assert_eq!(KeyParameterValue::UserSecureID(i64::MAX), aidl_kp.into());
- }
- #[test]
- fn test_convert_from_wire_blob() {
- let aidl_kp = KmKeyParameter {
- tag: Tag::CONFIRMATION_TOKEN,
- value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes()),
- };
- assert_eq!(
- KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()),
- aidl_kp.into()
- );
- }
-}
diff --git a/keystore2/src/key_parameter/basic_tests.rs b/keystore2/src/key_parameter/basic_tests.rs
new file mode 100644
index 0000000..2bb3724
--- /dev/null
+++ b/keystore2/src/key_parameter/basic_tests.rs
@@ -0,0 +1,28 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::key_parameter::*;
+
+// Test basic functionality of KeyParameter.
+#[test]
+fn test_key_parameter() {
+ let key_parameter =
+ KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::RSA), SecurityLevel::STRONGBOX);
+
+ assert_eq!(key_parameter.get_tag(), Tag::ALGORITHM);
+
+ assert_eq!(*key_parameter.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA));
+
+ assert_eq!(*key_parameter.security_level(), SecurityLevel::STRONGBOX);
+}
diff --git a/keystore2/src/key_parameter/generated_key_parameter_tests.rs b/keystore2/src/key_parameter/generated_key_parameter_tests.rs
new file mode 100644
index 0000000..a5c0a8b
--- /dev/null
+++ b/keystore2/src/key_parameter/generated_key_parameter_tests.rs
@@ -0,0 +1,95 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::*;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::TagType::TagType;
+
+fn get_field_by_tag_type(tag: Tag) -> KmKeyParameterValue {
+ let tag_type = TagType((tag.0 as u32 & 0xF0000000) as i32);
+ match tag {
+ Tag::ALGORITHM => return KmKeyParameterValue::Algorithm(Default::default()),
+ Tag::BLOCK_MODE => return KmKeyParameterValue::BlockMode(Default::default()),
+ Tag::PADDING => return KmKeyParameterValue::PaddingMode(Default::default()),
+ Tag::DIGEST => return KmKeyParameterValue::Digest(Default::default()),
+ Tag::RSA_OAEP_MGF_DIGEST => return KmKeyParameterValue::Digest(Default::default()),
+ Tag::EC_CURVE => return KmKeyParameterValue::EcCurve(Default::default()),
+ Tag::ORIGIN => return KmKeyParameterValue::Origin(Default::default()),
+ Tag::PURPOSE => return KmKeyParameterValue::KeyPurpose(Default::default()),
+ Tag::USER_AUTH_TYPE => {
+ return KmKeyParameterValue::HardwareAuthenticatorType(Default::default())
+ }
+ Tag::HARDWARE_TYPE => return KmKeyParameterValue::SecurityLevel(Default::default()),
+ _ => {}
+ }
+ match tag_type {
+ TagType::INVALID => return KmKeyParameterValue::Invalid(Default::default()),
+ TagType::ENUM | TagType::ENUM_REP => {}
+ TagType::UINT | TagType::UINT_REP => {
+ return KmKeyParameterValue::Integer(Default::default())
+ }
+ TagType::ULONG | TagType::ULONG_REP => {
+ return KmKeyParameterValue::LongInteger(Default::default())
+ }
+ TagType::DATE => return KmKeyParameterValue::DateTime(Default::default()),
+ TagType::BOOL => return KmKeyParameterValue::BoolValue(Default::default()),
+ TagType::BIGNUM | TagType::BYTES => return KmKeyParameterValue::Blob(Default::default()),
+ _ => {}
+ }
+ panic!("Unknown tag/tag_type: {:?} {:?}", tag, tag_type);
+}
+
+fn check_field_matches_tag_type(list_o_parameters: &[KmKeyParameter]) {
+ for kp in list_o_parameters.iter() {
+ match (&kp.value, get_field_by_tag_type(kp.tag)) {
+ (&KmKeyParameterValue::Algorithm(_), KmKeyParameterValue::Algorithm(_))
+ | (&KmKeyParameterValue::BlockMode(_), KmKeyParameterValue::BlockMode(_))
+ | (&KmKeyParameterValue::PaddingMode(_), KmKeyParameterValue::PaddingMode(_))
+ | (&KmKeyParameterValue::Digest(_), KmKeyParameterValue::Digest(_))
+ | (&KmKeyParameterValue::EcCurve(_), KmKeyParameterValue::EcCurve(_))
+ | (&KmKeyParameterValue::Origin(_), KmKeyParameterValue::Origin(_))
+ | (&KmKeyParameterValue::KeyPurpose(_), KmKeyParameterValue::KeyPurpose(_))
+ | (
+ &KmKeyParameterValue::HardwareAuthenticatorType(_),
+ KmKeyParameterValue::HardwareAuthenticatorType(_),
+ )
+ | (&KmKeyParameterValue::SecurityLevel(_), KmKeyParameterValue::SecurityLevel(_))
+ | (&KmKeyParameterValue::Invalid(_), KmKeyParameterValue::Invalid(_))
+ | (&KmKeyParameterValue::Integer(_), KmKeyParameterValue::Integer(_))
+ | (&KmKeyParameterValue::LongInteger(_), KmKeyParameterValue::LongInteger(_))
+ | (&KmKeyParameterValue::DateTime(_), KmKeyParameterValue::DateTime(_))
+ | (&KmKeyParameterValue::BoolValue(_), KmKeyParameterValue::BoolValue(_))
+ | (&KmKeyParameterValue::Blob(_), KmKeyParameterValue::Blob(_)) => {}
+ (actual, expected) => panic!(
+ "Tag {:?} associated with variant {:?} expected {:?}",
+ kp.tag, actual, expected
+ ),
+ }
+ }
+}
+
+#[test]
+fn key_parameter_value_field_matches_tag_type() {
+ check_field_matches_tag_type(&KeyParameterValue::make_field_matches_tag_type_test_vector());
+}
+
+#[test]
+fn key_parameter_serialization_test() {
+ let params = KeyParameterValue::make_key_parameter_defaults_vector();
+ let mut out_buffer: Vec<u8> = Default::default();
+ serde_cbor::to_writer(&mut out_buffer, ¶ms).expect("Failed to serialize key parameters.");
+ let deserialized_params: Vec<KeyParameter> =
+ serde_cbor::from_reader(&mut out_buffer.as_slice())
+ .expect("Failed to deserialize key parameters.");
+ assert_eq!(params, deserialized_params);
+}
diff --git a/keystore2/src/key_parameter/storage_tests.rs b/keystore2/src/key_parameter/storage_tests.rs
new file mode 100644
index 0000000..38a57e4
--- /dev/null
+++ b/keystore2/src/key_parameter/storage_tests.rs
@@ -0,0 +1,263 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! The storage_tests module first tests the 'new_from_sql' method for KeyParameters of different
+//! data types and then tests 'to_sql' method for KeyParameters of those
+//! different data types. The five different data types for KeyParameter values are:
+//! i) enums of u32
+//! ii) u32
+//! iii) u64
+//! iv) Vec<u8>
+//! v) bool
+
+use crate::error::*;
+use crate::key_parameter::*;
+use anyhow::Result;
+use rusqlite::types::ToSql;
+use rusqlite::{params, Connection};
+
+/// Test initializing a KeyParameter (with key parameter value corresponding to an enum of i32)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_enum_i32() -> Result<()> {
+ let db = init_db()?;
+ insert_into_keyparameter(
+ &db,
+ 1,
+ Tag::ALGORITHM.0,
+ &Algorithm::RSA.0,
+ SecurityLevel::STRONGBOX.0,
+ )?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(Tag::ALGORITHM, key_param.get_tag());
+ assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA));
+ assert_eq!(*key_param.security_level(), SecurityLevel::STRONGBOX);
+ Ok(())
+}
+
+/// Test initializing a KeyParameter (with key parameter value which is of i32)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_i32() -> Result<()> {
+ let db = init_db()?;
+ insert_into_keyparameter(&db, 1, Tag::KEY_SIZE.0, &1024, SecurityLevel::STRONGBOX.0)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(Tag::KEY_SIZE, key_param.get_tag());
+ assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::KeySize(1024));
+ Ok(())
+}
+
+/// Test initializing a KeyParameter (with key parameter value which is of i64)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_i64() -> Result<()> {
+ let db = init_db()?;
+ // max value for i64, just to test corner cases
+ insert_into_keyparameter(
+ &db,
+ 1,
+ Tag::RSA_PUBLIC_EXPONENT.0,
+ &(i64::MAX),
+ SecurityLevel::STRONGBOX.0,
+ )?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(Tag::RSA_PUBLIC_EXPONENT, key_param.get_tag());
+ assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::RSAPublicExponent(i64::MAX));
+ Ok(())
+}
+
+/// Test initializing a KeyParameter (with key parameter value which is of bool)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_bool() -> Result<()> {
+ let db = init_db()?;
+ insert_into_keyparameter(&db, 1, Tag::CALLER_NONCE.0, &Null, SecurityLevel::STRONGBOX.0)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(Tag::CALLER_NONCE, key_param.get_tag());
+ assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::CallerNonce);
+ Ok(())
+}
+
+/// Test initializing a KeyParameter (with key parameter value which is of Vec<u8>)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_vec_u8() -> Result<()> {
+ let db = init_db()?;
+ let app_id = String::from("MyAppID");
+ let app_id_bytes = app_id.into_bytes();
+ insert_into_keyparameter(
+ &db,
+ 1,
+ Tag::APPLICATION_ID.0,
+ &app_id_bytes,
+ SecurityLevel::STRONGBOX.0,
+ )?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(Tag::APPLICATION_ID, key_param.get_tag());
+ assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::ApplicationID(app_id_bytes));
+ Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which corresponds to an enum of i32)
+/// in the database
+#[test]
+fn test_to_sql_enum_i32() -> Result<()> {
+ let db = init_db()?;
+ let kp =
+ KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::RSA), SecurityLevel::STRONGBOX);
+ store_keyparameter(&db, 1, &kp)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(kp.get_tag(), key_param.get_tag());
+ assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+ assert_eq!(kp.security_level(), key_param.security_level());
+ Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which is of i32) in the database
+#[test]
+fn test_to_sql_i32() -> Result<()> {
+ let db = init_db()?;
+ let kp = KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::STRONGBOX);
+ store_keyparameter(&db, 1, &kp)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(kp.get_tag(), key_param.get_tag());
+ assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+ assert_eq!(kp.security_level(), key_param.security_level());
+ Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which is of i64) in the database
+#[test]
+fn test_to_sql_i64() -> Result<()> {
+ let db = init_db()?;
+ // max value for i64, just to test corner cases
+ let kp =
+ KeyParameter::new(KeyParameterValue::RSAPublicExponent(i64::MAX), SecurityLevel::STRONGBOX);
+ store_keyparameter(&db, 1, &kp)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(kp.get_tag(), key_param.get_tag());
+ assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+ assert_eq!(kp.security_level(), key_param.security_level());
+ Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which is of Vec<u8>) in the database
+#[test]
+fn test_to_sql_vec_u8() -> Result<()> {
+ let db = init_db()?;
+ let kp = KeyParameter::new(
+ KeyParameterValue::ApplicationID(String::from("MyAppID").into_bytes()),
+ SecurityLevel::STRONGBOX,
+ );
+ store_keyparameter(&db, 1, &kp)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(kp.get_tag(), key_param.get_tag());
+ assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+ assert_eq!(kp.security_level(), key_param.security_level());
+ Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which is of i32) in the database
+#[test]
+fn test_to_sql_bool() -> Result<()> {
+ let db = init_db()?;
+ let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX);
+ store_keyparameter(&db, 1, &kp)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(kp.get_tag(), key_param.get_tag());
+ assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+ assert_eq!(kp.security_level(), key_param.security_level());
+ Ok(())
+}
+
+#[test]
+/// Test Tag::Invalid
+fn test_invalid_tag() -> Result<()> {
+ let db = init_db()?;
+ insert_into_keyparameter(&db, 1, 0, &123, 1)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(Tag::INVALID, key_param.get_tag());
+ Ok(())
+}
+
+#[test]
+fn test_non_existing_enum_variant() -> Result<()> {
+ let db = init_db()?;
+ insert_into_keyparameter(&db, 1, 100, &123, 1)?;
+ let key_param = query_from_keyparameter(&db)?;
+ assert_eq!(Tag::INVALID, key_param.get_tag());
+ Ok(())
+}
+
+#[test]
+fn test_invalid_conversion_from_sql() -> Result<()> {
+ let db = init_db()?;
+ insert_into_keyparameter(&db, 1, Tag::ALGORITHM.0, &Null, 1)?;
+ tests::check_result_contains_error_string(
+ query_from_keyparameter(&db),
+ "Failed to read sql data for tag: ALGORITHM.",
+ );
+ Ok(())
+}
+
+/// Helper method to init database table for key parameter
+fn init_db() -> Result<Connection> {
+ let db = Connection::open_in_memory().context("Failed to initialize sqlite connection.")?;
+ db.execute("ATTACH DATABASE ? as 'persistent';", params![""])
+ .context("Failed to attach databases.")?;
+ db.execute(
+ "CREATE TABLE IF NOT EXISTS persistent.keyparameter (
+ keyentryid INTEGER,
+ tag INTEGER,
+ data ANY,
+ security_level INTEGER);",
+ [],
+ )
+ .context("Failed to initialize \"keyparameter\" table.")?;
+ Ok(db)
+}
+
+/// Helper method to insert an entry into key parameter table, with individual parameters
+fn insert_into_keyparameter<T: ToSql>(
+ db: &Connection,
+ key_id: i64,
+ tag: i32,
+ value: &T,
+ security_level: i32,
+) -> Result<()> {
+ db.execute(
+ "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
+ VALUES(?, ?, ?, ?);",
+ params![key_id, tag, *value, security_level],
+ )?;
+ Ok(())
+}
+
+/// Helper method to store a key parameter instance.
+fn store_keyparameter(db: &Connection, key_id: i64, kp: &KeyParameter) -> Result<()> {
+ db.execute(
+ "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
+ VALUES(?, ?, ?, ?);",
+ params![key_id, kp.get_tag().0, kp.key_parameter_value(), kp.security_level().0],
+ )?;
+ Ok(())
+}
+
+/// Helper method to query a row from keyparameter table
+fn query_from_keyparameter(db: &Connection) -> Result<KeyParameter> {
+ let mut stmt = db.prepare("SELECT tag, data, security_level FROM persistent.keyparameter")?;
+ let mut rows = stmt.query([])?;
+ let row = rows.next()?.unwrap();
+ KeyParameter::new_from_sql(Tag(row.get(0)?), &SqlField::new(1, row), SecurityLevel(row.get(2)?))
+}
diff --git a/keystore2/src/key_parameter/wire_tests.rs b/keystore2/src/key_parameter/wire_tests.rs
new file mode 100644
index 0000000..278b766
--- /dev/null
+++ b/keystore2/src/key_parameter/wire_tests.rs
@@ -0,0 +1,119 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! The wire_tests module tests the 'convert_to_wire' and 'convert_from_wire' methods for
+//! KeyParameter, for the four different types used in KmKeyParameter, in addition to Invalid
+//! key parameter.
+//! i) bool
+//! ii) integer
+//! iii) longInteger
+//! iv) blob
+
+use crate::key_parameter::*;
+/// unit tests for to conversions
+#[test]
+fn test_convert_to_wire_invalid() {
+ let kp = KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::STRONGBOX);
+ assert_eq!(
+ KmKeyParameter { tag: Tag::INVALID, value: KmKeyParameterValue::Invalid(0) },
+ kp.value.into()
+ );
+}
+#[test]
+fn test_convert_to_wire_bool() {
+ let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX);
+ assert_eq!(
+ KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) },
+ kp.value.into()
+ );
+}
+#[test]
+fn test_convert_to_wire_integer() {
+ let kp = KeyParameter::new(
+ KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT),
+ SecurityLevel::STRONGBOX,
+ );
+ assert_eq!(
+ KmKeyParameter {
+ tag: Tag::PURPOSE,
+ value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT)
+ },
+ kp.value.into()
+ );
+}
+#[test]
+fn test_convert_to_wire_long_integer() {
+ let kp = KeyParameter::new(KeyParameterValue::UserSecureID(i64::MAX), SecurityLevel::STRONGBOX);
+ assert_eq!(
+ KmKeyParameter {
+ tag: Tag::USER_SECURE_ID,
+ value: KmKeyParameterValue::LongInteger(i64::MAX)
+ },
+ kp.value.into()
+ );
+}
+#[test]
+fn test_convert_to_wire_blob() {
+ let kp = KeyParameter::new(
+ KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()),
+ SecurityLevel::STRONGBOX,
+ );
+ assert_eq!(
+ KmKeyParameter {
+ tag: Tag::CONFIRMATION_TOKEN,
+ value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes())
+ },
+ kp.value.into()
+ );
+}
+
+/// unit tests for from conversion
+#[test]
+fn test_convert_from_wire_invalid() {
+ let aidl_kp = KmKeyParameter { tag: Tag::INVALID, ..Default::default() };
+ assert_eq!(KeyParameterValue::Invalid, aidl_kp.into());
+}
+#[test]
+fn test_convert_from_wire_bool() {
+ let aidl_kp =
+ KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) };
+ assert_eq!(KeyParameterValue::CallerNonce, aidl_kp.into());
+}
+#[test]
+fn test_convert_from_wire_integer() {
+ let aidl_kp = KmKeyParameter {
+ tag: Tag::PURPOSE,
+ value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT),
+ };
+ assert_eq!(KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), aidl_kp.into());
+}
+#[test]
+fn test_convert_from_wire_long_integer() {
+ let aidl_kp = KmKeyParameter {
+ tag: Tag::USER_SECURE_ID,
+ value: KmKeyParameterValue::LongInteger(i64::MAX),
+ };
+ assert_eq!(KeyParameterValue::UserSecureID(i64::MAX), aidl_kp.into());
+}
+#[test]
+fn test_convert_from_wire_blob() {
+ let aidl_kp = KmKeyParameter {
+ tag: Tag::CONFIRMATION_TOKEN,
+ value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes()),
+ };
+ assert_eq!(
+ KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()),
+ aidl_kp.into()
+ );
+}
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index 178b36c..008e6fe 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -93,6 +93,7 @@
ENFORCEMENTS.install_confirmation_token_receiver(confirmation_token_receiver);
+ std::thread::spawn(keystore2::globals::await_boot_completed);
entropy::register_feeder();
shared_secret_negotiation::perform_shared_secret_negotiation();
diff --git a/keystore2/src/km_compat.rs b/keystore2/src/km_compat.rs
index 5e3bdfa..95e9294 100644
--- a/keystore2/src/km_compat.rs
+++ b/keystore2/src/km_compat.rs
@@ -214,6 +214,12 @@
fn sendRootOfTrust(&self, root_of_trust: &[u8]) -> binder::Result<()> {
self.real.sendRootOfTrust(root_of_trust)
}
+ fn setAdditionalAttestationInfo(
+ &self,
+ additional_attestation_info: &[KeyParameter],
+ ) -> binder::Result<()> {
+ self.real.setAdditionalAttestationInfo(additional_attestation_info)
+ }
// For methods that emit keyblobs, check whether the underlying real device
// supports the relevant parameters, and forward to the appropriate device.
diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp
index e9ff1ff..7a6ef4a 100644
--- a/keystore2/src/km_compat/km_compat.cpp
+++ b/keystore2/src/km_compat/km_compat.cpp
@@ -839,6 +839,11 @@
return convertErrorCode(KMV1::ErrorCode::UNIMPLEMENTED);
}
+ScopedAStatus KeyMintDevice::setAdditionalAttestationInfo(
+ const std::vector<KeyParameter>& /* additionalAttestationInfo */) {
+ return convertErrorCode(KMV1::ErrorCode::UNIMPLEMENTED);
+}
+
ScopedAStatus KeyMintOperation::updateAad(const std::vector<uint8_t>& input,
const std::optional<HardwareAuthToken>& optAuthToken,
const std::optional<TimeStampToken>& optTimeStampToken) {
diff --git a/keystore2/src/km_compat/km_compat.h b/keystore2/src/km_compat/km_compat.h
index c4bcdaa..71f7fbe 100644
--- a/keystore2/src/km_compat/km_compat.h
+++ b/keystore2/src/km_compat/km_compat.h
@@ -147,6 +147,9 @@
std::vector<uint8_t>* rootOfTrust);
ScopedAStatus sendRootOfTrust(const std::vector<uint8_t>& rootOfTrust);
+ ScopedAStatus
+ setAdditionalAttestationInfo(const std::vector<KeyParameter>& additionalAttestationInfo);
+
// These are public to allow testing code to use them directly.
// This class should not be used publicly anyway.
std::variant<std::vector<Certificate>, KMV1_ErrorCode>
diff --git a/keystore2/src/km_compat/km_compat_type_conversion.h b/keystore2/src/km_compat/km_compat_type_conversion.h
index 5db7e3d..d6a2dcc 100644
--- a/keystore2/src/km_compat/km_compat_type_conversion.h
+++ b/keystore2/src/km_compat/km_compat_type_conversion.h
@@ -750,8 +750,12 @@
case KMV1::Tag::CERTIFICATE_SUBJECT:
case KMV1::Tag::CERTIFICATE_NOT_BEFORE:
case KMV1::Tag::CERTIFICATE_NOT_AFTER:
+ // These tags do not exist in KM < KeyMint 1.
+ break;
case KMV1::Tag::ATTESTATION_ID_SECOND_IMEI:
- // These tags do not exist in KM < KeyMint 1.0.
+ // This tag doesn't exist in KM < KeyMint 3.
+ case KMV1::Tag::MODULE_HASH:
+ // This tag doesn't exist in KM < KeyMint 4.
break;
case KMV1::Tag::MAX_BOOT_LEVEL:
// Does not exist in API level 30 or below.
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index 2bb7f27..e05e686 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -36,6 +36,9 @@
const SUPPORTED_LEGACY_BLOB_VERSION: u8 = 3;
+#[cfg(test)]
+mod tests;
+
mod flags {
/// This flag is deprecated. It is here to support keys that have been written with this flag
/// set, but we don't create any new keys with this flag.
@@ -958,7 +961,7 @@
fn make_user_path_name(&self, user_id: u32) -> PathBuf {
let mut path = self.path.clone();
- path.push(&format!("user_{}", user_id));
+ path.push(format!("user_{}", user_id));
path
}
@@ -1645,675 +1648,3 @@
Ok(())
}
}
-
-#[cfg(test)]
-mod test {
- #![allow(dead_code)]
- use super::*;
- use crate::legacy_blob::test_utils::legacy_blob_test_vectors::*;
- use crate::legacy_blob::test_utils::*;
- use anyhow::{anyhow, Result};
- use keystore2_crypto::aes_gcm_decrypt;
- use keystore2_test_utils::TempDir;
- use rand::Rng;
- use std::convert::TryInto;
- use std::ops::Deref;
- use std::string::FromUtf8Error;
-
- #[test]
- fn decode_encode_alias_test() {
- static ALIAS: &str = "#({}test[])😗";
- static ENCODED_ALIAS: &str = "+S+X{}test[]+Y.`-O-H-G";
- // Second multi byte out of range ------v
- static ENCODED_ALIAS_ERROR1: &str = "+S+{}test[]+Y";
- // Incomplete multi byte ------------------------v
- static ENCODED_ALIAS_ERROR2: &str = "+S+X{}test[]+";
- // Our encoding: ".`-O-H-G"
- // is UTF-8: 0xF0 0x9F 0x98 0x97
- // is UNICODE: U+1F617
- // is 😗
- // But +H below is a valid encoding for 0x18 making this sequence invalid UTF-8.
- static ENCODED_ALIAS_ERROR_UTF8: &str = ".`-O+H-G";
-
- assert_eq!(ENCODED_ALIAS, &LegacyBlobLoader::encode_alias(ALIAS));
- assert_eq!(ALIAS, &LegacyBlobLoader::decode_alias(ENCODED_ALIAS).unwrap());
- assert_eq!(
- Some(&Error::BadEncoding),
- LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR1)
- .unwrap_err()
- .root_cause()
- .downcast_ref::<Error>()
- );
- assert_eq!(
- Some(&Error::BadEncoding),
- LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR2)
- .unwrap_err()
- .root_cause()
- .downcast_ref::<Error>()
- );
- assert!(LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR_UTF8)
- .unwrap_err()
- .root_cause()
- .downcast_ref::<FromUtf8Error>()
- .is_some());
-
- for _i in 0..100 {
- // Any valid UTF-8 string should be en- and decoded without loss.
- let alias_str = rand::thread_rng().gen::<[char; 20]>().iter().collect::<String>();
- let random_alias = alias_str.as_bytes();
- let encoded = LegacyBlobLoader::encode_alias(&alias_str);
- let decoded = match LegacyBlobLoader::decode_alias(&encoded) {
- Ok(d) => d,
- Err(_) => panic!("random_alias: {:x?}\nencoded {}", random_alias, encoded),
- };
- assert_eq!(random_alias.to_vec(), decoded.bytes().collect::<Vec<u8>>());
- }
- }
-
- #[test]
- fn read_golden_key_blob_test() -> anyhow::Result<()> {
- let blob = LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*BLOB, |_, _, _, _, _| {
- Err(anyhow!("should not be called"))
- })
- .unwrap();
- assert!(!blob.is_encrypted());
- assert!(!blob.is_fallback());
- assert!(!blob.is_strongbox());
- assert!(!blob.is_critical_to_device_encryption());
- assert_eq!(blob.value(), &BlobValue::Generic([0xde, 0xed, 0xbe, 0xef].to_vec()));
-
- let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
- &mut &*REAL_LEGACY_BLOB,
- |_, _, _, _, _| Err(anyhow!("should not be called")),
- )
- .unwrap();
- assert!(!blob.is_encrypted());
- assert!(!blob.is_fallback());
- assert!(!blob.is_strongbox());
- assert!(!blob.is_critical_to_device_encryption());
- assert_eq!(
- blob.value(),
- &BlobValue::Decrypted(REAL_LEGACY_BLOB_PAYLOAD.try_into().unwrap())
- );
- Ok(())
- }
-
- #[test]
- fn read_aes_gcm_encrypted_key_blob_test() {
- let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
- &mut &*AES_GCM_ENCRYPTED_BLOB,
- |d, iv, tag, salt, key_size| {
- assert_eq!(salt, None);
- assert_eq!(key_size, None);
- assert_eq!(
- iv,
- &[
- 0xbd, 0xdb, 0x8d, 0x69, 0x72, 0x56, 0xf0, 0xf5, 0xa4, 0x02, 0x88, 0x7f,
- 0x00, 0x00, 0x00, 0x00,
- ]
- );
- assert_eq!(
- tag,
- &[
- 0x50, 0xd9, 0x97, 0x95, 0x37, 0x6e, 0x28, 0x6a, 0x28, 0x9d, 0x51, 0xb9,
- 0xb9, 0xe0, 0x0b, 0xc3
- ][..]
- );
- aes_gcm_decrypt(d, iv, tag, AES_KEY).context("Trying to decrypt blob.")
- },
- )
- .unwrap();
- assert!(blob.is_encrypted());
- assert!(!blob.is_fallback());
- assert!(!blob.is_strongbox());
- assert!(!blob.is_critical_to_device_encryption());
-
- assert_eq!(blob.value(), &BlobValue::Decrypted(DECRYPTED_PAYLOAD.try_into().unwrap()));
- }
-
- #[test]
- fn read_golden_key_blob_too_short_test() {
- let error =
- LegacyBlobLoader::new_from_stream_decrypt_with(&mut &BLOB[0..15], |_, _, _, _, _| {
- Err(anyhow!("should not be called"))
- })
- .unwrap_err();
- assert_eq!(Some(&Error::BadLen), error.root_cause().downcast_ref::<Error>());
- }
-
- #[test]
- fn test_is_empty() {
- let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir.");
- let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
- assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty."));
-
- let _db = crate::database::KeystoreDB::new(temp_dir.path(), None)
- .expect("Failed to open database.");
-
- assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty."));
-
- std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0.");
-
- assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty."));
-
- std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10.");
-
- assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
-
- std::fs::remove_dir_all(&*temp_dir.build().push("user_0"))
- .expect("Failed to remove user_0.");
-
- assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
-
- std::fs::remove_dir_all(&*temp_dir.build().push("user_10"))
- .expect("Failed to remove user_10.");
-
- assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again."));
- }
-
- #[test]
- fn test_legacy_blobs() -> anyhow::Result<()> {
- let temp_dir = TempDir::new("legacy_blob_test").unwrap();
- std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
-
- std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
-
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
- USRPKEY_AUTHBOUND,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
- USRPKEY_AUTHBOUND_CHR,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
- USRCERT_AUTHBOUND,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
- CACERT_AUTHBOUND,
- )
- .unwrap();
-
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_USRPKEY_non_authbound"),
- USRPKEY_NON_AUTHBOUND,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_non_authbound"),
- USRPKEY_NON_AUTHBOUND_CHR,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_USRCERT_non_authbound"),
- USRCERT_NON_AUTHBOUND,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_CACERT_non_authbound"),
- CACERT_NON_AUTHBOUND,
- )
- .unwrap();
-
- let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
- if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
- {
- assert_eq!(flags, 4);
- assert_eq!(
- value,
- BlobValue::Encrypted {
- data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
- iv: USRPKEY_AUTHBOUND_IV.to_vec(),
- tag: USRPKEY_AUTHBOUND_TAG.to_vec()
- }
- );
- assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
- assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
- } else {
- panic!("");
- }
-
- if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) =
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
- {
- assert_eq!(flags, 4);
- //assert_eq!(value, BlobValue::Encrypted(..));
- assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
- assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
- } else {
- panic!("");
- }
- if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
- legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
- {
- assert_eq!(flags, 0);
- assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
- assert_eq!(&cert[..], LOADED_CERT_NON_AUTHBOUND);
- assert_eq!(&chain[..], LOADED_CACERT_NON_AUTHBOUND);
- } else {
- panic!("");
- }
-
- legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
- legacy_blob_loader
- .remove_keystore_entry(10223, "non_authbound")
- .expect("This should succeed.");
-
- assert_eq!(
- (None, None, None),
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
- );
- assert_eq!(
- (None, None, None),
- legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
- );
-
- // The database should not be empty due to the super key.
- assert!(!legacy_blob_loader.is_empty()?);
- assert!(!legacy_blob_loader.is_empty_user(0)?);
-
- // The database should be considered empty for user 1.
- assert!(legacy_blob_loader.is_empty_user(1)?);
-
- legacy_blob_loader.remove_super_key(0);
-
- // Now it should be empty.
- assert!(legacy_blob_loader.is_empty_user(0)?);
- assert!(legacy_blob_loader.is_empty()?);
-
- Ok(())
- }
-
- struct TestKey(ZVec);
-
- impl crate::utils::AesGcmKey for TestKey {
- fn key(&self) -> &[u8] {
- &self.0
- }
- }
-
- impl Deref for TestKey {
- type Target = [u8];
- fn deref(&self) -> &Self::Target {
- &self.0
- }
- }
-
- #[test]
- fn test_with_encrypted_characteristics() -> anyhow::Result<()> {
- let temp_dir = TempDir::new("test_with_encrypted_characteristics").unwrap();
- std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
-
- let pw: Password = PASSWORD.into();
- let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
- let super_key =
- Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
-
- std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
-
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
- USRPKEY_AUTHBOUND,
- )
- .unwrap();
- make_encrypted_characteristics_file(
- &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
- &super_key,
- KEY_PARAMETERS,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
- USRCERT_AUTHBOUND,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
- CACERT_AUTHBOUND,
- )
- .unwrap();
-
- let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
- assert_eq!(
- legacy_blob_loader
- .load_by_uid_alias(10223, "authbound", &None)
- .unwrap_err()
- .root_cause()
- .downcast_ref::<Error>(),
- Some(&Error::LockedComponent)
- );
-
- assert_eq!(
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(),
- (
- Some((
- Blob {
- flags: 4,
- value: BlobValue::Encrypted {
- data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
- iv: USRPKEY_AUTHBOUND_IV.to_vec(),
- tag: USRPKEY_AUTHBOUND_TAG.to_vec()
- }
- },
- structured_test_params()
- )),
- Some(LOADED_CERT_AUTHBOUND.to_vec()),
- Some(LOADED_CACERT_AUTHBOUND.to_vec())
- )
- );
-
- legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
-
- assert_eq!(
- (None, None, None),
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
- );
-
- // The database should not be empty due to the super key.
- assert!(!legacy_blob_loader.is_empty().unwrap());
- assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
-
- // The database should be considered empty for user 1.
- assert!(legacy_blob_loader.is_empty_user(1).unwrap());
-
- legacy_blob_loader.remove_super_key(0);
-
- // Now it should be empty.
- assert!(legacy_blob_loader.is_empty_user(0).unwrap());
- assert!(legacy_blob_loader.is_empty().unwrap());
-
- Ok(())
- }
-
- #[test]
- fn test_with_encrypted_certificates() -> anyhow::Result<()> {
- let temp_dir = TempDir::new("test_with_encrypted_certificates").unwrap();
- std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
-
- let pw: Password = PASSWORD.into();
- let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
- let super_key =
- Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
-
- std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
-
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
- USRPKEY_AUTHBOUND,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
- USRPKEY_AUTHBOUND_CHR,
- )
- .unwrap();
- make_encrypted_usr_cert_file(
- &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
- &super_key,
- LOADED_CERT_AUTHBOUND,
- )
- .unwrap();
- make_encrypted_ca_cert_file(
- &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
- &super_key,
- LOADED_CACERT_AUTHBOUND,
- )
- .unwrap();
-
- let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
- assert_eq!(
- legacy_blob_loader
- .load_by_uid_alias(10223, "authbound", &None)
- .unwrap_err()
- .root_cause()
- .downcast_ref::<Error>(),
- Some(&Error::LockedComponent)
- );
-
- assert_eq!(
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(),
- (
- Some((
- Blob {
- flags: 4,
- value: BlobValue::Encrypted {
- data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
- iv: USRPKEY_AUTHBOUND_IV.to_vec(),
- tag: USRPKEY_AUTHBOUND_TAG.to_vec()
- }
- },
- structured_test_params_cache()
- )),
- Some(LOADED_CERT_AUTHBOUND.to_vec()),
- Some(LOADED_CACERT_AUTHBOUND.to_vec())
- )
- );
-
- legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
-
- assert_eq!(
- (None, None, None),
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
- );
-
- // The database should not be empty due to the super key.
- assert!(!legacy_blob_loader.is_empty().unwrap());
- assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
-
- // The database should be considered empty for user 1.
- assert!(legacy_blob_loader.is_empty_user(1).unwrap());
-
- legacy_blob_loader.remove_super_key(0);
-
- // Now it should be empty.
- assert!(legacy_blob_loader.is_empty_user(0).unwrap());
- assert!(legacy_blob_loader.is_empty().unwrap());
-
- Ok(())
- }
-
- #[test]
- fn test_in_place_key_migration() -> anyhow::Result<()> {
- let temp_dir = TempDir::new("test_in_place_key_migration").unwrap();
- std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
-
- let pw: Password = PASSWORD.into();
- let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
- let super_key =
- Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
-
- std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
-
- std::fs::write(
- &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
- USRPKEY_AUTHBOUND,
- )
- .unwrap();
- std::fs::write(
- &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
- USRPKEY_AUTHBOUND_CHR,
- )
- .unwrap();
- make_encrypted_usr_cert_file(
- &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
- &super_key,
- LOADED_CERT_AUTHBOUND,
- )
- .unwrap();
- make_encrypted_ca_cert_file(
- &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
- &super_key,
- LOADED_CACERT_AUTHBOUND,
- )
- .unwrap();
-
- let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
- assert_eq!(
- legacy_blob_loader
- .load_by_uid_alias(10223, "authbound", &None)
- .unwrap_err()
- .root_cause()
- .downcast_ref::<Error>(),
- Some(&Error::LockedComponent)
- );
-
- let super_key: Option<Arc<dyn AesGcm>> = Some(super_key);
-
- assert_eq!(
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(),
- (
- Some((
- Blob {
- flags: 4,
- value: BlobValue::Encrypted {
- data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
- iv: USRPKEY_AUTHBOUND_IV.to_vec(),
- tag: USRPKEY_AUTHBOUND_TAG.to_vec()
- }
- },
- structured_test_params_cache()
- )),
- Some(LOADED_CERT_AUTHBOUND.to_vec()),
- Some(LOADED_CACERT_AUTHBOUND.to_vec())
- )
- );
-
- legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap();
-
- assert_eq!(
- legacy_blob_loader
- .load_by_uid_alias(10224, "boundauth", &None)
- .unwrap_err()
- .root_cause()
- .downcast_ref::<Error>(),
- Some(&Error::LockedComponent)
- );
-
- assert_eq!(
- legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(),
- (
- Some((
- Blob {
- flags: 4,
- value: BlobValue::Encrypted {
- data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
- iv: USRPKEY_AUTHBOUND_IV.to_vec(),
- tag: USRPKEY_AUTHBOUND_TAG.to_vec()
- }
- },
- structured_test_params_cache()
- )),
- Some(LOADED_CERT_AUTHBOUND.to_vec()),
- Some(LOADED_CACERT_AUTHBOUND.to_vec())
- )
- );
-
- legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed.");
-
- assert_eq!(
- (None, None, None),
- legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap()
- );
-
- // The database should not be empty due to the super key.
- assert!(!legacy_blob_loader.is_empty().unwrap());
- assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
-
- // The database should be considered empty for user 1.
- assert!(legacy_blob_loader.is_empty_user(1).unwrap());
-
- legacy_blob_loader.remove_super_key(0);
-
- // Now it should be empty.
- assert!(legacy_blob_loader.is_empty_user(0).unwrap());
- assert!(legacy_blob_loader.is_empty().unwrap());
-
- Ok(())
- }
-
- #[test]
- fn list_non_existing_user() -> Result<()> {
- let temp_dir = TempDir::new("list_non_existing_user").unwrap();
- let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
- assert!(legacy_blob_loader.list_user(20)?.is_empty());
-
- Ok(())
- }
-
- #[test]
- fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> {
- let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap();
- let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
- assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty());
-
- Ok(())
- }
-
- #[test]
- fn test_move_keystore_entry() {
- let temp_dir = TempDir::new("test_move_keystore_entry").unwrap();
- std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
-
- const SOME_CONTENT: &[u8] = b"some content";
- const ANOTHER_CONTENT: &[u8] = b"another content";
- const SOME_FILENAME: &str = "some_file";
- const ANOTHER_FILENAME: &str = "another_file";
-
- std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT)
- .unwrap();
-
- std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT)
- .unwrap();
-
- // Non existent source id silently ignored.
- assert!(LegacyBlobLoader::move_keystore_file_if_exists(
- 1,
- 2,
- "non_existent",
- ANOTHER_FILENAME,
- "ignored",
- |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
- )
- .is_ok());
-
- // Content of another_file has not changed.
- let another_content =
- std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
- assert_eq!(&another_content, ANOTHER_CONTENT);
-
- // Check that some_file still exists.
- assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
- // Existing target files are silently overwritten.
-
- assert!(LegacyBlobLoader::move_keystore_file_if_exists(
- 1,
- 2,
- SOME_FILENAME,
- ANOTHER_FILENAME,
- "ignored",
- |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
- )
- .is_ok());
-
- // Content of another_file is now "some content".
- let another_content =
- std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
- assert_eq!(&another_content, SOME_CONTENT);
-
- // Check that some_file no longer exists.
- assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
- }
-}
diff --git a/keystore2/src/legacy_blob/tests.rs b/keystore2/src/legacy_blob/tests.rs
new file mode 100644
index 0000000..53fe03f
--- /dev/null
+++ b/keystore2/src/legacy_blob/tests.rs
@@ -0,0 +1,676 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Tests for legacy keyblob processing.
+
+#![allow(dead_code)]
+use super::*;
+use crate::legacy_blob::test_utils::legacy_blob_test_vectors::*;
+use crate::legacy_blob::test_utils::*;
+use anyhow::{anyhow, Result};
+use keystore2_crypto::aes_gcm_decrypt;
+use keystore2_test_utils::TempDir;
+use rand::Rng;
+use std::convert::TryInto;
+use std::ops::Deref;
+use std::string::FromUtf8Error;
+
+#[test]
+fn decode_encode_alias_test() {
+ static ALIAS: &str = "#({}test[])😗";
+ static ENCODED_ALIAS: &str = "+S+X{}test[]+Y.`-O-H-G";
+ // Second multi byte out of range ------v
+ static ENCODED_ALIAS_ERROR1: &str = "+S+{}test[]+Y";
+ // Incomplete multi byte ------------------------v
+ static ENCODED_ALIAS_ERROR2: &str = "+S+X{}test[]+";
+ // Our encoding: ".`-O-H-G"
+ // is UTF-8: 0xF0 0x9F 0x98 0x97
+ // is UNICODE: U+1F617
+ // is 😗
+ // But +H below is a valid encoding for 0x18 making this sequence invalid UTF-8.
+ static ENCODED_ALIAS_ERROR_UTF8: &str = ".`-O+H-G";
+
+ assert_eq!(ENCODED_ALIAS, &LegacyBlobLoader::encode_alias(ALIAS));
+ assert_eq!(ALIAS, &LegacyBlobLoader::decode_alias(ENCODED_ALIAS).unwrap());
+ assert_eq!(
+ Some(&Error::BadEncoding),
+ LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR1)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>()
+ );
+ assert_eq!(
+ Some(&Error::BadEncoding),
+ LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR2)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>()
+ );
+ assert!(LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR_UTF8)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<FromUtf8Error>()
+ .is_some());
+
+ for _i in 0..100 {
+ // Any valid UTF-8 string should be en- and decoded without loss.
+ let alias_str = rand::thread_rng().gen::<[char; 20]>().iter().collect::<String>();
+ let random_alias = alias_str.as_bytes();
+ let encoded = LegacyBlobLoader::encode_alias(&alias_str);
+ let decoded = match LegacyBlobLoader::decode_alias(&encoded) {
+ Ok(d) => d,
+ Err(_) => panic!("random_alias: {:x?}\nencoded {}", random_alias, encoded),
+ };
+ assert_eq!(random_alias.to_vec(), decoded.bytes().collect::<Vec<u8>>());
+ }
+}
+
+#[test]
+fn read_golden_key_blob_test() -> anyhow::Result<()> {
+ let blob = LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*BLOB, |_, _, _, _, _| {
+ Err(anyhow!("should not be called"))
+ })
+ .unwrap();
+ assert!(!blob.is_encrypted());
+ assert!(!blob.is_fallback());
+ assert!(!blob.is_strongbox());
+ assert!(!blob.is_critical_to_device_encryption());
+ assert_eq!(blob.value(), &BlobValue::Generic([0xde, 0xed, 0xbe, 0xef].to_vec()));
+
+ let blob =
+ LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*REAL_LEGACY_BLOB, |_, _, _, _, _| {
+ Err(anyhow!("should not be called"))
+ })
+ .unwrap();
+ assert!(!blob.is_encrypted());
+ assert!(!blob.is_fallback());
+ assert!(!blob.is_strongbox());
+ assert!(!blob.is_critical_to_device_encryption());
+ assert_eq!(blob.value(), &BlobValue::Decrypted(REAL_LEGACY_BLOB_PAYLOAD.try_into().unwrap()));
+ Ok(())
+}
+
+#[test]
+fn read_aes_gcm_encrypted_key_blob_test() {
+ let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
+ &mut &*AES_GCM_ENCRYPTED_BLOB,
+ |d, iv, tag, salt, key_size| {
+ assert_eq!(salt, None);
+ assert_eq!(key_size, None);
+ assert_eq!(
+ iv,
+ &[
+ 0xbd, 0xdb, 0x8d, 0x69, 0x72, 0x56, 0xf0, 0xf5, 0xa4, 0x02, 0x88, 0x7f, 0x00,
+ 0x00, 0x00, 0x00,
+ ]
+ );
+ assert_eq!(
+ tag,
+ &[
+ 0x50, 0xd9, 0x97, 0x95, 0x37, 0x6e, 0x28, 0x6a, 0x28, 0x9d, 0x51, 0xb9, 0xb9,
+ 0xe0, 0x0b, 0xc3
+ ][..]
+ );
+ aes_gcm_decrypt(d, iv, tag, AES_KEY).context("Trying to decrypt blob.")
+ },
+ )
+ .unwrap();
+ assert!(blob.is_encrypted());
+ assert!(!blob.is_fallback());
+ assert!(!blob.is_strongbox());
+ assert!(!blob.is_critical_to_device_encryption());
+
+ assert_eq!(blob.value(), &BlobValue::Decrypted(DECRYPTED_PAYLOAD.try_into().unwrap()));
+}
+
+#[test]
+fn read_golden_key_blob_too_short_test() {
+ let error =
+ LegacyBlobLoader::new_from_stream_decrypt_with(&mut &BLOB[0..15], |_, _, _, _, _| {
+ Err(anyhow!("should not be called"))
+ })
+ .unwrap_err();
+ assert_eq!(Some(&Error::BadLen), error.root_cause().downcast_ref::<Error>());
+}
+
+#[test]
+fn test_is_empty() {
+ let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir.");
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty."));
+
+ let _db =
+ crate::database::KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database.");
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty."));
+
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty."));
+
+ std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
+
+ std::fs::remove_dir_all(&*temp_dir.build().push("user_0")).expect("Failed to remove user_0.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
+
+ std::fs::remove_dir_all(&*temp_dir.build().push("user_10")).expect("Failed to remove user_10.");
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again."));
+}
+
+#[test]
+fn test_legacy_blobs() -> anyhow::Result<()> {
+ let temp_dir = TempDir::new("legacy_blob_test").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND_CHR,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+ USRCERT_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+ CACERT_AUTHBOUND,
+ )
+ .unwrap();
+
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRPKEY_non_authbound"),
+ USRPKEY_NON_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_non_authbound"),
+ USRPKEY_NON_AUTHBOUND_CHR,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRCERT_non_authbound"),
+ USRCERT_NON_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_CACERT_non_authbound"),
+ CACERT_NON_AUTHBOUND,
+ )
+ .unwrap();
+
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
+ {
+ assert_eq!(flags, 4);
+ assert_eq!(
+ value,
+ BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ );
+ assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
+ assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
+ } else {
+ panic!("");
+ }
+
+ if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) =
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
+ {
+ assert_eq!(flags, 4);
+ //assert_eq!(value, BlobValue::Encrypted(..));
+ assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
+ assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
+ } else {
+ panic!("");
+ }
+ if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
+ {
+ assert_eq!(flags, 0);
+ assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
+ assert_eq!(&cert[..], LOADED_CERT_NON_AUTHBOUND);
+ assert_eq!(&chain[..], LOADED_CACERT_NON_AUTHBOUND);
+ } else {
+ panic!("");
+ }
+
+ legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+ legacy_blob_loader.remove_keystore_entry(10223, "non_authbound").expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
+ );
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty()?);
+ assert!(!legacy_blob_loader.is_empty_user(0)?);
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1)?);
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0)?);
+ assert!(legacy_blob_loader.is_empty()?);
+
+ Ok(())
+}
+
+struct TestKey(ZVec);
+
+impl crate::utils::AesGcmKey for TestKey {
+ fn key(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+impl Deref for TestKey {
+ type Target = [u8];
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+#[test]
+fn test_with_encrypted_characteristics() -> anyhow::Result<()> {
+ let temp_dir = TempDir::new("test_with_encrypted_characteristics").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
+ let super_key =
+ Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND,
+ )
+ .unwrap();
+ make_encrypted_characteristics_file(
+ &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+ &super_key,
+ KEY_PARAMETERS,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+ USRCERT_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+ CACERT_AUTHBOUND,
+ )
+ .unwrap();
+
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert_eq!(
+ legacy_blob_loader
+ .load_by_uid_alias(10223, "authbound", &None)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>(),
+ Some(&Error::LockedComponent)
+ );
+
+ assert_eq!(
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(),
+ (
+ Some((
+ Blob {
+ flags: 4,
+ value: BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ },
+ structured_test_params()
+ )),
+ Some(LOADED_CERT_AUTHBOUND.to_vec()),
+ Some(LOADED_CACERT_AUTHBOUND.to_vec())
+ )
+ );
+
+ legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty().unwrap());
+ assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+ assert!(legacy_blob_loader.is_empty().unwrap());
+
+ Ok(())
+}
+
+#[test]
+fn test_with_encrypted_certificates() -> anyhow::Result<()> {
+ let temp_dir = TempDir::new("test_with_encrypted_certificates").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
+ let super_key =
+ Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND_CHR,
+ )
+ .unwrap();
+ make_encrypted_usr_cert_file(
+ &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+ &super_key,
+ LOADED_CERT_AUTHBOUND,
+ )
+ .unwrap();
+ make_encrypted_ca_cert_file(
+ &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+ &super_key,
+ LOADED_CACERT_AUTHBOUND,
+ )
+ .unwrap();
+
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert_eq!(
+ legacy_blob_loader
+ .load_by_uid_alias(10223, "authbound", &None)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>(),
+ Some(&Error::LockedComponent)
+ );
+
+ assert_eq!(
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(),
+ (
+ Some((
+ Blob {
+ flags: 4,
+ value: BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ },
+ structured_test_params_cache()
+ )),
+ Some(LOADED_CERT_AUTHBOUND.to_vec()),
+ Some(LOADED_CACERT_AUTHBOUND.to_vec())
+ )
+ );
+
+ legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty().unwrap());
+ assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+ assert!(legacy_blob_loader.is_empty().unwrap());
+
+ Ok(())
+}
+
+#[test]
+fn test_in_place_key_migration() -> anyhow::Result<()> {
+ let temp_dir = TempDir::new("test_in_place_key_migration").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
+ let super_key =
+ Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND_CHR,
+ )
+ .unwrap();
+ make_encrypted_usr_cert_file(
+ &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+ &super_key,
+ LOADED_CERT_AUTHBOUND,
+ )
+ .unwrap();
+ make_encrypted_ca_cert_file(
+ &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+ &super_key,
+ LOADED_CACERT_AUTHBOUND,
+ )
+ .unwrap();
+
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert_eq!(
+ legacy_blob_loader
+ .load_by_uid_alias(10223, "authbound", &None)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>(),
+ Some(&Error::LockedComponent)
+ );
+
+ let super_key: Option<Arc<dyn AesGcm>> = Some(super_key);
+
+ assert_eq!(
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(),
+ (
+ Some((
+ Blob {
+ flags: 4,
+ value: BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ },
+ structured_test_params_cache()
+ )),
+ Some(LOADED_CERT_AUTHBOUND.to_vec()),
+ Some(LOADED_CACERT_AUTHBOUND.to_vec())
+ )
+ );
+
+ legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap();
+
+ assert_eq!(
+ legacy_blob_loader
+ .load_by_uid_alias(10224, "boundauth", &None)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>(),
+ Some(&Error::LockedComponent)
+ );
+
+ assert_eq!(
+ legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(),
+ (
+ Some((
+ Blob {
+ flags: 4,
+ value: BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ },
+ structured_test_params_cache()
+ )),
+ Some(LOADED_CERT_AUTHBOUND.to_vec()),
+ Some(LOADED_CACERT_AUTHBOUND.to_vec())
+ )
+ );
+
+ legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap()
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty().unwrap());
+ assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+ assert!(legacy_blob_loader.is_empty().unwrap());
+
+ Ok(())
+}
+
+#[test]
+fn list_non_existing_user() -> Result<()> {
+ let temp_dir = TempDir::new("list_non_existing_user").unwrap();
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert!(legacy_blob_loader.list_user(20)?.is_empty());
+
+ Ok(())
+}
+
+#[test]
+fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> {
+ let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap();
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty());
+
+ Ok(())
+}
+
+#[test]
+fn test_move_keystore_entry() {
+ let temp_dir = TempDir::new("test_move_keystore_entry").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ const SOME_CONTENT: &[u8] = b"some content";
+ const ANOTHER_CONTENT: &[u8] = b"another content";
+ const SOME_FILENAME: &str = "some_file";
+ const ANOTHER_FILENAME: &str = "another_file";
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT).unwrap();
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT)
+ .unwrap();
+
+ // Non existent source id silently ignored.
+ assert!(LegacyBlobLoader::move_keystore_file_if_exists(
+ 1,
+ 2,
+ "non_existent",
+ ANOTHER_FILENAME,
+ "ignored",
+ |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
+ )
+ .is_ok());
+
+ // Content of another_file has not changed.
+ let another_content =
+ std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
+ assert_eq!(&another_content, ANOTHER_CONTENT);
+
+ // Check that some_file still exists.
+ assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
+ // Existing target files are silently overwritten.
+
+ assert!(LegacyBlobLoader::move_keystore_file_if_exists(
+ 1,
+ 2,
+ SOME_FILENAME,
+ ANOTHER_FILENAME,
+ "ignored",
+ |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
+ )
+ .is_ok());
+
+ // Content of another_file is now "some content".
+ let another_content =
+ std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
+ assert_eq!(&another_content, SOME_CONTENT);
+
+ // Check that some_file no longer exists.
+ assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
+}
diff --git a/keystore2/src/legacy_importer.rs b/keystore2/src/legacy_importer.rs
index 045f848..0d8dc4a 100644
--- a/keystore2/src/legacy_importer.rs
+++ b/keystore2/src/legacy_importer.rs
@@ -786,7 +786,7 @@
.context(ks_err!("Trying to load legacy blob."))?;
// Determine if the key needs special handling to be deleted.
- let (need_gc, is_super_encrypted) = km_blob_params
+ let (need_gc, is_super_encrypted, is_de_critical) = km_blob_params
.as_ref()
.map(|(blob, params)| {
let params = match params {
@@ -798,13 +798,18 @@
KeyParameterValue::RollbackResistance == *kp.key_parameter_value()
}),
blob.is_encrypted(),
+ blob.is_critical_to_device_encryption(),
)
})
- .unwrap_or((false, false));
+ .unwrap_or((false, false, false));
if keep_non_super_encrypted_keys && !is_super_encrypted {
continue;
}
+ if uid == rustutils::users::AID_SYSTEM && is_de_critical {
+ log::info!("skip deletion of system key '{alias}' which is DE-critical");
+ continue;
+ }
if need_gc {
let mark_deleted = match km_blob_params
@@ -923,7 +928,7 @@
blob,
&[],
|blob| {
- let _wd = wd::watch("Calling GetKeyCharacteristics.");
+ let _wd = wd::watch("get_key_characteristics_without_app_data: calling IKeyMintDevice::getKeyCharacteristics");
map_km_error(km_dev.getKeyCharacteristics(blob, &[], &[]))
},
|_| Ok(()),
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index 43d99d1..7b6ea68 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -19,16 +19,19 @@
use crate::error::map_km_error;
use crate::error::Error;
use crate::globals::get_keymint_device;
-use crate::globals::{DB, LEGACY_IMPORTER, SUPER_KEY};
+use crate::globals::{DB, ENCODED_MODULE_INFO, LEGACY_IMPORTER, SUPER_KEY};
use crate::ks_err;
use crate::permission::{KeyPerm, KeystorePerm};
use crate::super_key::SuperKeyManager;
use crate::utils::{
- check_get_app_uids_affected_by_sid_permissions, check_key_permission,
+ check_dump_permission, check_get_app_uids_affected_by_sid_permissions, check_key_permission,
check_keystore_permission, uid_to_android_user, watchdog as wd,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel,
+ ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
+};
+use apex_aidl_interface::aidl::android::apex::{
+ IApexService::IApexService,
};
use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::{
BnKeystoreMaintenance, IKeystoreMaintenance,
@@ -36,14 +39,47 @@
use android_security_maintenance::binder::{
BinderFeatures, Interface, Result as BinderResult, Strong, ThreadState,
};
+use android_security_metrics::aidl::android::security::metrics::{
+ KeystoreAtomPayload::KeystoreAtomPayload::StorageStats
+};
use android_system_keystore2::aidl::android::system::keystore2::KeyDescriptor::KeyDescriptor;
use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
-use anyhow::{Context, Result};
+use anyhow::{anyhow, Context, Result};
+use binder::wait_for_interface;
+use bssl_crypto::digest;
+use der::{DerOrd, Encode, asn1::OctetString, asn1::SetOfVec, Sequence};
use keystore2_crypto::Password;
+use rustutils::system_properties::PropertyWatcher;
+use std::cmp::Ordering;
/// Reexport Domain for the benefit of DeleteListener
pub use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
+#[cfg(test)]
+mod tests;
+
+/// Version number of KeyMint V4.
+pub const KEYMINT_V4: i32 = 400;
+
+/// Module information structure for DER-encoding.
+#[derive(Sequence, Debug, PartialEq, Eq)]
+struct ModuleInfo {
+ name: OctetString,
+ version: i64,
+}
+
+impl DerOrd for ModuleInfo {
+ // DER mandates "encodings of the component values of a set-of value shall appear in ascending
+ // order". `der_cmp` serves as a proxy for determining that ordering (though why the `der` crate
+ // requires this is unclear). Essentially, we just need to compare the `name` lengths, and then
+ // if those are equal, the `name`s themselves. (No need to consider `version`s since there can't
+ // be more than one `ModuleInfo` with the same `name` in the set-of `ModuleInfo`s.) We rely on
+ // `OctetString`'s `der_cmp` to do the aforementioned comparison.
+ fn der_cmp(&self, other: &Self) -> std::result::Result<Ordering, der::Error> {
+ self.name.der_cmp(&other.name)
+ }
+}
+
/// The Maintenance module takes a delete listener argument which observes user and namespace
/// deletion events.
pub trait DeleteListener {
@@ -136,19 +172,35 @@
.context(ks_err!("While invoking the delete listener."))
}
- fn call_with_watchdog<F>(sec_level: SecurityLevel, name: &'static str, op: &F) -> Result<()>
+ fn call_with_watchdog<F>(
+ sec_level: SecurityLevel,
+ name: &'static str,
+ op: &F,
+ min_version: Option<i32>,
+ ) -> Result<()>
where
F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>,
{
- let (km_dev, _, _) =
+ let (km_dev, hw_info, _) =
get_keymint_device(&sec_level).context(ks_err!("getting keymint device"))?;
- let _wp = wd::watch_millis_with("In call_with_watchdog", 500, (sec_level, name));
+ if let Some(min_version) = min_version {
+ if hw_info.versionNumber < min_version {
+ log::info!("skipping {name} for {sec_level:?} since its keymint version {} is less than the minimum required version {min_version}", hw_info.versionNumber);
+ return Ok(());
+ }
+ }
+
+ let _wp = wd::watch_millis_with("Maintenance::call_with_watchdog", 500, (sec_level, name));
map_km_error(op(km_dev)).with_context(|| ks_err!("calling {}", name))?;
Ok(())
}
- fn call_on_all_security_levels<F>(name: &'static str, op: F) -> Result<()>
+ fn call_on_all_security_levels<F>(
+ name: &'static str,
+ op: F,
+ min_version: Option<i32>,
+ ) -> Result<()>
where
F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>,
{
@@ -157,19 +209,29 @@
(SecurityLevel::STRONGBOX, "STRONGBOX"),
];
sec_levels.iter().try_fold((), |_result, (sec_level, sec_level_string)| {
- let curr_result = Maintenance::call_with_watchdog(*sec_level, name, &op);
+ let curr_result = Maintenance::call_with_watchdog(*sec_level, name, &op, min_version);
match curr_result {
Ok(()) => log::info!(
"Call to {} succeeded for security level {}.",
name,
&sec_level_string
),
- Err(ref e) => log::error!(
- "Call to {} failed for security level {}: {}.",
- name,
- &sec_level_string,
- e
- ),
+ Err(ref e) => {
+ if *sec_level == SecurityLevel::STRONGBOX
+ && e.downcast_ref::<Error>()
+ == Some(&Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
+ {
+ log::info!("Call to {} failed for StrongBox as it is not available", name);
+ return Ok(());
+ } else {
+ log::error!(
+ "Call to {} failed for security level {}: {}.",
+ name,
+ &sec_level_string,
+ e
+ )
+ }
+ }
}
curr_result
})
@@ -185,7 +247,61 @@
{
log::error!("SUPER_KEY.set_up_boot_level_cache failed:\n{:?}\n:(", e);
}
- Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded())
+
+ if keystore2_flags::attest_modules() {
+ std::thread::spawn(move || {
+ Self::watch_apex_info()
+ .unwrap_or_else(|e| log::error!("watch_apex_info failed: {e:?}"));
+ });
+ } else {
+ rustutils::system_properties::write("keystore.module_hash.sent", "true")
+ .context(ks_err!("failed to set keystore.module_hash.sent to true"))?;
+ }
+ Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded(), None)
+ }
+
+ /// Watch the `apexd.status` system property, and read apex module information once
+ /// it is `activated`.
+ ///
+ /// Blocks waiting for system property changes, so must be run in its own thread.
+ fn watch_apex_info() -> Result<()> {
+ let apex_prop = "apexd.status";
+ log::info!("start monitoring '{apex_prop}' property");
+ let mut w =
+ PropertyWatcher::new(apex_prop).context(ks_err!("PropertyWatcher::new failed"))?;
+ loop {
+ let value = w.read(|_name, value| Ok(value.to_string()));
+ log::info!("property '{apex_prop}' is now '{value:?}'");
+ if matches!(value.as_deref(), Ok("activated")) {
+ let modules =
+ Self::read_apex_info().context(ks_err!("failed to read apex info"))?;
+ Self::set_module_info(modules).context(ks_err!("failed to set module info"))?;
+ rustutils::system_properties::write("keystore.module_hash.sent", "true")
+ .context(ks_err!("failed to set keystore.module_hash.sent to true"))?;
+ break;
+ }
+ log::info!("await a change to '{apex_prop}'...");
+ w.wait(None).context(ks_err!("property wait failed"))?;
+ log::info!("await a change to '{apex_prop}'...notified");
+ }
+ Ok(())
+ }
+
+ fn read_apex_info() -> Result<Vec<ModuleInfo>> {
+ let _wp = wd::watch("read_apex_info via IApexService.getActivePackages()");
+ let apexd: Strong<dyn IApexService> =
+ wait_for_interface("apexservice").context("failed to AIDL connect to apexd")?;
+ let packages = apexd.getActivePackages().context("failed to retrieve active packages")?;
+ packages
+ .into_iter()
+ .map(|pkg| {
+ log::info!("apex modules += {} version {}", pkg.moduleName, pkg.versionCode);
+ let name = OctetString::new(pkg.moduleName.as_bytes()).map_err(|e| {
+ anyhow!("failed to convert '{}' to OCTET_STRING: {e:?}", pkg.moduleName)
+ })?;
+ Ok(ModuleInfo { name, version: pkg.versionCode })
+ })
+ .collect()
}
fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
@@ -241,7 +357,7 @@
.context(ks_err!("Checking permission"))?;
log::info!("In delete_all_keys.");
- Maintenance::call_on_all_security_levels("deleteAllKeys", |dev| dev.deleteAllKeys())
+ Maintenance::call_on_all_security_levels("deleteAllKeys", |dev| dev.deleteAllKeys(), None)
}
fn get_app_uids_affected_by_sid(
@@ -255,9 +371,162 @@
DB.with(|db| db.borrow_mut().get_app_uids_affected_by_sid(user_id, secure_user_id))
.context(ks_err!("Failed to get app UIDs affected by SID"))
}
+
+ fn dump_state(&self, f: &mut dyn std::io::Write) -> std::io::Result<()> {
+ writeln!(f, "keystore2 running")?;
+ writeln!(f)?;
+
+ // Display underlying device information
+ for sec_level in &[SecurityLevel::TRUSTED_ENVIRONMENT, SecurityLevel::STRONGBOX] {
+ let Ok((_dev, hw_info, uuid)) = get_keymint_device(sec_level) else { continue };
+
+ writeln!(f, "Device info for {sec_level:?} with {uuid:?}")?;
+ writeln!(f, " HAL version: {}", hw_info.versionNumber)?;
+ writeln!(f, " Implementation name: {}", hw_info.keyMintName)?;
+ writeln!(f, " Implementation author: {}", hw_info.keyMintAuthorName)?;
+ writeln!(f, " Timestamp token required: {}", hw_info.timestampTokenRequired)?;
+ }
+ writeln!(f)?;
+
+ // Display module attestation information
+ {
+ let info = ENCODED_MODULE_INFO.read().unwrap();
+ if let Some(info) = info.as_ref() {
+ writeln!(f, "Attested module information (DER-encoded):")?;
+ writeln!(f, " {}", hex::encode(info))?;
+ writeln!(f)?;
+ } else {
+ writeln!(f, "Attested module information not set")?;
+ writeln!(f)?;
+ }
+ }
+
+ // Display database size information.
+ match crate::metrics_store::pull_storage_stats() {
+ Ok(atoms) => {
+ writeln!(f, "Database size information (in bytes):")?;
+ for atom in atoms {
+ if let StorageStats(stats) = &atom.payload {
+ let stype = format!("{:?}", stats.storage_type);
+ if stats.unused_size == 0 {
+ writeln!(f, " {:<40}: {:>12}", stype, stats.size)?;
+ } else {
+ writeln!(
+ f,
+ " {:<40}: {:>12} (unused {})",
+ stype, stats.size, stats.unused_size
+ )?;
+ }
+ }
+ }
+ }
+ Err(e) => {
+ writeln!(f, "Failed to retrieve storage stats: {e:?}")?;
+ }
+ }
+ writeln!(f)?;
+
+ // Display database config information.
+ writeln!(f, "Database configuration:")?;
+ DB.with(|db| -> std::io::Result<()> {
+ let pragma_str = |f: &mut dyn std::io::Write, name| -> std::io::Result<()> {
+ let mut db = db.borrow_mut();
+ let value: String = db
+ .pragma(name)
+ .unwrap_or_else(|e| format!("unknown value for '{name}', failed: {e:?}"));
+ writeln!(f, " {name} = {value}")
+ };
+ let pragma_i32 = |f: &mut dyn std::io::Write, name| -> std::io::Result<()> {
+ let mut db = db.borrow_mut();
+ let value: i32 = db.pragma(name).unwrap_or_else(|e| {
+ log::error!("unknown value for '{name}', failed: {e:?}");
+ -1
+ });
+ writeln!(f, " {name} = {value}")
+ };
+ pragma_i32(f, "auto_vacuum")?;
+ pragma_str(f, "journal_mode")?;
+ pragma_i32(f, "journal_size_limit")?;
+ pragma_i32(f, "synchronous")?;
+ pragma_i32(f, "schema_version")?;
+ pragma_i32(f, "user_version")?;
+ Ok(())
+ })?;
+ writeln!(f)?;
+
+ // Display accumulated metrics.
+ writeln!(f, "Metrics information:")?;
+ writeln!(f)?;
+ write!(f, "{:?}", *crate::metrics_store::METRICS_STORE)?;
+ writeln!(f)?;
+
+ // Reminder: any additional information added to the `dump_state()` output needs to be
+ // careful not to include confidential information (e.g. key material).
+
+ Ok(())
+ }
+
+ fn set_module_info(module_info: Vec<ModuleInfo>) -> Result<()> {
+ log::info!("set_module_info with {} modules", module_info.len());
+ let encoding = Self::encode_module_info(module_info)
+ .map_err(|e| anyhow!({ e }))
+ .context(ks_err!("Failed to encode module_info"))?;
+ let hash = digest::Sha256::hash(&encoding).to_vec();
+
+ {
+ let mut saved = ENCODED_MODULE_INFO.write().unwrap();
+ if let Some(saved_encoding) = &*saved {
+ if *saved_encoding == encoding {
+ log::warn!(
+ "Module info already set, ignoring repeated attempt to set same info."
+ );
+ return Ok(());
+ }
+ return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!(
+ "Failed to set module info as it is already set to a different value."
+ ));
+ }
+ *saved = Some(encoding);
+ }
+
+ let kps =
+ vec![KeyParameter { tag: Tag::MODULE_HASH, value: KeyParameterValue::Blob(hash) }];
+
+ Maintenance::call_on_all_security_levels(
+ "setAdditionalAttestationInfo",
+ |dev| dev.setAdditionalAttestationInfo(&kps),
+ Some(KEYMINT_V4),
+ )
+ }
+
+ fn encode_module_info(module_info: Vec<ModuleInfo>) -> Result<Vec<u8>, der::Error> {
+ SetOfVec::<ModuleInfo>::from_iter(module_info.into_iter())?.to_der()
+ }
}
-impl Interface for Maintenance {}
+impl Interface for Maintenance {
+ fn dump(
+ &self,
+ f: &mut dyn std::io::Write,
+ _args: &[&std::ffi::CStr],
+ ) -> Result<(), binder::StatusCode> {
+ if !keystore2_flags::enable_dump() {
+ log::info!("skipping dump() as flag not enabled");
+ return Ok(());
+ }
+ log::info!("dump()");
+ let _wp = wd::watch("IKeystoreMaintenance::dump");
+ check_dump_permission().map_err(|_e| {
+ log::error!("dump permission denied");
+ binder::StatusCode::PERMISSION_DENIED
+ })?;
+
+ self.dump_state(f).map_err(|e| {
+ log::error!("dump_state failed: {e:?}");
+ binder::StatusCode::UNKNOWN_ERROR
+ })
+ }
+}
impl IKeystoreMaintenance for Maintenance {
fn onUserAdded(&self, user_id: i32) -> BinderResult<()> {
@@ -313,7 +582,7 @@
}
fn deleteAllKeys(&self) -> BinderResult<()> {
- log::warn!("deleteAllKeys()");
+ log::warn!("deleteAllKeys() invoked, indicating initial setup or post-factory reset");
let _wp = wd::watch("IKeystoreMaintenance::deleteAllKeys");
Self::delete_all_keys().map_err(into_logged_binder)
}
diff --git a/keystore2/src/maintenance/tests.rs b/keystore2/src/maintenance/tests.rs
new file mode 100644
index 0000000..fbafa73
--- /dev/null
+++ b/keystore2/src/maintenance/tests.rs
@@ -0,0 +1,182 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Maintenance tests.
+use super::*;
+use der::ErrorKind;
+
+#[test]
+fn test_encode_module_info_empty() {
+ let expected = vec![0x31, 0x00];
+ assert_eq!(expected, Maintenance::encode_module_info(Vec::new()).unwrap());
+}
+
+#[test]
+fn test_encode_module_info_same_name() {
+ // Same versions
+ let module_info: Vec<ModuleInfo> = vec![
+ ModuleInfo {
+ name: OctetString::new("com.android.os.statsd".to_string()).unwrap(),
+ version: 25,
+ },
+ ModuleInfo {
+ name: OctetString::new("com.android.os.statsd".to_string()).unwrap(),
+ version: 25,
+ },
+ ];
+ let actual = Maintenance::encode_module_info(module_info);
+ assert!(actual.is_err());
+ assert_eq!(ErrorKind::SetDuplicate, actual.unwrap_err().kind());
+
+ // Different versions
+ let module_info: Vec<ModuleInfo> = vec![
+ ModuleInfo {
+ name: OctetString::new("com.android.os.statsd".to_string()).unwrap(),
+ version: 3,
+ },
+ ModuleInfo {
+ name: OctetString::new("com.android.os.statsd".to_string()).unwrap(),
+ version: 789,
+ },
+ ];
+ let actual = Maintenance::encode_module_info(module_info);
+ assert!(actual.is_err());
+ assert_eq!(ErrorKind::SetDuplicate, actual.unwrap_err().kind());
+}
+
+#[test]
+fn test_encode_module_info_same_name_length() {
+ let module_info: Vec<ModuleInfo> = vec![
+ ModuleInfo { name: OctetString::new("com.android.wifi".to_string()).unwrap(), version: 2 },
+ ModuleInfo { name: OctetString::new("com.android.virt".to_string()).unwrap(), version: 1 },
+ ];
+ let actual = Maintenance::encode_module_info(module_info).unwrap();
+ let expected = hex::decode(concat!(
+ "312e", // SET OF, len 46
+ "3015", // SEQUENCE, len 21
+ "0410", // OCTET STRING, len 16
+ "636f6d2e616e64726f69642e76697274", // "com.android.virt"
+ "020101", // INTEGER len 1 value 1
+ "3015", // SEQUENCE, len 21
+ "0410", // OCTET STRING, len 16
+ "636f6d2e616e64726f69642e77696669", // "com.android.wifi"
+ "020102", // INTEGER len 1 value 2
+ ))
+ .unwrap();
+ assert_eq!(expected, actual);
+}
+
+#[test]
+fn test_encode_module_info_version_irrelevant() {
+ // Versions of the modules are irrelevant for determining encoding order since differing names
+ // guarantee a unique ascending order. See Maintenance::ModuleInfo::der_cmp for more detail.
+ let module_info: Vec<ModuleInfo> = vec![
+ ModuleInfo {
+ name: OctetString::new("com.android.extservices".to_string()).unwrap(),
+ version: 1,
+ },
+ ModuleInfo { name: OctetString::new("com.android.adbd".to_string()).unwrap(), version: 14 },
+ ];
+ let actual = Maintenance::encode_module_info(module_info).unwrap();
+ let expected = hex::decode(concat!(
+ "3135", // SET OF, len 53
+ "3015", // SEQUENCE, len 21
+ "0410", // OCTET STRING, len 16
+ "636f6d2e616e64726f69642e61646264", // "com.android.abdb"
+ "02010e", // INTEGER len 2 value 14
+ "301c", // SEQUENCE, len 28
+ "0417", // OCTET STRING, len 23
+ "636f6d2e616e64726f69642e6578747365727669636573", // "com.android.extservices"
+ "020101", // INTEGER len 1 value 1
+ ))
+ .unwrap();
+ assert_eq!(expected, actual);
+}
+
+#[test]
+fn test_encode_module_info_alphaordering_irrelevant() {
+ // Character ordering of the names of modules is irrelevant for determining encoding order since
+ // differing name lengths guarantee a unique ascending order. See
+ // Maintenance::ModuleInfo::der_cmp for more detail.
+ let module_info: Vec<ModuleInfo> = vec![
+ ModuleInfo {
+ name: OctetString::new("com.android.crashrecovery".to_string()).unwrap(),
+ version: 3,
+ },
+ ModuleInfo { name: OctetString::new("com.android.rkpd".to_string()).unwrap(), version: 8 },
+ ];
+ let actual = Maintenance::encode_module_info(module_info).unwrap();
+ let expected = hex::decode(concat!(
+ "3137", // SET OF, len 55
+ "3015", // SEQUENCE, len 21
+ "0410", // OCTET STRING, len 16
+ "636f6d2e616e64726f69642e726b7064", // "com.android.rkpd"
+ "020108", // INTEGER len 1 value 8
+ "301e", // SEQUENCE, len 30
+ "0419", // OCTET STRING, len 25
+ "636f6d2e616e64726f69642e63726173687265636f76657279", // "com.android.crashrecovery"
+ "020103", // INTEGER len 1 value 3
+ ))
+ .unwrap();
+ assert_eq!(expected, actual);
+}
+
+#[test]
+fn test_encode_module_info() {
+ // Collection of `ModuleInfo`s from a few of the other test_encode_module_info_* tests
+ let module_info: Vec<ModuleInfo> = vec![
+ ModuleInfo { name: OctetString::new("com.android.rkpd".to_string()).unwrap(), version: 8 },
+ ModuleInfo {
+ name: OctetString::new("com.android.extservices".to_string()).unwrap(),
+ version: 1,
+ },
+ ModuleInfo {
+ name: OctetString::new("com.android.crashrecovery".to_string()).unwrap(),
+ version: 3,
+ },
+ ModuleInfo { name: OctetString::new("com.android.wifi".to_string()).unwrap(), version: 2 },
+ ModuleInfo { name: OctetString::new("com.android.virt".to_string()).unwrap(), version: 1 },
+ ModuleInfo { name: OctetString::new("com.android.adbd".to_string()).unwrap(), version: 14 },
+ ];
+ let actual = Maintenance::encode_module_info(module_info).unwrap();
+ let expected = hex::decode(concat!(
+ "31819a", // SET OF, len 154
+ "3015", // SEQUENCE, len 21
+ "0410", // OCTET STRING, len 16
+ "636f6d2e616e64726f69642e61646264", // "com.android.abdb"
+ "02010e", // INTEGER len 2 value 14
+ "3015", // SEQUENCE, len 21
+ "0410", // OCTET STRING, len 16
+ "636f6d2e616e64726f69642e726b7064", // "com.android.rkpd"
+ "020108", // INTEGER len 1 value 8
+ "3015", // SEQUENCE, len 21
+ "0410", // OCTET STRING, len 16
+ "636f6d2e616e64726f69642e76697274", // "com.android.virt"
+ "020101", // INTEGER len 1 value 1
+ "3015", // SEQUENCE, len 21
+ "0410", // OCTET STRING, len 16
+ "636f6d2e616e64726f69642e77696669", // "com.android.wifi"
+ "020102", // INTEGER len 1 value 2
+ "301c", // SEQUENCE, len 28
+ "0417", // OCTET STRING, len 23
+ "636f6d2e616e64726f69642e6578747365727669636573", // "com.android.extservices"
+ "020101", // INTEGER len 1 value 1
+ "301e", // SEQUENCE, len 30
+ "0419", // OCTET STRING, len 25
+ "636f6d2e616e64726f69642e63726173687265636f76657279", // "com.android.crashrecovery"
+ "020103", // INTEGER len 1 value 3
+ ))
+ .unwrap();
+ assert_eq!(expected, actual);
+}
diff --git a/keystore2/src/metrics.rs b/keystore2/src/metrics.rs
index 4757739..b884809 100644
--- a/keystore2/src/metrics.rs
+++ b/keystore2/src/metrics.rs
@@ -51,7 +51,7 @@
impl IKeystoreMetrics for Metrics {
fn pullMetrics(&self, atom_id: AtomID) -> BinderResult<Vec<KeystoreAtom>> {
- let _wp = wd::watch("IKeystoreMetrics::pullMetrics");
+ let _wp = wd::watch_millis_with("IKeystoreMetrics::pullMetrics", 500, atom_id);
self.pull_metrics(atom_id).map_err(into_logged_binder)
}
}
diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs
index 5a76d04..30c5973 100644
--- a/keystore2/src/metrics_store.rs
+++ b/keystore2/src/metrics_store.rs
@@ -22,6 +22,7 @@
use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
use crate::ks_err;
use crate::operation::Outcome;
+use crate::utils::watchdog as wd;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin,
@@ -44,18 +45,18 @@
SecurityLevel::SecurityLevel as MetricsSecurityLevel, Storage::Storage as MetricsStorage,
};
use anyhow::{anyhow, Context, Result};
-use lazy_static::lazy_static;
use std::collections::HashMap;
-use std::sync::Mutex;
+use std::sync::{LazyLock, Mutex};
+
+#[cfg(test)]
+mod tests;
// Note: Crash events are recorded at keystore restarts, based on the assumption that keystore only
// gets restarted after a crash, during a boot cycle.
const KEYSTORE_CRASH_COUNT_PROPERTY: &str = "keystore.crash_count";
-lazy_static! {
- /// Singleton for MetricsStore.
- pub static ref METRICS_STORE: MetricsStore = Default::default();
-}
+/// Singleton for MetricsStore.
+pub static METRICS_STORE: LazyLock<MetricsStore> = LazyLock::new(Default::default);
/// MetricsStore stores the <atom object, count> as <key, value> in the inner hash map,
/// indexed by the atom id, in the outer hash map.
@@ -72,6 +73,26 @@
metrics_store: Mutex<HashMap<AtomID, HashMap<KeystoreAtomPayload, i32>>>,
}
+impl std::fmt::Debug for MetricsStore {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ let store = self.metrics_store.lock().unwrap();
+ let mut atom_ids: Vec<&AtomID> = store.keys().collect();
+ atom_ids.sort();
+ for atom_id in atom_ids {
+ writeln!(f, " {} : [", atom_id.show())?;
+ let data = store.get(atom_id).unwrap();
+ let mut payloads: Vec<&KeystoreAtomPayload> = data.keys().collect();
+ payloads.sort();
+ for payload in payloads {
+ let count = data.get(payload).unwrap();
+ writeln!(f, " {} => count={count}", payload.show())?;
+ }
+ writeln!(f, " ]")?;
+ }
+ Ok(())
+ }
+}
+
impl MetricsStore {
/// There are some atoms whose maximum cardinality exceeds the cardinality limits tolerated
/// by statsd. Statsd tolerates cardinality between 200-300. Therefore, the in-memory storage
@@ -85,13 +106,15 @@
/// empty vector.
pub fn get_atoms(&self, atom_id: AtomID) -> Result<Vec<KeystoreAtom>> {
// StorageStats is an original pulled atom (i.e. not a pushed atom converted to a
- // pulledd atom). Therefore, it is handled separately.
+ // pulled atom). Therefore, it is handled separately.
if AtomID::STORAGE_STATS == atom_id {
+ let _wp = wd::watch("MetricsStore::get_atoms calling pull_storage_stats");
return pull_storage_stats();
}
// Process keystore crash stats.
if AtomID::CRASH_STATS == atom_id {
+ let _wp = wd::watch("MetricsStore::get_atoms calling read_keystore_crash_count");
return match read_keystore_crash_count()? {
Some(count) => Ok(vec![KeystoreAtom {
payload: KeystoreAtomPayload::CrashStats(CrashStats {
@@ -103,8 +126,6 @@
};
}
- // It is safe to call unwrap here since the lock can not be poisoned based on its usage
- // in this module and the lock is not acquired in the same thread before.
let metrics_store_guard = self.metrics_store.lock().unwrap();
metrics_store_guard.get(&atom_id).map_or(Ok(Vec::<KeystoreAtom>::new()), |atom_count_map| {
Ok(atom_count_map
@@ -116,8 +137,6 @@
/// Insert an atom object to the metrics_store indexed by the atom ID.
fn insert_atom(&self, atom_id: AtomID, atom: KeystoreAtomPayload) {
- // It is ok to unwrap here since the mutex cannot be poisoned according to the way it is
- // used in this module. And the lock is not acquired by this thread before.
let mut metrics_store_guard = self.metrics_store.lock().unwrap();
let atom_count_map = metrics_store_guard.entry(atom_id).or_default();
if atom_count_map.len() < MetricsStore::SINGLE_ATOM_STORE_MAX_SIZE {
@@ -189,7 +208,7 @@
};
let mut key_creation_with_auth_info = KeyCreationWithAuthInfo {
- user_auth_type: MetricsHardwareAuthenticatorType::AUTH_TYPE_UNSPECIFIED,
+ user_auth_type: MetricsHardwareAuthenticatorType::NO_AUTH_TYPE,
log10_auth_key_timeout_seconds: -1,
security_level: MetricsSecurityLevel::SECURITY_LEVEL_UNSPECIFIED,
};
@@ -242,6 +261,12 @@
HardwareAuthenticatorType::FINGERPRINT => {
MetricsHardwareAuthenticatorType::FINGERPRINT
}
+ a if a.0
+ == HardwareAuthenticatorType::PASSWORD.0
+ | HardwareAuthenticatorType::FINGERPRINT.0 =>
+ {
+ MetricsHardwareAuthenticatorType::PASSWORD_OR_FINGERPRINT
+ }
HardwareAuthenticatorType::ANY => MetricsHardwareAuthenticatorType::ANY,
_ => MetricsHardwareAuthenticatorType::AUTH_TYPE_UNSPECIFIED,
}
@@ -519,7 +544,7 @@
}
}
-fn pull_storage_stats() -> Result<Vec<KeystoreAtom>> {
+pub(crate) fn pull_storage_stats() -> Result<Vec<KeystoreAtom>> {
let mut atom_vec: Vec<KeystoreAtom> = Vec::new();
let mut append = |stat| {
match stat {
@@ -680,3 +705,281 @@
///Bit position in the KeyPurpose bitmap for Attest Key.
ATTEST_KEY_BIT_POS = 7,
}
+
+/// The various metrics-related types are not defined in this crate, so the orphan
+/// trait rule means that `std::fmt::Debug` cannot be implemented for them.
+/// Instead, create our own local trait that generates a debug string for a type.
+trait Summary {
+ fn show(&self) -> String;
+}
+
+/// Implement the [`Summary`] trait for AIDL-derived pseudo-enums, mapping named enum values to
+/// specified short names, all padded with spaces to the specified width (to allow improved
+/// readability when printed in a group).
+macro_rules! impl_summary_enum {
+ { $enum:ident, $width:literal, $( $variant:ident => $short:literal ),+ $(,)? } => {
+ impl Summary for $enum{
+ fn show(&self) -> String {
+ match self.0 {
+ $(
+ x if x == Self::$variant.0 => format!(concat!("{:",
+ stringify!($width),
+ "}"),
+ $short),
+ )*
+ v => format!("Unknown({})", v),
+ }
+ }
+ }
+ }
+}
+
+impl_summary_enum!(AtomID, 14,
+ STORAGE_STATS => "STORAGE",
+ KEYSTORE2_ATOM_WITH_OVERFLOW => "OVERFLOW",
+ KEY_CREATION_WITH_GENERAL_INFO => "KEYGEN_GENERAL",
+ KEY_CREATION_WITH_AUTH_INFO => "KEYGEN_AUTH",
+ KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO => "KEYGEN_MODES",
+ KEY_OPERATION_WITH_PURPOSE_AND_MODES_INFO => "KEYOP_MODES",
+ KEY_OPERATION_WITH_GENERAL_INFO => "KEYOP_GENERAL",
+ RKP_ERROR_STATS => "RKP_ERR",
+ CRASH_STATS => "CRASH",
+);
+
+impl_summary_enum!(MetricsStorage, 28,
+ STORAGE_UNSPECIFIED => "UNSPECIFIED",
+ KEY_ENTRY => "KEY_ENTRY",
+ KEY_ENTRY_ID_INDEX => "KEY_ENTRY_ID_IDX" ,
+ KEY_ENTRY_DOMAIN_NAMESPACE_INDEX => "KEY_ENTRY_DOMAIN_NS_IDX" ,
+ BLOB_ENTRY => "BLOB_ENTRY",
+ BLOB_ENTRY_KEY_ENTRY_ID_INDEX => "BLOB_ENTRY_KEY_ENTRY_ID_IDX" ,
+ KEY_PARAMETER => "KEY_PARAMETER",
+ KEY_PARAMETER_KEY_ENTRY_ID_INDEX => "KEY_PARAM_KEY_ENTRY_ID_IDX" ,
+ KEY_METADATA => "KEY_METADATA",
+ KEY_METADATA_KEY_ENTRY_ID_INDEX => "KEY_META_KEY_ENTRY_ID_IDX" ,
+ GRANT => "GRANT",
+ AUTH_TOKEN => "AUTH_TOKEN",
+ BLOB_METADATA => "BLOB_METADATA",
+ BLOB_METADATA_BLOB_ENTRY_ID_INDEX => "BLOB_META_BLOB_ENTRY_ID_IDX" ,
+ METADATA => "METADATA",
+ DATABASE => "DATABASE",
+ LEGACY_STORAGE => "LEGACY_STORAGE",
+);
+
+impl_summary_enum!(MetricsAlgorithm, 4,
+ ALGORITHM_UNSPECIFIED => "NONE",
+ RSA => "RSA",
+ EC => "EC",
+ AES => "AES",
+ TRIPLE_DES => "DES",
+ HMAC => "HMAC",
+);
+
+impl_summary_enum!(MetricsEcCurve, 5,
+ EC_CURVE_UNSPECIFIED => "NONE",
+ P_224 => "P-224",
+ P_256 => "P-256",
+ P_384 => "P-384",
+ P_521 => "P-521",
+ CURVE_25519 => "25519",
+);
+
+impl_summary_enum!(MetricsKeyOrigin, 10,
+ ORIGIN_UNSPECIFIED => "UNSPEC",
+ GENERATED => "GENERATED",
+ DERIVED => "DERIVED",
+ IMPORTED => "IMPORTED",
+ RESERVED => "RESERVED",
+ SECURELY_IMPORTED => "SEC-IMPORT",
+);
+
+impl_summary_enum!(MetricsSecurityLevel, 9,
+ SECURITY_LEVEL_UNSPECIFIED => "UNSPEC",
+ SECURITY_LEVEL_SOFTWARE => "SOFTWARE",
+ SECURITY_LEVEL_TRUSTED_ENVIRONMENT => "TEE",
+ SECURITY_LEVEL_STRONGBOX => "STRONGBOX",
+ SECURITY_LEVEL_KEYSTORE => "KEYSTORE",
+);
+
+impl_summary_enum!(MetricsHardwareAuthenticatorType, 8,
+ AUTH_TYPE_UNSPECIFIED => "UNSPEC",
+ NONE => "NONE",
+ PASSWORD => "PASSWD",
+ FINGERPRINT => "FPRINT",
+ PASSWORD_OR_FINGERPRINT => "PW_OR_FP",
+ ANY => "ANY",
+ NO_AUTH_TYPE => "NOAUTH",
+);
+
+impl_summary_enum!(MetricsPurpose, 7,
+ KEY_PURPOSE_UNSPECIFIED => "UNSPEC",
+ ENCRYPT => "ENCRYPT",
+ DECRYPT => "DECRYPT",
+ SIGN => "SIGN",
+ VERIFY => "VERIFY",
+ WRAP_KEY => "WRAPKEY",
+ AGREE_KEY => "AGREEKY",
+ ATTEST_KEY => "ATTESTK",
+);
+
+impl_summary_enum!(MetricsOutcome, 7,
+ OUTCOME_UNSPECIFIED => "UNSPEC",
+ DROPPED => "DROPPED",
+ SUCCESS => "SUCCESS",
+ ABORT => "ABORT",
+ PRUNED => "PRUNED",
+ ERROR => "ERROR",
+);
+
+impl_summary_enum!(MetricsRkpError, 6,
+ RKP_ERROR_UNSPECIFIED => "UNSPEC",
+ OUT_OF_KEYS => "OOKEYS",
+ FALL_BACK_DURING_HYBRID => "FALLBK",
+);
+
+/// Convert an argument into a corresponding format clause. (This is needed because
+/// macro expansion text for repeated inputs needs to mention one of the repeated
+/// inputs.)
+macro_rules! format_clause {
+ { $ignored:ident } => { "{}" }
+}
+
+/// Generate code to print a string corresponding to a bitmask, where the given
+/// enum identifies which bits mean what. If additional bits (not included in
+/// the enum variants) are set, include the whole bitmask in the output so no
+/// information is lost.
+macro_rules! show_enum_bitmask {
+ { $v:expr, $enum:ident, $( $variant:ident => $short:literal ),+ $(,)? } => {
+ {
+ let v: i32 = $v;
+ let mut displayed_mask = 0i32;
+ $(
+ displayed_mask |= 1 << $enum::$variant as i32;
+ )*
+ let undisplayed_mask = !displayed_mask;
+ let undisplayed = v & undisplayed_mask;
+ let extra = if undisplayed == 0 {
+ "".to_string()
+ } else {
+ format!("(full:{v:#010x})")
+ };
+ format!(
+ concat!( $( format_clause!($variant), )* "{}"),
+ $(
+ if v & 1 << $enum::$variant as i32 != 0 { $short } else { "-" },
+ )*
+ extra
+ )
+ }
+ }
+}
+
+fn show_purpose(v: i32) -> String {
+ show_enum_bitmask!(v, KeyPurposeBitPosition,
+ ATTEST_KEY_BIT_POS => "A",
+ AGREE_KEY_BIT_POS => "G",
+ WRAP_KEY_BIT_POS => "W",
+ VERIFY_BIT_POS => "V",
+ SIGN_BIT_POS => "S",
+ DECRYPT_BIT_POS => "D",
+ ENCRYPT_BIT_POS => "E",
+ )
+}
+
+fn show_padding(v: i32) -> String {
+ show_enum_bitmask!(v, PaddingModeBitPosition,
+ PKCS7_BIT_POS => "7",
+ RSA_PKCS1_1_5_SIGN_BIT_POS => "S",
+ RSA_PKCS1_1_5_ENCRYPT_BIT_POS => "E",
+ RSA_PSS_BIT_POS => "P",
+ RSA_OAEP_BIT_POS => "O",
+ NONE_BIT_POSITION => "N",
+ )
+}
+
+fn show_digest(v: i32) -> String {
+ show_enum_bitmask!(v, DigestBitPosition,
+ SHA_2_512_BIT_POS => "5",
+ SHA_2_384_BIT_POS => "3",
+ SHA_2_256_BIT_POS => "2",
+ SHA_2_224_BIT_POS => "4",
+ SHA_1_BIT_POS => "1",
+ MD5_BIT_POS => "M",
+ NONE_BIT_POSITION => "N",
+ )
+}
+
+fn show_blockmode(v: i32) -> String {
+ show_enum_bitmask!(v, BlockModeBitPosition,
+ GCM_BIT_POS => "G",
+ CTR_BIT_POS => "T",
+ CBC_BIT_POS => "C",
+ ECB_BIT_POS => "E",
+ )
+}
+
+impl Summary for KeystoreAtomPayload {
+ fn show(&self) -> String {
+ match self {
+ KeystoreAtomPayload::StorageStats(v) => {
+ format!("{} sz={} unused={}", v.storage_type.show(), v.size, v.unused_size)
+ }
+ KeystoreAtomPayload::KeyCreationWithGeneralInfo(v) => {
+ format!(
+ "{} ksz={:>4} crv={} {} rc={:4} attest? {}",
+ v.algorithm.show(),
+ v.key_size,
+ v.ec_curve.show(),
+ v.key_origin.show(),
+ v.error_code,
+ if v.attestation_requested { "Y" } else { "N" }
+ )
+ }
+ KeystoreAtomPayload::KeyCreationWithAuthInfo(v) => {
+ format!(
+ "auth={} log(time)={:3} sec={}",
+ v.user_auth_type.show(),
+ v.log10_auth_key_timeout_seconds,
+ v.security_level.show()
+ )
+ }
+ KeystoreAtomPayload::KeyCreationWithPurposeAndModesInfo(v) => {
+ format!(
+ "{} purpose={} padding={} digest={} blockmode={}",
+ v.algorithm.show(),
+ show_purpose(v.purpose_bitmap),
+ show_padding(v.padding_mode_bitmap),
+ show_digest(v.digest_bitmap),
+ show_blockmode(v.block_mode_bitmap),
+ )
+ }
+ KeystoreAtomPayload::KeyOperationWithGeneralInfo(v) => {
+ format!(
+ "{} {:>8} upgraded? {} sec={}",
+ v.outcome.show(),
+ v.error_code,
+ if v.key_upgraded { "Y" } else { "N" },
+ v.security_level.show()
+ )
+ }
+ KeystoreAtomPayload::KeyOperationWithPurposeAndModesInfo(v) => {
+ format!(
+ "{} padding={} digest={} blockmode={}",
+ v.purpose.show(),
+ show_padding(v.padding_mode_bitmap),
+ show_digest(v.digest_bitmap),
+ show_blockmode(v.block_mode_bitmap)
+ )
+ }
+ KeystoreAtomPayload::RkpErrorStats(v) => {
+ format!("{} sec={}", v.rkpError.show(), v.security_level.show())
+ }
+ KeystoreAtomPayload::CrashStats(v) => {
+ format!("count={}", v.count_of_crash_events)
+ }
+ KeystoreAtomPayload::Keystore2AtomWithOverflow(v) => {
+ format!("atom={}", v.atom_id.show())
+ }
+ }
+ }
+}
diff --git a/keystore2/src/metrics_store/tests.rs b/keystore2/src/metrics_store/tests.rs
new file mode 100644
index 0000000..95d4a01
--- /dev/null
+++ b/keystore2/src/metrics_store/tests.rs
@@ -0,0 +1,160 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::metrics_store::*;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ HardwareAuthenticatorType::HardwareAuthenticatorType as AuthType, KeyParameter::KeyParameter,
+ KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
+};
+use android_security_metrics::aidl::android::security::metrics::{
+ HardwareAuthenticatorType::HardwareAuthenticatorType as MetricsAuthType,
+ SecurityLevel::SecurityLevel as MetricsSecurityLevel,
+};
+
+#[test]
+fn test_enum_show() {
+ let algo = MetricsAlgorithm::RSA;
+ assert_eq!("RSA ", algo.show());
+ let algo = MetricsAlgorithm(42);
+ assert_eq!("Unknown(42)", algo.show());
+}
+
+#[test]
+fn test_enum_bitmask_show() {
+ let mut modes = 0i32;
+ compute_block_mode_bitmap(&mut modes, BlockMode::ECB);
+ compute_block_mode_bitmap(&mut modes, BlockMode::CTR);
+
+ assert_eq!(show_blockmode(modes), "-T-E");
+
+ // Add some bits not covered by the enum of valid bit positions.
+ modes |= 0xa0;
+ assert_eq!(show_blockmode(modes), "-T-E(full:0x000000aa)");
+ modes |= 0x300;
+ assert_eq!(show_blockmode(modes), "-T-E(full:0x000003aa)");
+}
+
+fn create_key_param_with_auth_type(auth_type: AuthType) -> KeyParameter {
+ KeyParameter {
+ tag: Tag::USER_AUTH_TYPE,
+ value: KeyParameterValue::HardwareAuthenticatorType(auth_type),
+ }
+}
+
+#[test]
+fn test_user_auth_type() {
+ let test_cases = [
+ (vec![], MetricsAuthType::NO_AUTH_TYPE),
+ (vec![AuthType::NONE], MetricsAuthType::NONE),
+ (vec![AuthType::PASSWORD], MetricsAuthType::PASSWORD),
+ (vec![AuthType::FINGERPRINT], MetricsAuthType::FINGERPRINT),
+ (
+ vec![AuthType(AuthType::PASSWORD.0 | AuthType::FINGERPRINT.0)],
+ MetricsAuthType::PASSWORD_OR_FINGERPRINT,
+ ),
+ (vec![AuthType::ANY], MetricsAuthType::ANY),
+ // 7 is the "next" undefined HardwareAuthenticatorType enum tag number, so
+ // force this test to fail and be updated if someone adds a new enum value.
+ (vec![AuthType(7)], MetricsAuthType::AUTH_TYPE_UNSPECIFIED),
+ (vec![AuthType(123)], MetricsAuthType::AUTH_TYPE_UNSPECIFIED),
+ (
+ // In practice, Tag::USER_AUTH_TYPE isn't a repeatable tag. It's allowed
+ // to appear once for auth-bound keys and contains the binary OR of the
+ // applicable auth types. However, this test case repeats the tag more
+ // than once in order to unit test the logic that constructs the atom.
+ vec![AuthType::ANY, AuthType(123), AuthType::PASSWORD],
+ // The last auth type wins.
+ MetricsAuthType::PASSWORD,
+ ),
+ ];
+ for (auth_types, expected) in test_cases {
+ let key_params: Vec<_> =
+ auth_types.iter().map(|a| create_key_param_with_auth_type(*a)).collect();
+ let (_, atom_with_auth_info, _) = process_key_creation_event_stats(
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ &key_params,
+ &Ok(()),
+ );
+ assert!(matches!(
+ atom_with_auth_info,
+ KeystoreAtomPayload::KeyCreationWithAuthInfo(a) if a.user_auth_type == expected
+ ));
+ }
+}
+
+fn create_key_param_with_auth_timeout(timeout: i32) -> KeyParameter {
+ KeyParameter { tag: Tag::AUTH_TIMEOUT, value: KeyParameterValue::Integer(timeout) }
+}
+
+#[test]
+fn test_log_auth_timeout_seconds() {
+ let test_cases = [
+ (vec![], -1),
+ (vec![-1], 0),
+ // The metrics code computes the value of this field for a timeout `t` with
+ // `f32::log10(t as f32) as i32`. The result of f32::log10(0 as f32) is `-inf`.
+ // Casting this to i32 means it gets "rounded" to i32::MIN, which is -2147483648.
+ (vec![0], -2147483648),
+ (vec![1], 0),
+ (vec![9], 0),
+ (vec![10], 1),
+ (vec![999], 2),
+ (
+ // In practice, Tag::AUTH_TIMEOUT isn't a repeatable tag. It's allowed to
+ // appear once for auth-bound keys. However, this test case repeats the
+ // tag more than once in order to unit test the logic that constructs the
+ // atom.
+ vec![1, 0, 10],
+ // The last timeout wins.
+ 1,
+ ),
+ ];
+ for (timeouts, expected) in test_cases {
+ let key_params: Vec<_> =
+ timeouts.iter().map(|t| create_key_param_with_auth_timeout(*t)).collect();
+ let (_, atom_with_auth_info, _) = process_key_creation_event_stats(
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ &key_params,
+ &Ok(()),
+ );
+ assert!(matches!(
+ atom_with_auth_info,
+ KeystoreAtomPayload::KeyCreationWithAuthInfo(a)
+ if a.log10_auth_key_timeout_seconds == expected
+ ));
+ }
+}
+
+#[test]
+fn test_security_level() {
+ let test_cases = [
+ (SecurityLevel::SOFTWARE, MetricsSecurityLevel::SECURITY_LEVEL_SOFTWARE),
+ (
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ MetricsSecurityLevel::SECURITY_LEVEL_TRUSTED_ENVIRONMENT,
+ ),
+ (SecurityLevel::STRONGBOX, MetricsSecurityLevel::SECURITY_LEVEL_STRONGBOX),
+ (SecurityLevel::KEYSTORE, MetricsSecurityLevel::SECURITY_LEVEL_KEYSTORE),
+ (SecurityLevel(123), MetricsSecurityLevel::SECURITY_LEVEL_UNSPECIFIED),
+ ];
+ for (security_level, expected) in test_cases {
+ let (_, atom_with_auth_info, _) =
+ process_key_creation_event_stats(security_level, &[], &Ok(()));
+ assert!(matches!(
+ atom_with_auth_info,
+ KeystoreAtomPayload::KeyCreationWithAuthInfo(a)
+ if a.security_level == expected
+ ));
+ }
+}
diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs
index 7d988e1..c11c1f4 100644
--- a/keystore2/src/operation.rs
+++ b/keystore2/src/operation.rs
@@ -31,6 +31,7 @@
//! * `abort` is called.
//! * The operation gets dropped.
//! * The operation gets pruned.
+//!
//! `Operation` has an `Outcome` member. While the outcome is `Outcome::Unknown`,
//! the operation is active and in a good state. Any of the above conditions may
//! change the outcome to one of the defined outcomes Success, Abort, Dropped,
@@ -286,7 +287,7 @@
}
*locked_outcome = Outcome::Pruned;
- let _wp = wd::watch("In Operation::prune: calling abort()");
+ let _wp = wd::watch("Operation::prune: calling IKeyMintOperation::abort()");
// We abort the operation. If there was an error we log it but ignore it.
if let Err(e) = map_km_error(self.km_op.abort()) {
@@ -307,9 +308,8 @@
locked_outcome: &mut Outcome,
err: Result<T, Error>,
) -> Result<T, Error> {
- match &err {
- Err(e) => *locked_outcome = Outcome::ErrorCode(error_to_serialized_error(e)),
- Ok(_) => (),
+ if let Err(e) = &err {
+ *locked_outcome = Outcome::ErrorCode(error_to_serialized_error(e))
}
err
}
@@ -362,7 +362,7 @@
.context(ks_err!("Trying to get auth tokens."))?;
self.update_outcome(&mut outcome, {
- let _wp = wd::watch("Operation::update_aad: calling updateAad");
+ let _wp = wd::watch("Operation::update_aad: calling IKeyMintOperation::updateAad");
map_km_error(self.km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref()))
})
.context(ks_err!("Update failed."))?;
@@ -386,7 +386,7 @@
let output = self
.update_outcome(&mut outcome, {
- let _wp = wd::watch("Operation::update: calling update");
+ let _wp = wd::watch("Operation::update: calling IKeyMintOperation::update");
map_km_error(self.km_op.update(input, hat.as_ref(), tst.as_ref()))
})
.context(ks_err!("Update failed."))?;
@@ -416,7 +416,7 @@
let output = self
.update_outcome(&mut outcome, {
- let _wp = wd::watch("Operation::finish: calling finish");
+ let _wp = wd::watch("Operation::finish: calling IKeyMintOperation::finish");
map_km_error(self.km_op.finish(
input,
signature,
@@ -447,7 +447,7 @@
*locked_outcome = outcome;
{
- let _wp = wd::watch("Operation::abort: calling abort");
+ let _wp = wd::watch("Operation::abort: calling IKeyMintOperation::abort");
map_km_error(self.km_op.abort()).context(ks_err!("KeyMint::abort failed."))
}
}
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index 982bc82..023774f 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -26,11 +26,11 @@
};
use anyhow::Context as AnyhowContext;
use keystore2_selinux as selinux;
-use lazy_static::lazy_static;
use selinux::{implement_class, Backend, ClassPermission};
use std::cmp::PartialEq;
use std::convert::From;
use std::ffi::CStr;
+use std::sync::LazyLock;
// Replace getcon with a mock in the test situation
#[cfg(not(test))]
@@ -38,12 +38,13 @@
#[cfg(test)]
use tests::test_getcon as getcon;
-lazy_static! {
- // Panicking here is allowed because keystore cannot function without this backend
- // and it would happen early and indicate a gross misconfiguration of the device.
- static ref KEYSTORE2_KEY_LABEL_BACKEND: selinux::KeystoreKeyBackend =
- selinux::KeystoreKeyBackend::new().unwrap();
-}
+#[cfg(test)]
+mod tests;
+
+// Panicking here is allowed because keystore cannot function without this backend
+// and it would happen early and indicate a gross misconfiguration of the device.
+static KEYSTORE2_KEY_LABEL_BACKEND: LazyLock<selinux::KeystoreKeyBackend> =
+ LazyLock::new(|| selinux::KeystoreKeyBackend::new().unwrap());
fn lookup_keystore2_key_context(namespace: i64) -> anyhow::Result<selinux::Context> {
KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string())
@@ -281,12 +282,19 @@
/// SELinux keystore key backend, and the result is used
/// as target context.
pub fn check_grant_permission(
+ caller_uid: u32,
caller_ctx: &CStr,
access_vec: KeyPermSet,
key: &KeyDescriptor,
) -> anyhow::Result<()> {
let target_context = match key.domain {
- Domain::APP => getcon().context("check_grant_permission: getcon failed.")?,
+ Domain::APP => {
+ if caller_uid as i64 != key.nspace {
+ return Err(selinux::Error::perm())
+ .context("Trying to access key without ownership.");
+ }
+ getcon().context("check_grant_permission: getcon failed.")?
+ }
Domain::SELINUX => lookup_keystore2_key_context(key.nspace)
.context("check_grant_permission: Domain::SELINUX: Failed to lookup namespace.")?,
_ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)),
@@ -397,433 +405,3 @@
selinux::check_permission(caller_ctx, &target_context, perm)
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use anyhow::anyhow;
- use anyhow::Result;
- use keystore2_selinux::*;
-
- const ALL_PERMS: KeyPermSet = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::UseDevId,
- KeyPerm::ReqForcedOp,
- KeyPerm::GenUniqueId,
- KeyPerm::Grant,
- KeyPerm::GetInfo,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- KeyPerm::ConvertStorageKeyToEphemeral,
- ];
-
- const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![
- KeyPerm::Delete,
- KeyPerm::UseDevId,
- // No KeyPerm::Grant
- KeyPerm::GetInfo,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- ];
-
- const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::UseDevId,
- KeyPerm::ReqForcedOp,
- KeyPerm::GenUniqueId,
- // No KeyPerm::Grant
- KeyPerm::GetInfo,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- KeyPerm::ConvertStorageKeyToEphemeral,
- ];
-
- const UNPRIV_PERMS: KeyPermSet = key_perm_set![
- KeyPerm::Delete,
- KeyPerm::GetInfo,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- ];
-
- /// The su_key namespace as defined in su.te and keystore_key_contexts of the
- /// SePolicy (system/sepolicy).
- const SU_KEY_NAMESPACE: i32 = 0;
- /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
- /// SePolicy (system/sepolicy).
- const SHELL_KEY_NAMESPACE: i32 = 1;
-
- pub fn test_getcon() -> Result<Context> {
- Context::new("u:object_r:keystore:s0")
- }
-
- // This macro evaluates the given expression and checks that
- // a) evaluated to Result::Err() and that
- // b) the wrapped error is selinux::Error::perm() (permission denied).
- // We use a macro here because a function would mask which invocation caused the failure.
- //
- // TODO b/164121720 Replace this macro with a function when `track_caller` is available.
- macro_rules! assert_perm_failed {
- ($test_function:expr) => {
- let result = $test_function;
- assert!(result.is_err(), "Permission check should have failed.");
- assert_eq!(
- Some(&selinux::Error::perm()),
- result.err().unwrap().root_cause().downcast_ref::<selinux::Error>()
- );
- };
- }
-
- fn check_context() -> Result<(selinux::Context, i32, bool)> {
- // Calling the non mocked selinux::getcon here intended.
- let context = selinux::getcon()?;
- match context.to_str().unwrap() {
- "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
- "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
- c => Err(anyhow!(format!(
- "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
- c
- ))),
- }
- }
-
- #[test]
- fn check_keystore_permission_test() -> Result<()> {
- let system_server_ctx = Context::new("u:r:system_server:s0")?;
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Lock).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Reset).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Unlock).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangeUser).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok());
- let shell_ctx = Context::new("u:r:shell:s0")?;
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::List));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Lock));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Reset));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Unlock));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangeUser));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID));
- Ok(())
- }
-
- #[test]
- fn check_grant_permission_app() -> Result<()> {
- let system_server_ctx = Context::new("u:r:system_server:s0")?;
- let shell_ctx = Context::new("u:r:shell:s0")?;
- let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
- check_grant_permission(&system_server_ctx, SYSTEM_SERVER_PERMISSIONS_NO_GRANT, &key)
- .expect("Grant permission check failed.");
-
- // attempts to grant the grant permission must always fail even when privileged.
- assert_perm_failed!(check_grant_permission(
- &system_server_ctx,
- KeyPerm::Grant.into(),
- &key
- ));
- // unprivileged grant attempts always fail. shell does not have the grant permission.
- assert_perm_failed!(check_grant_permission(&shell_ctx, UNPRIV_PERMS, &key));
- Ok(())
- }
-
- #[test]
- fn check_grant_permission_selinux() -> Result<()> {
- let (sctx, namespace, is_su) = check_context()?;
- let key = KeyDescriptor {
- domain: Domain::SELINUX,
- nspace: namespace as i64,
- alias: None,
- blob: None,
- };
- if is_su {
- assert!(check_grant_permission(&sctx, NOT_GRANT_PERMS, &key).is_ok());
- // attempts to grant the grant permission must always fail even when privileged.
- assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::Grant.into(), &key));
- } else {
- // unprivileged grant attempts always fail. shell does not have the grant permission.
- assert_perm_failed!(check_grant_permission(&sctx, UNPRIV_PERMS, &key));
- }
- Ok(())
- }
-
- #[test]
- fn check_key_permission_domain_grant() -> Result<()> {
- let key = KeyDescriptor { domain: Domain::GRANT, nspace: 0, alias: None, blob: None };
-
- assert_perm_failed!(check_key_permission(
- 0,
- &selinux::Context::new("ignored").unwrap(),
- KeyPerm::Grant,
- &key,
- &Some(UNPRIV_PERMS)
- ));
-
- check_key_permission(
- 0,
- &selinux::Context::new("ignored").unwrap(),
- KeyPerm::Use,
- &key,
- &Some(ALL_PERMS),
- )
- }
-
- #[test]
- fn check_key_permission_domain_app() -> Result<()> {
- let system_server_ctx = Context::new("u:r:system_server:s0")?;
- let shell_ctx = Context::new("u:r:shell:s0")?;
- let gmscore_app = Context::new("u:r:gmscore_app:s0")?;
-
- let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
-
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Use, &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Delete, &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Rebind, &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Update, &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Grant, &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::UseDevId, &key, &None).is_ok());
- assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &key, &None).is_ok());
-
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::Use, &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::Delete, &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::Rebind, &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::Update, &key, &None).is_ok());
- assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::Grant, &key, &None));
- assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ReqForcedOp, &key, &None));
- assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None));
- assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None));
- assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &key, &None));
-
- // Also make sure that the permission fails if the caller is not the owner.
- assert_perm_failed!(check_key_permission(
- 1, // the owner is 0
- &system_server_ctx,
- KeyPerm::Use,
- &key,
- &None
- ));
- // Unless there was a grant.
- assert!(check_key_permission(
- 1,
- &system_server_ctx,
- KeyPerm::Use,
- &key,
- &Some(key_perm_set![KeyPerm::Use])
- )
- .is_ok());
- // But fail if the grant did not cover the requested permission.
- assert_perm_failed!(check_key_permission(
- 1,
- &system_server_ctx,
- KeyPerm::Use,
- &key,
- &Some(key_perm_set![KeyPerm::GetInfo])
- ));
-
- Ok(())
- }
-
- #[test]
- fn check_key_permission_domain_selinux() -> Result<()> {
- let (sctx, namespace, is_su) = check_context()?;
- let key = KeyDescriptor {
- domain: Domain::SELINUX,
- nspace: namespace as i64,
- alias: None,
- blob: None,
- };
-
- assert!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::Delete, &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::GetInfo, &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::Rebind, &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::Update, &key, &None).is_ok());
-
- if is_su {
- assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok());
- } else {
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None));
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None));
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None));
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None));
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None));
- }
- Ok(())
- }
-
- #[test]
- fn check_key_permission_domain_blob() -> Result<()> {
- let (sctx, namespace, is_su) = check_context()?;
- let key = KeyDescriptor {
- domain: Domain::BLOB,
- nspace: namespace as i64,
- alias: None,
- blob: None,
- };
-
- if is_su {
- check_key_permission(0, &sctx, KeyPerm::Use, &key, &None)
- } else {
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None));
- Ok(())
- }
- }
-
- #[test]
- fn check_key_permission_domain_key_id() -> Result<()> {
- let key = KeyDescriptor { domain: Domain::KEY_ID, nspace: 0, alias: None, blob: None };
-
- assert_eq!(
- Some(&KsError::sys()),
- check_key_permission(
- 0,
- &selinux::Context::new("ignored").unwrap(),
- KeyPerm::Use,
- &key,
- &None
- )
- .err()
- .unwrap()
- .root_cause()
- .downcast_ref::<KsError>()
- );
- Ok(())
- }
-
- #[test]
- fn key_perm_set_all_test() {
- let v = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::UseDevId,
- KeyPerm::ReqForcedOp,
- KeyPerm::GenUniqueId,
- KeyPerm::Grant,
- KeyPerm::GetInfo,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use // Test if the macro accepts missing comma at the end of the list.
- ];
- let mut i = v.into_iter();
- assert_eq!(i.next().unwrap().name(), "delete");
- assert_eq!(i.next().unwrap().name(), "gen_unique_id");
- assert_eq!(i.next().unwrap().name(), "get_info");
- assert_eq!(i.next().unwrap().name(), "grant");
- assert_eq!(i.next().unwrap().name(), "manage_blob");
- assert_eq!(i.next().unwrap().name(), "rebind");
- assert_eq!(i.next().unwrap().name(), "req_forced_op");
- assert_eq!(i.next().unwrap().name(), "update");
- assert_eq!(i.next().unwrap().name(), "use");
- assert_eq!(i.next().unwrap().name(), "use_dev_id");
- assert_eq!(None, i.next());
- }
- #[test]
- fn key_perm_set_sparse_test() {
- let v = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::ReqForcedOp,
- KeyPerm::GenUniqueId,
- KeyPerm::Update,
- KeyPerm::Use, // Test if macro accepts the comma at the end of the list.
- ];
- let mut i = v.into_iter();
- assert_eq!(i.next().unwrap().name(), "gen_unique_id");
- assert_eq!(i.next().unwrap().name(), "manage_blob");
- assert_eq!(i.next().unwrap().name(), "req_forced_op");
- assert_eq!(i.next().unwrap().name(), "update");
- assert_eq!(i.next().unwrap().name(), "use");
- assert_eq!(None, i.next());
- }
- #[test]
- fn key_perm_set_empty_test() {
- let v = key_perm_set![];
- let mut i = v.into_iter();
- assert_eq!(None, i.next());
- }
- #[test]
- fn key_perm_set_include_subset_test() {
- let v1 = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::UseDevId,
- KeyPerm::ReqForcedOp,
- KeyPerm::GenUniqueId,
- KeyPerm::Grant,
- KeyPerm::GetInfo,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- ];
- let v2 = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- ];
- assert!(v1.includes(v2));
- assert!(!v2.includes(v1));
- }
- #[test]
- fn key_perm_set_include_equal_test() {
- let v1 = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- ];
- let v2 = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- ];
- assert!(v1.includes(v2));
- assert!(v2.includes(v1));
- }
- #[test]
- fn key_perm_set_include_overlap_test() {
- let v1 = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::Grant, // only in v1
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- ];
- let v2 = key_perm_set![
- KeyPerm::ManageBlob,
- KeyPerm::Delete,
- KeyPerm::ReqForcedOp, // only in v2
- KeyPerm::Rebind,
- KeyPerm::Update,
- KeyPerm::Use,
- ];
- assert!(!v1.includes(v2));
- assert!(!v2.includes(v1));
- }
- #[test]
- fn key_perm_set_include_no_overlap_test() {
- let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,];
- let v2 =
- key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,];
- assert!(!v1.includes(v2));
- assert!(!v2.includes(v1));
- }
-}
diff --git a/keystore2/src/permission/tests.rs b/keystore2/src/permission/tests.rs
new file mode 100644
index 0000000..68c9b74
--- /dev/null
+++ b/keystore2/src/permission/tests.rs
@@ -0,0 +1,430 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Access control tests.
+
+use super::*;
+use crate::key_perm_set;
+use anyhow::anyhow;
+use anyhow::Result;
+use keystore2_selinux::*;
+
+const ALL_PERMS: KeyPermSet = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Grant,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ KeyPerm::ConvertStorageKeyToEphemeral,
+];
+
+const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ // No KeyPerm::Grant
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+];
+
+const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ // No KeyPerm::Grant
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ KeyPerm::ConvertStorageKeyToEphemeral,
+];
+
+const UNPRIV_PERMS: KeyPermSet = key_perm_set![
+ KeyPerm::Delete,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+];
+
+/// The su_key namespace as defined in su.te and keystore_key_contexts of the
+/// SePolicy (system/sepolicy).
+const SU_KEY_NAMESPACE: i32 = 0;
+/// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
+/// SePolicy (system/sepolicy).
+const SHELL_KEY_NAMESPACE: i32 = 1;
+
+pub fn test_getcon() -> Result<Context> {
+ Context::new("u:object_r:keystore:s0")
+}
+
+// This macro evaluates the given expression and checks that
+// a) evaluated to Result::Err() and that
+// b) the wrapped error is selinux::Error::perm() (permission denied).
+// We use a macro here because a function would mask which invocation caused the failure.
+//
+// TODO b/164121720 Replace this macro with a function when `track_caller` is available.
+macro_rules! assert_perm_failed {
+ ($test_function:expr) => {
+ let result = $test_function;
+ assert!(result.is_err(), "Permission check should have failed.");
+ assert_eq!(
+ Some(&selinux::Error::perm()),
+ result.err().unwrap().root_cause().downcast_ref::<selinux::Error>()
+ );
+ };
+}
+
+fn check_context() -> Result<(selinux::Context, i32, bool)> {
+ // Calling the non mocked selinux::getcon here intended.
+ let context = selinux::getcon()?;
+ match context.to_str().unwrap() {
+ "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
+ "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
+ c => Err(anyhow!(format!(
+ "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
+ c
+ ))),
+ }
+}
+
+#[test]
+fn check_keystore_permission_test() -> Result<()> {
+ let system_server_ctx = Context::new("u:r:system_server:s0")?;
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Lock).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Reset).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Unlock).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangeUser).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok());
+ let shell_ctx = Context::new("u:r:shell:s0")?;
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::List));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Lock));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Reset));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Unlock));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangeUser));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID));
+ Ok(())
+}
+
+#[test]
+fn check_grant_permission_app() -> Result<()> {
+ let system_server_ctx = Context::new("u:r:system_server:s0")?;
+ let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
+ check_grant_permission(0, &system_server_ctx, SYSTEM_SERVER_PERMISSIONS_NO_GRANT, &key)
+ .expect("Grant permission check failed.");
+
+ // attempts to grant the grant permission must always fail even when privileged.
+ assert_perm_failed!(check_grant_permission(0, &system_server_ctx, KeyPerm::Grant.into(), &key));
+ Ok(())
+}
+
+#[test]
+fn check_grant_permission_selinux() -> Result<()> {
+ let (sctx, namespace, is_su) = check_context()?;
+ let key = KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: namespace as i64,
+ alias: None,
+ blob: None,
+ };
+ if is_su {
+ assert!(check_grant_permission(0, &sctx, NOT_GRANT_PERMS, &key).is_ok());
+ // attempts to grant the grant permission must always fail even when privileged.
+ assert_perm_failed!(check_grant_permission(0, &sctx, KeyPerm::Grant.into(), &key));
+ } else {
+ // unprivileged grant attempts always fail. shell does not have the grant permission.
+ assert_perm_failed!(check_grant_permission(0, &sctx, UNPRIV_PERMS, &key));
+ }
+ Ok(())
+}
+
+#[test]
+fn check_key_permission_domain_grant() -> Result<()> {
+ let key = KeyDescriptor { domain: Domain::GRANT, nspace: 0, alias: None, blob: None };
+
+ assert_perm_failed!(check_key_permission(
+ 0,
+ &selinux::Context::new("ignored").unwrap(),
+ KeyPerm::Grant,
+ &key,
+ &Some(UNPRIV_PERMS)
+ ));
+
+ check_key_permission(
+ 0,
+ &selinux::Context::new("ignored").unwrap(),
+ KeyPerm::Use,
+ &key,
+ &Some(ALL_PERMS),
+ )
+}
+
+#[test]
+fn check_key_permission_domain_app() -> Result<()> {
+ let system_server_ctx = Context::new("u:r:system_server:s0")?;
+ let shell_ctx = Context::new("u:r:shell:s0")?;
+ let gmscore_app = Context::new("u:r:gmscore_app:s0")?;
+
+ let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
+
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Use, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Delete, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Rebind, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Update, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Grant, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::UseDevId, &key, &None).is_ok());
+ assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &key, &None).is_ok());
+
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::Use, &key, &None).is_ok());
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::Delete, &key, &None).is_ok());
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::Rebind, &key, &None).is_ok());
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::Update, &key, &None).is_ok());
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ReqForcedOp, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &key, &None));
+
+ // Also make sure that the permission fails if the caller is not the owner.
+ assert_perm_failed!(check_key_permission(
+ 1, // the owner is 0
+ &system_server_ctx,
+ KeyPerm::Use,
+ &key,
+ &None
+ ));
+ // Unless there was a grant.
+ assert!(check_key_permission(
+ 1,
+ &system_server_ctx,
+ KeyPerm::Use,
+ &key,
+ &Some(key_perm_set![KeyPerm::Use])
+ )
+ .is_ok());
+ // But fail if the grant did not cover the requested permission.
+ assert_perm_failed!(check_key_permission(
+ 1,
+ &system_server_ctx,
+ KeyPerm::Use,
+ &key,
+ &Some(key_perm_set![KeyPerm::GetInfo])
+ ));
+
+ Ok(())
+}
+
+#[test]
+fn check_key_permission_domain_selinux() -> Result<()> {
+ let (sctx, namespace, is_su) = check_context()?;
+ let key = KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: namespace as i64,
+ alias: None,
+ blob: None,
+ };
+
+ assert!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::Delete, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::GetInfo, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::Rebind, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::Update, &key, &None).is_ok());
+
+ if is_su {
+ assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok());
+ } else {
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None));
+ }
+ Ok(())
+}
+
+#[test]
+fn check_key_permission_domain_blob() -> Result<()> {
+ let (sctx, namespace, is_su) = check_context()?;
+ let key =
+ KeyDescriptor { domain: Domain::BLOB, nspace: namespace as i64, alias: None, blob: None };
+
+ if is_su {
+ check_key_permission(0, &sctx, KeyPerm::Use, &key, &None)
+ } else {
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None));
+ Ok(())
+ }
+}
+
+#[test]
+fn check_key_permission_domain_key_id() -> Result<()> {
+ let key = KeyDescriptor { domain: Domain::KEY_ID, nspace: 0, alias: None, blob: None };
+
+ assert_eq!(
+ Some(&KsError::sys()),
+ check_key_permission(
+ 0,
+ &selinux::Context::new("ignored").unwrap(),
+ KeyPerm::Use,
+ &key,
+ &None
+ )
+ .err()
+ .unwrap()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+ Ok(())
+}
+
+#[test]
+fn key_perm_set_all_test() {
+ let v = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Grant,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use // Test if the macro accepts missing comma at the end of the list.
+ ];
+ let mut i = v.into_iter();
+ assert_eq!(i.next().unwrap().name(), "delete");
+ assert_eq!(i.next().unwrap().name(), "gen_unique_id");
+ assert_eq!(i.next().unwrap().name(), "get_info");
+ assert_eq!(i.next().unwrap().name(), "grant");
+ assert_eq!(i.next().unwrap().name(), "manage_blob");
+ assert_eq!(i.next().unwrap().name(), "rebind");
+ assert_eq!(i.next().unwrap().name(), "req_forced_op");
+ assert_eq!(i.next().unwrap().name(), "update");
+ assert_eq!(i.next().unwrap().name(), "use");
+ assert_eq!(i.next().unwrap().name(), "use_dev_id");
+ assert_eq!(None, i.next());
+}
+#[test]
+fn key_perm_set_sparse_test() {
+ let v = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Update,
+ KeyPerm::Use, // Test if macro accepts the comma at the end of the list.
+ ];
+ let mut i = v.into_iter();
+ assert_eq!(i.next().unwrap().name(), "gen_unique_id");
+ assert_eq!(i.next().unwrap().name(), "manage_blob");
+ assert_eq!(i.next().unwrap().name(), "req_forced_op");
+ assert_eq!(i.next().unwrap().name(), "update");
+ assert_eq!(i.next().unwrap().name(), "use");
+ assert_eq!(None, i.next());
+}
+#[test]
+fn key_perm_set_empty_test() {
+ let v = key_perm_set![];
+ let mut i = v.into_iter();
+ assert_eq!(None, i.next());
+}
+#[test]
+fn key_perm_set_include_subset_test() {
+ let v1 = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Grant,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ ];
+ let v2 = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ ];
+ assert!(v1.includes(v2));
+ assert!(!v2.includes(v1));
+}
+#[test]
+fn key_perm_set_include_equal_test() {
+ let v1 = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ ];
+ let v2 = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ ];
+ assert!(v1.includes(v2));
+ assert!(v2.includes(v1));
+}
+#[test]
+fn key_perm_set_include_overlap_test() {
+ let v1 = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Grant, // only in v1
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ ];
+ let v2 = key_perm_set![
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::ReqForcedOp, // only in v2
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ ];
+ assert!(!v1.includes(v2));
+ assert!(!v2.includes(v1));
+}
+#[test]
+fn key_perm_set_include_no_overlap_test() {
+ let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,];
+ let v2 = key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,];
+ assert!(!v1.includes(v2));
+ assert!(!v2.includes(v1));
+}
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
index a8a88d2..bf1149c 100644
--- a/keystore2/src/raw_device.rs
+++ b/keystore2/src/raw_device.rs
@@ -212,8 +212,8 @@
|key_blob| {
map_km_error({
let _wp = wd::watch(concat!(
- "In KeyMintDevice::lookup_or_generate_key: ",
- "calling getKeyCharacteristics."
+ "KeyMintDevice::lookup_or_generate_key: ",
+ "calling IKeyMintDevice::getKeyCharacteristics."
));
self.km_dev.getKeyCharacteristics(key_blob, &[], &[])
})
@@ -305,7 +305,9 @@
let (begin_result, _) = self
.upgrade_keyblob_if_required_with(db, key_id_guard, key_blob, |blob| {
map_km_error({
- let _wp = wd::watch("In use_key_in_one_step: calling: begin");
+ let _wp = wd::watch(
+ "KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::begin",
+ );
self.km_dev.begin(purpose, blob, operation_parameters, auth_token)
})
})
@@ -313,7 +315,8 @@
let operation: Strong<dyn IKeyMintOperation> =
begin_result.operation.ok_or_else(Error::sys).context(ks_err!("Operation missing"))?;
map_km_error({
- let _wp = wd::watch("In use_key_in_one_step: calling: finish");
+ let _wp =
+ wd::watch("KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::finish");
operation.finish(Some(input), None, None, None, None)
})
.context(ks_err!("Failed to finish operation."))
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index cda93b3..2bdafd4 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -20,9 +20,8 @@
//! DB.
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate,
- KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel,
- Tag::Tag,
+ Algorithm::Algorithm, AttestationKey::AttestationKey, KeyParameter::KeyParameter,
+ KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
};
use android_security_rkp_aidl::aidl::android::security::rkp::RemotelyProvisionedKey::RemotelyProvisionedKey;
use android_system_keystore2::aidl::android::system::keystore2::{
@@ -85,7 +84,7 @@
key: &KeyDescriptor,
caller_uid: u32,
params: &[KeyParameter],
- ) -> Result<Option<(AttestationKey, Certificate)>> {
+ ) -> Result<Option<(AttestationKey, Vec<u8>)>> {
if !self.is_asymmetric_key(params) || key.domain != Domain::APP {
Ok(None)
} else {
@@ -106,13 +105,14 @@
AttestationKey {
keyBlob: rkpd_key.keyBlob,
attestKeyParams: vec![],
- // Batch certificate is at the beginning of the certificate chain.
+ // Batch certificate is at the beginning of the concatenated certificate
+ // chain, and the helper function only looks at the first cert.
issuerSubjectName: parse_subject_from_certificate(
&rkpd_key.encodedCertChain,
)
.context(ks_err!("Failed to parse subject."))?,
},
- Certificate { encodedCertificate: rkpd_key.encodedCertChain },
+ rkpd_key.encodedCertChain,
))),
}
}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 8412397..233f2ae 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -49,7 +49,7 @@
};
use crate::{globals::get_keymint_device, id_rotation::IdRotationState};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- Algorithm::Algorithm, AttestationKey::AttestationKey,
+ Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate,
HardwareAuthenticatorType::HardwareAuthenticatorType, IKeyMintDevice::IKeyMintDevice,
KeyCreationResult::KeyCreationResult, KeyFormat::KeyFormat,
KeyMintHardwareInfo::KeyMintHardwareInfo, KeyParameter::KeyParameter,
@@ -64,7 +64,9 @@
KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode,
};
use anyhow::{anyhow, Context, Result};
+use postprocessor_client::process_certificate_chain;
use rkpd_client::store_rkpd_attestation_key;
+use rustutils::system_properties::read_bool;
use std::convert::TryInto;
use std::time::SystemTime;
@@ -131,11 +133,21 @@
certificateChain: mut certificate_chain,
} = creation_result;
+ // Unify the possible contents of the certificate chain. The first entry in the `Vec` is
+ // always the leaf certificate (if present), but the rest of the chain may be present as
+ // either:
+ // - `certificate_chain[1..n]`: each entry holds a single certificate, as returned by
+ // KeyMint, or
+ // - `certificate[1`]: a single `Certificate` from RKP that actually (and confusingly)
+ // holds the DER-encoded certs of the chain concatenated together.
let mut cert_info: CertificateInfo = CertificateInfo::new(
+ // Leaf is always a single cert in the first entry, if present.
match certificate_chain.len() {
0 => None,
_ => Some(certificate_chain.remove(0).encodedCertificate),
},
+ // Remainder may be either `[1..n]` individual certs, or just `[1]` holding a
+ // concatenated chain. Convert the former to the latter.
match certificate_chain.len() {
0 => None,
_ => Some(
@@ -329,8 +341,9 @@
operation_parameters,
|blob| loop {
match map_km_error({
- let _wp =
- self.watch("In KeystoreSecurityLevel::create_operation: calling begin");
+ let _wp = self.watch(
+ "KeystoreSecurityLevel::create_operation: calling IKeyMintDevice::begin",
+ );
self.keymint.begin(
purpose,
blob,
@@ -444,7 +457,7 @@
// If there is an attestation challenge we need to get an application id.
if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) {
let _wp =
- self.watch("In KeystoreSecurityLevel::add_required_parameters calling: get_aaid");
+ self.watch(" KeystoreSecurityLevel::add_required_parameters: calling get_aaid");
match keystore2_aaid::get_aaid(uid) {
Ok(aaid_ok) => {
result.push(KeyParameter {
@@ -581,8 +594,8 @@
map_km_error({
let _wp = self.watch_millis(
concat!(
- "In KeystoreSecurityLevel::generate_key (UserGenerated): ",
- "calling generate_key."
+ "KeystoreSecurityLevel::generate_key (UserGenerated): ",
+ "calling IKeyMintDevice::generate_key"
),
5000, // Generate can take a little longer.
);
@@ -601,8 +614,8 @@
map_km_error({
let _wp = self.watch_millis(
concat!(
- "In KeystoreSecurityLevel::generate_key (RkpdProvisioned): ",
- "calling generate_key.",
+ "KeystoreSecurityLevel::generate_key (RkpdProvisioned): ",
+ "calling IKeyMintDevice::generate_key",
),
5000, // Generate can take a little longer.
);
@@ -621,15 +634,38 @@
log_security_safe_params(¶ms)
))
.map(|(mut result, _)| {
- result.certificateChain.push(attestation_certs);
+ if read_bool("remote_provisioning.use_cert_processor", false).unwrap_or(false) {
+ let _wp = self.watch_millis(
+ concat!(
+ "KeystoreSecurityLevel::generate_key (RkpdProvisioned): ",
+ "calling KeystorePostProcessor::process_certificate_chain",
+ ),
+ 1000, // Post processing may take a little while due to network call.
+ );
+ // process_certificate_chain would either replace the certificate chain if
+ // post-processing is successful or it would fallback to the original chain
+ // on failure. In either case, we should get back the certificate chain
+ // that is fit for storing with the newly generated key.
+ result.certificateChain =
+ process_certificate_chain(result.certificateChain, attestation_certs);
+ } else {
+ // The `certificateChain` in a `KeyCreationResult` should normally have one
+ // `Certificate` for each certificate in the chain. To avoid having to
+ // unnecessarily parse the RKP chain (which is concatenated DER-encoded
+ // certs), stuff the whole concatenated chain into a single `Certificate`.
+ // This is untangled by `store_new_key()`.
+ result
+ .certificateChain
+ .push(Certificate { encodedCertificate: attestation_certs });
+ }
result
})
}
None => map_km_error({
let _wp = self.watch_millis(
concat!(
- "In KeystoreSecurityLevel::generate_key (No attestation): ",
- "calling generate_key.",
+ "KeystoreSecurityLevel::generate_key (No attestation key): ",
+ "calling IKeyMintDevice::generate_key",
),
5000, // Generate can take a little longer.
);
@@ -696,7 +732,8 @@
let km_dev = &self.keymint;
let creation_result = map_km_error({
- let _wp = self.watch("In KeystoreSecurityLevel::import_key: calling importKey.");
+ let _wp =
+ self.watch("KeystoreSecurityLevel::import_key: calling IKeyMintDevice::importKey.");
km_dev.importKey(¶ms, format, key_data, None /* attestKey */)
})
.context(ks_err!("Trying to call importKey"))?;
@@ -810,7 +847,7 @@
&[],
|wrapping_blob| {
let _wp = self.watch(
- "In KeystoreSecurityLevel::import_wrapped_key: calling importWrappedKey.",
+ "KeystoreSecurityLevel::import_wrapped_key: calling IKeyMintDevice::importWrappedKey.",
);
let creation_result = map_km_error(self.keymint.importWrappedKey(
wrapped_data,
@@ -951,8 +988,8 @@
let km_dev = &self.keymint;
let res = {
let _wp = self.watch(concat!(
- "In IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ",
- "calling convertStorageKeyToEphemeral (1)"
+ "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ",
+ "calling IKeyMintDevice::convertStorageKeyToEphemeral (1)"
));
map_km_error(km_dev.convertStorageKeyToEphemeral(key_blob))
};
@@ -962,19 +999,18 @@
}
Err(error::Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
let upgraded_blob = {
- let _wp = self.watch("In convert_storage_key_to_ephemeral: calling upgradeKey");
+ let _wp = self.watch("IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: calling IKeyMintDevice::upgradeKey");
map_km_error(km_dev.upgradeKey(key_blob, &[]))
}
.context(ks_err!("Failed to upgrade key blob."))?;
let ephemeral_key = {
- let _wp = self.watch(
- "In convert_storage_key_to_ephemeral: calling convertStorageKeyToEphemeral (2)",
- );
+ let _wp = self.watch(concat!(
+ "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ",
+ "calling IKeyMintDevice::convertStorageKeyToEphemeral (2)"
+ ));
map_km_error(km_dev.convertStorageKeyToEphemeral(&upgraded_blob))
}
- .context(ks_err!(
- "Failed to retrieve ephemeral key (after upgrade)."
- ))?;
+ .context(ks_err!("Failed to retrieve ephemeral key (after upgrade)."))?;
Ok(EphemeralStorageKeyResponse {
ephemeralKey: ephemeral_key,
upgradedBlob: Some(upgraded_blob),
@@ -1001,7 +1037,8 @@
let km_dev = &self.keymint;
{
- let _wp = self.watch("In KeystoreSecuritylevel::delete_key: calling deleteKey");
+ let _wp =
+ self.watch("KeystoreSecuritylevel::delete_key: calling IKeyMintDevice::deleteKey");
map_km_error(km_dev.deleteKey(key_blob)).context(ks_err!("keymint device deleteKey"))
}
}
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index b760a56..85ac7bc 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -27,7 +27,10 @@
};
use crate::{
database::Uuid,
- globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_IMPORTER, SUPER_KEY},
+ globals::{
+ create_thread_local_db, DB, ENCODED_MODULE_INFO, LEGACY_BLOB_LOADER, LEGACY_IMPORTER,
+ SUPER_KEY,
+ },
};
use crate::{database::KEYSTORE_UUID, permission};
use crate::{
@@ -39,6 +42,7 @@
id_rotation::IdRotationState,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::Tag::Tag;
use android_hardware_security_keymint::binder::{BinderFeatures, Strong, ThreadState};
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
@@ -62,11 +66,18 @@
id_rotation_state: IdRotationState,
) -> Result<Strong<dyn IKeystoreService>> {
let mut result: Self = Default::default();
- let (dev, uuid) = KeystoreSecurityLevel::new_native_binder(
+ let (dev, uuid) = match KeystoreSecurityLevel::new_native_binder(
SecurityLevel::TRUSTED_ENVIRONMENT,
id_rotation_state.clone(),
- )
- .context(ks_err!("Trying to construct mandatory security level TEE."))?;
+ ) {
+ Ok(v) => v,
+ Err(e) => {
+ log::error!("Failed to construct mandatory security level TEE: {e:?}");
+ log::error!("Does the device have a /default Keymaster or KeyMint instance?");
+ return Err(e.context(ks_err!("Trying to construct mandatory security level TEE")));
+ }
+ };
+
result.i_sec_level_by_uuid.insert(uuid, dev);
result.uuid_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid);
@@ -307,6 +318,20 @@
DB.with(|db| count_key_entries(&mut db.borrow_mut(), k.domain, k.nspace))
}
+ fn get_supplementary_attestation_info(&self, tag: Tag) -> Result<Vec<u8>> {
+ match tag {
+ Tag::MODULE_HASH => {
+ let info = ENCODED_MODULE_INFO.read().unwrap();
+ (*info)
+ .clone()
+ .ok_or(Error::Rc(ResponseCode::INFO_NOT_AVAILABLE))
+ .context(ks_err!("Module info not received."))
+ }
+ _ => Err(Error::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context(ks_err!("Tag {tag:?} not supported for getSupplementaryAttestationInfo.")),
+ }
+ }
+
fn list_entries_batched(
&self,
domain: Domain,
@@ -434,4 +459,14 @@
let _wp = wd::watch("IKeystoreService::getNumberOfEntries");
self.count_num_entries(domain, namespace).map_err(into_logged_binder)
}
+
+ fn getSupplementaryAttestationInfo(&self, tag: Tag) -> binder::Result<Vec<u8>> {
+ if keystore2_flags::attest_modules() {
+ let _wp = wd::watch("IKeystoreService::getSupplementaryAttestationInfo");
+ self.get_supplementary_attestation_info(tag).map_err(into_logged_binder)
+ } else {
+ log::error!("attest_modules flag is not toggled");
+ Err(binder::StatusCode::UNKNOWN_TRANSACTION.into())
+ }
+ }
}
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 706a255..3e65753 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -52,6 +52,9 @@
};
use std::{convert::TryFrom, ops::Deref};
+#[cfg(test)]
+mod tests;
+
const MAX_MAX_BOOT_LEVEL: usize = 1_000_000_000;
/// Allow up to 15 seconds between the user unlocking using a biometric, and the auth
/// token being used to unlock in [`SuperKeyManager::try_unlock_user_with_biometric`].
@@ -914,7 +917,7 @@
KeyType::Client, /* TODO Should be Super b/189470584 */
|dev| {
let _wp =
- wd::watch("In lock_unlocked_device_required_keys: calling importKey.");
+ wd::watch("SKM::lock_unlocked_device_required_keys: calling IKeyMintDevice::importKey.");
dev.importKey(key_params.as_slice(), KeyFormat::RAW, &encrypting_key, None)
},
)?;
@@ -1215,7 +1218,7 @@
Ref(&'a [u8]),
}
-impl<'a> KeyBlob<'a> {
+impl KeyBlob<'_> {
pub fn force_reencrypt(&self) -> bool {
if let KeyBlob::Sensitive { force_reencrypt, .. } = self {
*force_reencrypt
@@ -1226,7 +1229,7 @@
}
/// Deref returns a reference to the key material in any variant.
-impl<'a> Deref for KeyBlob<'a> {
+impl Deref for KeyBlob<'_> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
@@ -1237,288 +1240,3 @@
}
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::database::tests::make_bootlevel_key_entry;
- use crate::database::tests::make_test_key_entry;
- use crate::database::tests::new_test_db;
- use rand::prelude::*;
- const USER_ID: u32 = 0;
- const TEST_KEY_ALIAS: &str = "TEST_KEY";
- const TEST_BOOT_KEY_ALIAS: &str = "TEST_BOOT_KEY";
-
- pub fn generate_password_blob() -> Password<'static> {
- let mut rng = rand::thread_rng();
- let mut password = vec![0u8; 64];
- rng.fill_bytes(&mut password);
-
- let mut zvec = ZVec::new(64).expect("Failed to create ZVec");
- zvec[..].copy_from_slice(&password[..]);
-
- Password::Owned(zvec)
- }
-
- fn setup_test(pw: &Password) -> (Arc<RwLock<SuperKeyManager>>, KeystoreDB, LegacyImporter) {
- let mut keystore_db = new_test_db().unwrap();
- let mut legacy_importer = LegacyImporter::new(Arc::new(Default::default()));
- legacy_importer.set_empty();
- let skm: Arc<RwLock<SuperKeyManager>> = Default::default();
- assert!(skm
- .write()
- .unwrap()
- .initialize_user(&mut keystore_db, &legacy_importer, USER_ID, pw, false)
- .is_ok());
- (skm, keystore_db, legacy_importer)
- }
-
- fn assert_unlocked(
- skm: &Arc<RwLock<SuperKeyManager>>,
- keystore_db: &mut KeystoreDB,
- legacy_importer: &LegacyImporter,
- user_id: u32,
- err_msg: &str,
- ) {
- let user_state =
- skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
- match user_state {
- UserState::AfterFirstUnlock(_) => {}
- _ => panic!("{}", err_msg),
- }
- }
-
- fn assert_locked(
- skm: &Arc<RwLock<SuperKeyManager>>,
- keystore_db: &mut KeystoreDB,
- legacy_importer: &LegacyImporter,
- user_id: u32,
- err_msg: &str,
- ) {
- let user_state =
- skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
- match user_state {
- UserState::BeforeFirstUnlock => {}
- _ => panic!("{}", err_msg),
- }
- }
-
- fn assert_uninitialized(
- skm: &Arc<RwLock<SuperKeyManager>>,
- keystore_db: &mut KeystoreDB,
- legacy_importer: &LegacyImporter,
- user_id: u32,
- err_msg: &str,
- ) {
- let user_state =
- skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
- match user_state {
- UserState::Uninitialized => {}
- _ => panic!("{}", err_msg),
- }
- }
-
- #[test]
- fn test_initialize_user() {
- let pw: Password = generate_password_blob();
- let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
- assert_unlocked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user was not unlocked after initialization!",
- );
- }
-
- #[test]
- fn test_unlock_user() {
- let pw: Password = generate_password_blob();
- let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
- assert_unlocked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user was not unlocked after initialization!",
- );
-
- skm.write().unwrap().data.user_keys.clear();
- assert_locked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "Clearing the cache did not lock the user!",
- );
-
- assert!(skm
- .write()
- .unwrap()
- .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw)
- .is_ok());
- assert_unlocked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user did not unlock!",
- );
- }
-
- #[test]
- fn test_unlock_wrong_password() {
- let pw: Password = generate_password_blob();
- let wrong_pw: Password = generate_password_blob();
- let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
- assert_unlocked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user was not unlocked after initialization!",
- );
-
- skm.write().unwrap().data.user_keys.clear();
- assert_locked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "Clearing the cache did not lock the user!",
- );
-
- assert!(skm
- .write()
- .unwrap()
- .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &wrong_pw)
- .is_err());
- assert_locked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user was unlocked with an incorrect password!",
- );
- }
-
- #[test]
- fn test_unlock_user_idempotent() {
- let pw: Password = generate_password_blob();
- let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
- assert_unlocked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user was not unlocked after initialization!",
- );
-
- skm.write().unwrap().data.user_keys.clear();
- assert_locked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "Clearing the cache did not lock the user!",
- );
-
- for _ in 0..5 {
- assert!(skm
- .write()
- .unwrap()
- .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw)
- .is_ok());
- assert_unlocked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user did not unlock!",
- );
- }
- }
-
- fn test_user_removal(locked: bool) {
- let pw: Password = generate_password_blob();
- let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
- assert_unlocked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user was not unlocked after initialization!",
- );
-
- assert!(make_test_key_entry(
- &mut keystore_db,
- Domain::APP,
- USER_ID.into(),
- TEST_KEY_ALIAS,
- None
- )
- .is_ok());
- assert!(make_bootlevel_key_entry(
- &mut keystore_db,
- Domain::APP,
- USER_ID.into(),
- TEST_BOOT_KEY_ALIAS,
- false
- )
- .is_ok());
-
- assert!(keystore_db
- .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client)
- .unwrap());
- assert!(keystore_db
- .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client)
- .unwrap());
-
- if locked {
- skm.write().unwrap().data.user_keys.clear();
- assert_locked(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "Clearing the cache did not lock the user!",
- );
- }
-
- assert!(skm
- .write()
- .unwrap()
- .remove_user(&mut keystore_db, &legacy_importer, USER_ID)
- .is_ok());
- assert_uninitialized(
- &skm,
- &mut keystore_db,
- &legacy_importer,
- USER_ID,
- "The user was not removed!",
- );
-
- assert!(!skm
- .write()
- .unwrap()
- .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID)
- .unwrap());
-
- assert!(!keystore_db
- .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client)
- .unwrap());
- assert!(!keystore_db
- .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client)
- .unwrap());
- }
-
- #[test]
- fn test_remove_unlocked_user() {
- test_user_removal(false);
- }
-
- #[test]
- fn test_remove_locked_user() {
- test_user_removal(true);
- }
-}
diff --git a/keystore2/src/super_key/tests.rs b/keystore2/src/super_key/tests.rs
new file mode 100644
index 0000000..76a96a7
--- /dev/null
+++ b/keystore2/src/super_key/tests.rs
@@ -0,0 +1,287 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Super-key tests.
+
+use super::*;
+use crate::database::tests::make_bootlevel_key_entry;
+use crate::database::tests::make_test_key_entry;
+use crate::database::tests::new_test_db;
+use rand::prelude::*;
+const USER_ID: u32 = 0;
+const TEST_KEY_ALIAS: &str = "TEST_KEY";
+const TEST_BOOT_KEY_ALIAS: &str = "TEST_BOOT_KEY";
+
+pub fn generate_password_blob() -> Password<'static> {
+ let mut rng = rand::thread_rng();
+ let mut password = vec![0u8; 64];
+ rng.fill_bytes(&mut password);
+
+ let mut zvec = ZVec::new(64).expect("Failed to create ZVec");
+ zvec[..].copy_from_slice(&password[..]);
+
+ Password::Owned(zvec)
+}
+
+fn setup_test(pw: &Password) -> (Arc<RwLock<SuperKeyManager>>, KeystoreDB, LegacyImporter) {
+ let mut keystore_db = new_test_db().unwrap();
+ let mut legacy_importer = LegacyImporter::new(Arc::new(Default::default()));
+ legacy_importer.set_empty();
+ let skm: Arc<RwLock<SuperKeyManager>> = Default::default();
+ assert!(skm
+ .write()
+ .unwrap()
+ .initialize_user(&mut keystore_db, &legacy_importer, USER_ID, pw, false)
+ .is_ok());
+ (skm, keystore_db, legacy_importer)
+}
+
+fn assert_unlocked(
+ skm: &Arc<RwLock<SuperKeyManager>>,
+ keystore_db: &mut KeystoreDB,
+ legacy_importer: &LegacyImporter,
+ user_id: u32,
+ err_msg: &str,
+) {
+ let user_state =
+ skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
+ match user_state {
+ UserState::AfterFirstUnlock(_) => {}
+ _ => panic!("{}", err_msg),
+ }
+}
+
+fn assert_locked(
+ skm: &Arc<RwLock<SuperKeyManager>>,
+ keystore_db: &mut KeystoreDB,
+ legacy_importer: &LegacyImporter,
+ user_id: u32,
+ err_msg: &str,
+) {
+ let user_state =
+ skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
+ match user_state {
+ UserState::BeforeFirstUnlock => {}
+ _ => panic!("{}", err_msg),
+ }
+}
+
+fn assert_uninitialized(
+ skm: &Arc<RwLock<SuperKeyManager>>,
+ keystore_db: &mut KeystoreDB,
+ legacy_importer: &LegacyImporter,
+ user_id: u32,
+ err_msg: &str,
+) {
+ let user_state =
+ skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
+ match user_state {
+ UserState::Uninitialized => {}
+ _ => panic!("{}", err_msg),
+ }
+}
+
+#[test]
+fn test_initialize_user() {
+ let pw: Password = generate_password_blob();
+ let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+ assert_unlocked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "The user was not unlocked after initialization!",
+ );
+}
+
+#[test]
+fn test_unlock_user() {
+ let pw: Password = generate_password_blob();
+ let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+ assert_unlocked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "The user was not unlocked after initialization!",
+ );
+
+ skm.write().unwrap().data.user_keys.clear();
+ assert_locked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "Clearing the cache did not lock the user!",
+ );
+
+ assert!(skm
+ .write()
+ .unwrap()
+ .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw)
+ .is_ok());
+ assert_unlocked(&skm, &mut keystore_db, &legacy_importer, USER_ID, "The user did not unlock!");
+}
+
+#[test]
+fn test_unlock_wrong_password() {
+ let pw: Password = generate_password_blob();
+ let wrong_pw: Password = generate_password_blob();
+ let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+ assert_unlocked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "The user was not unlocked after initialization!",
+ );
+
+ skm.write().unwrap().data.user_keys.clear();
+ assert_locked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "Clearing the cache did not lock the user!",
+ );
+
+ assert!(skm
+ .write()
+ .unwrap()
+ .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &wrong_pw)
+ .is_err());
+ assert_locked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "The user was unlocked with an incorrect password!",
+ );
+}
+
+#[test]
+fn test_unlock_user_idempotent() {
+ let pw: Password = generate_password_blob();
+ let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+ assert_unlocked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "The user was not unlocked after initialization!",
+ );
+
+ skm.write().unwrap().data.user_keys.clear();
+ assert_locked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "Clearing the cache did not lock the user!",
+ );
+
+ for _ in 0..5 {
+ assert!(skm
+ .write()
+ .unwrap()
+ .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw)
+ .is_ok());
+ assert_unlocked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "The user did not unlock!",
+ );
+ }
+}
+
+fn test_user_removal(locked: bool) {
+ let pw: Password = generate_password_blob();
+ let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+ assert_unlocked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "The user was not unlocked after initialization!",
+ );
+
+ assert!(make_test_key_entry(
+ &mut keystore_db,
+ Domain::APP,
+ USER_ID.into(),
+ TEST_KEY_ALIAS,
+ None
+ )
+ .is_ok());
+ assert!(make_bootlevel_key_entry(
+ &mut keystore_db,
+ Domain::APP,
+ USER_ID.into(),
+ TEST_BOOT_KEY_ALIAS,
+ false
+ )
+ .is_ok());
+
+ assert!(keystore_db
+ .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client)
+ .unwrap());
+ assert!(keystore_db
+ .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client)
+ .unwrap());
+
+ if locked {
+ skm.write().unwrap().data.user_keys.clear();
+ assert_locked(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "Clearing the cache did not lock the user!",
+ );
+ }
+
+ assert!(skm.write().unwrap().remove_user(&mut keystore_db, &legacy_importer, USER_ID).is_ok());
+ assert_uninitialized(
+ &skm,
+ &mut keystore_db,
+ &legacy_importer,
+ USER_ID,
+ "The user was not removed!",
+ );
+
+ assert!(!skm
+ .write()
+ .unwrap()
+ .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID)
+ .unwrap());
+
+ assert!(!keystore_db
+ .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client)
+ .unwrap());
+ assert!(!keystore_db
+ .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client)
+ .unwrap());
+}
+
+#[test]
+fn test_remove_unlocked_user() {
+ test_user_removal(false);
+}
+
+#[test]
+fn test_remove_locked_user() {
+ test_user_removal(true);
+}
diff --git a/keystore2/src/sw_keyblob.rs b/keystore2/src/sw_keyblob.rs
index 47ab49f..c0173b5 100644
--- a/keystore2/src/sw_keyblob.rs
+++ b/keystore2/src/sw_keyblob.rs
@@ -28,6 +28,9 @@
use keystore2_crypto::hmac_sha256;
use std::mem::size_of;
+#[cfg(test)]
+mod tests;
+
/// Root of trust value.
const SOFTWARE_ROOT_OF_TRUST: &[u8] = b"SW";
@@ -556,481 +559,3 @@
.clone_from_slice(&serialized_size.to_ne_bytes());
Ok(result)
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
- KeyOrigin::KeyOrigin, KeyParameter::KeyParameter,
- KeyParameterValue::KeyParameterValue as KPV, KeyPurpose::KeyPurpose,
- PaddingMode::PaddingMode, Tag::Tag,
- };
-
- macro_rules! expect_err {
- ($result:expr, $err_msg:expr) => {
- assert!(
- $result.is_err(),
- "Expected error containing '{}', got success {:?}",
- $err_msg,
- $result
- );
- let err = $result.err();
- assert!(
- format!("{:?}", err).contains($err_msg),
- "Unexpected error {:?}, doesn't contain '{}'",
- err,
- $err_msg
- );
- };
- }
-
- #[test]
- fn test_consume_u8() {
- let buffer = [1, 2];
- let mut data = &buffer[..];
- assert_eq!(1u8, consume_u8(&mut data).unwrap());
- assert_eq!(2u8, consume_u8(&mut data).unwrap());
- let result = consume_u8(&mut data);
- expect_err!(result, "failed to find 1 byte");
- }
-
- #[test]
- fn test_consume_u32() {
- // All supported platforms are little-endian.
- let buffer = [
- 0x01, 0x02, 0x03, 0x04, // little-endian u32
- 0x04, 0x03, 0x02, 0x01, // little-endian u32
- 0x11, 0x12, 0x13,
- ];
- let mut data = &buffer[..];
- assert_eq!(0x04030201u32, consume_u32(&mut data).unwrap());
- assert_eq!(0x01020304u32, consume_u32(&mut data).unwrap());
- let result = consume_u32(&mut data);
- expect_err!(result, "failed to find 4 bytes");
- }
-
- #[test]
- fn test_consume_i64() {
- // All supported platforms are little-endian.
- let buffer = [
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // little-endian i64
- 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // little-endian i64
- 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
- ];
- let mut data = &buffer[..];
- assert_eq!(0x0807060504030201i64, consume_i64(&mut data).unwrap());
- assert_eq!(0x0102030405060708i64, consume_i64(&mut data).unwrap());
- let result = consume_i64(&mut data);
- expect_err!(result, "failed to find 8 bytes");
- }
-
- #[test]
- fn test_consume_vec() {
- let buffer = [
- 0x01, 0x00, 0x00, 0x00, 0xaa, //
- 0x00, 0x00, 0x00, 0x00, //
- 0x01, 0x00, 0x00, 0x00, 0xbb, //
- 0x07, 0x00, 0x00, 0x00, 0xbb, // not enough data
- ];
- let mut data = &buffer[..];
- assert_eq!(vec![0xaa], consume_vec(&mut data).unwrap());
- assert_eq!(Vec::<u8>::new(), consume_vec(&mut data).unwrap());
- assert_eq!(vec![0xbb], consume_vec(&mut data).unwrap());
- let result = consume_vec(&mut data);
- expect_err!(result, "failed to find 7 bytes");
-
- let buffer = [
- 0x01, 0x00, 0x00, //
- ];
- let mut data = &buffer[..];
- let result = consume_vec(&mut data);
- expect_err!(result, "failed to find 4 bytes");
- }
-
- #[test]
- fn test_key_new_from_serialized() {
- let hidden = hidden_params(&[], &[SOFTWARE_ROOT_OF_TRUST]);
- // Test data originally generated by instrumenting Cuttlefish C++ KeyMint while running VTS
- // tests.
- let tests = [
- (
- concat!(
- "0010000000d43c2f04f948521b81bdbf001310f5920000000000000000000000",
- "00000000000c0000006400000002000010200000000300003080000000010000",
- "2000000000010000200100000004000020020000000600002001000000be0200",
- "1000000000c1020030b0ad0100c20200307b150300bd020060a8bb52407b0100",
- "00ce02003011643401cf020030000000003b06b13ae6ae6671",
- ),
- KeyBlob {
- key_material: hex::decode("d43c2f04f948521b81bdbf001310f592").unwrap(),
- hw_enforced: vec![],
- sw_enforced: vec![
- KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::AES) },
- KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(128) },
- KeyParameter {
- tag: Tag::PURPOSE,
- value: KPV::KeyPurpose(KeyPurpose::ENCRYPT),
- },
- KeyParameter {
- tag: Tag::PURPOSE,
- value: KPV::KeyPurpose(KeyPurpose::DECRYPT),
- },
- KeyParameter {
- tag: Tag::BLOCK_MODE,
- value: KPV::BlockMode(BlockMode::CBC),
- },
- KeyParameter {
- tag: Tag::PADDING,
- value: KPV::PaddingMode(PaddingMode::NONE),
- },
- KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
- KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
- KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
- KeyParameter {
- tag: Tag::CREATION_DATETIME,
- value: KPV::DateTime(1628871769000),
- },
- KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
- KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
- ],
- },
- Some(KeyFormat::RAW),
- ),
- (
- concat!(
- "00df0000003081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6b",
- "a14e46ab7ca532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654",
- "e9e79413cd503eae3d9cf68ed24f47a00706052b81040023a181890381860004",
- "006b840f0db0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d14",
- "0743bdd028db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b7",
- "45e30142e90685646661550344113aaf28bdee6cb02d19df1faab4398556a909",
- "7d6f64b95209601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3",
- "b877984d000000000000000000000000000000000c0000006400000002000010",
- "030000000a000010030000000100002002000000010000200300000005000020",
- "000000000300003009020000be02001000000000c1020030b0ad0100c2020030",
- "7b150300bd02006018d352407b010000ce02003011643401cf02003000000000",
- "2f69002e55e9b0a3"
- ),
- KeyBlob {
- key_material: hex::decode(concat!(
- "3081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6ba14e46ab7c",
- "a532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654e9e79413cd",
- "503eae3d9cf68ed24f47a00706052b81040023a181890381860004006b840f0d",
- "b0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d140743bdd028",
- "db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b745e30142e9",
- "0685646661550344113aaf28bdee6cb02d19df1faab4398556a9097d6f64b952",
- "09601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3b877984d",
- ))
- .unwrap(),
- hw_enforced: vec![],
- sw_enforced: vec![
- KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) },
- KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::P_521) },
- KeyParameter {
- tag: Tag::PURPOSE,
- value: KPV::KeyPurpose(KeyPurpose::SIGN),
- },
- KeyParameter {
- tag: Tag::PURPOSE,
- value: KPV::KeyPurpose(KeyPurpose::VERIFY),
- },
- KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
- KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(521) },
- KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
- KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
- KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
- KeyParameter {
- tag: Tag::CREATION_DATETIME,
- value: KPV::DateTime(1628871775000),
- },
- KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
- KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
- ],
- },
- Some(KeyFormat::PKCS8),
- ),
- (
- concat!(
- "0037000000541d4c440223650d5f51753c1abd80c725034485551e874d62327c",
- "65f6247a057f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc00000000",
- "0000000000000000000000000c00000064000000020000108000000003000030",
- "b801000001000020020000000100002003000000050000200400000008000030",
- "00010000be02001000000000c1020030b0ad0100c20200307b150300bd020060",
- "00d752407b010000ce02003011643401cf0200300000000036e6986ffc45fbb0",
- ),
- KeyBlob {
- key_material: hex::decode(concat!(
- "541d4c440223650d5f51753c1abd80c725034485551e874d62327c65f6247a05",
- "7f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc"
- ))
- .unwrap(),
- hw_enforced: vec![],
- sw_enforced: vec![
- KeyParameter {
- tag: Tag::ALGORITHM,
- value: KPV::Algorithm(Algorithm::HMAC),
- },
- KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(440) },
- KeyParameter {
- tag: Tag::PURPOSE,
- value: KPV::KeyPurpose(KeyPurpose::SIGN),
- },
- KeyParameter {
- tag: Tag::PURPOSE,
- value: KPV::KeyPurpose(KeyPurpose::VERIFY),
- },
- KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::SHA_2_256) },
- KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KPV::Integer(256) },
- KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
- KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
- KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
- KeyParameter {
- tag: Tag::CREATION_DATETIME,
- value: KPV::DateTime(1628871776000),
- },
- KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
- KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
- ],
- },
- Some(KeyFormat::RAW),
- ),
- (
- concat!(
- "00a8040000308204a40201000282010100bc47b5c71116766669b91fa747df87",
- "a1963df83956569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082",
- "825c7c6e482a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31",
- "c1ced84878edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c3973",
- "6838151642eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d",
- "5337c8bf9245d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0de",
- "b0957d61dbba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2",
- "540c84e45c4a99fb338b76bba7722856b5113341c349708937228f167d238ed8",
- "efb9cc19547dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af",
- "59fe87421af9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d3703",
- "8bf9f588ae20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178",
- "a848e06d558c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88e",
- "e610260e406c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30a",
- "e7a31f8262da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73a",
- "e6f8b29a9144eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b",
- "4217c8db50db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817",
- "d9380b190bd382aaffa37785759f285194c11a188bccde0e2e2902818100fb23",
- "3335770c9f3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a63",
- "3f7790d1011bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f2",
- "7a74852d6c7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd78009490",
- "4c2856d2b944fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f45990281",
- "8100bfecf2bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a7158458",
- "18e01181ff06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb",
- "0078b77fb5b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc84",
- "20fcbf48d1eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b57",
- "298902818100ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd",
- "3765559994a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55",
- "dad696d3821def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2",
- "d8480ed07a7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f6332",
- "38f521ba764902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6",
- "603c6265f70018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d46",
- "93fa48e3a9abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b",
- "2a57e18a2e131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc",
- "2af4ed75760858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1",
- "c0a258d882984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4",
- "340a9fae64a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c5526",
- "1941b3654533b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a2",
- "47f56a337e3b9845b4f2b61356000000000000000000000000000000000d0000",
- "007000000002000010010000000300003000080000c800005001000100000000",
- "0001000020020000000100002003000000050000200000000006000020010000",
- "00be02001000000000c1020030b0ad0100c20200307b150300bd020060a8bb52",
- "407b010000ce02003011643401cf02003000000000544862e9c961e857",
- ),
- KeyBlob {
- key_material: hex::decode(concat!(
- "308204a40201000282010100bc47b5c71116766669b91fa747df87a1963df839",
- "56569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082825c7c6e48",
- "2a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31c1ced84878",
- "edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c39736838151642",
- "eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d5337c8bf92",
- "45d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0deb0957d61db",
- "ba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2540c84e45c",
- "4a99fb338b76bba7722856b5113341c349708937228f167d238ed8efb9cc1954",
- "7dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af59fe87421a",
- "f9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d37038bf9f588ae",
- "20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178a848e06d55",
- "8c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88ee610260e40",
- "6c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30ae7a31f8262",
- "da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73ae6f8b29a91",
- "44eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b4217c8db50",
- "db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817d9380b190b",
- "d382aaffa37785759f285194c11a188bccde0e2e2902818100fb233335770c9f",
- "3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a633f7790d101",
- "1bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f27a74852d6c",
- "7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd780094904c2856d2b9",
- "44fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f459902818100bfecf2",
- "bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a715845818e01181ff",
- "06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb0078b77fb5",
- "b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc8420fcbf48d1",
- "eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b572989028181",
- "00ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd3765559994",
- "a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55dad696d382",
- "1def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2d8480ed07a",
- "7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f633238f521ba76",
- "4902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6603c6265f7",
- "0018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d4693fa48e3a9",
- "abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b2a57e18a2e",
- "131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc2af4ed7576",
- "0858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1c0a258d882",
- "984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4340a9fae64",
- "a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c55261941b36545",
- "33b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a247f56a337e",
- "3b9845b4f2b61356",
- ))
- .unwrap(),
- hw_enforced: vec![],
- sw_enforced: vec![
- KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::RSA) },
- KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(2048) },
- KeyParameter {
- tag: Tag::RSA_PUBLIC_EXPONENT,
- value: KPV::LongInteger(65537),
- },
- KeyParameter {
- tag: Tag::PURPOSE,
- value: KPV::KeyPurpose(KeyPurpose::SIGN),
- },
- KeyParameter {
- tag: Tag::PURPOSE,
- value: KPV::KeyPurpose(KeyPurpose::VERIFY),
- },
- KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
- KeyParameter {
- tag: Tag::PADDING,
- value: KPV::PaddingMode(PaddingMode::NONE),
- },
- KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
- KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
- KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
- KeyParameter {
- tag: Tag::CREATION_DATETIME,
- value: KPV::DateTime(1628871769000),
- },
- KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
- KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
- ],
- },
- // No support for RSA keys in export_key().
- None,
- ),
- ];
-
- for (input, want, want_format) in tests {
- let input = hex::decode(input).unwrap();
- let got = KeyBlob::new_from_serialized(&input, &hidden).expect("invalid keyblob!");
- assert!(got == want);
-
- if let Some(want_format) = want_format {
- let (got_format, _key_material, params) =
- export_key(&input, &[]).expect("invalid keyblob!");
- assert_eq!(got_format, want_format);
- // All the test cases are software-only keys.
- assert_eq!(params, got.sw_enforced);
- }
- }
- }
-
- #[test]
- fn test_add_der_len() {
- let tests = [
- (0, "00"),
- (1, "01"),
- (126, "7e"),
- (127, "7f"),
- (128, "8180"),
- (129, "8181"),
- (255, "81ff"),
- (256, "820100"),
- (257, "820101"),
- (65535, "82ffff"),
- ];
- for (input, want) in tests {
- let mut got = Vec::new();
- add_der_len(&mut got, input).unwrap();
- assert_eq!(hex::encode(got), want, " for input length {input}");
- }
- }
-
- #[test]
- fn test_pkcs8_wrap_key_p256() {
- // Key material taken from `ec_256_key` in
- // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp
- let input = hex::decode(concat!(
- "3025", // SEQUENCE (ECPrivateKey)
- "020101", // INTEGER length 1 value 1 (version)
- "0420", // OCTET STRING (privateKey)
- "737c2ecd7b8d1940bf2930aa9b4ed3ff",
- "941eed09366bc03299986481f3a4d859",
- ))
- .unwrap();
- let want = hex::decode(concat!(
- // RFC 5208 s5
- "3041", // SEQUENCE (PrivateKeyInfo) {
- "020100", // INTEGER length 1 value 0 (version)
- "3013", // SEQUENCE length 0x13 (AlgorithmIdentifier) {
- "0607", // OBJECT IDENTIFIER length 7 (algorithm)
- "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey)
- "0608", // OBJECT IDENTIFIER length 8 (param)
- "2a8648ce3d030107", // 1.2.840.10045.3.1.7 (secp256r1)
- // } end SEQUENCE (AlgorithmIdentifier)
- "0427", // OCTET STRING (privateKey) holding...
- "3025", // SEQUENCE (ECPrivateKey)
- "020101", // INTEGER length 1 value 1 (version)
- "0420", // OCTET STRING length 0x20 (privateKey)
- "737c2ecd7b8d1940bf2930aa9b4ed3ff",
- "941eed09366bc03299986481f3a4d859",
- // } end SEQUENCE (ECPrivateKey)
- // } end SEQUENCE (PrivateKeyInfo)
- ))
- .unwrap();
- let got = pkcs8_wrap_nist_key(&input, EcCurve::P_256).unwrap();
- assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input));
- }
-
- #[test]
- fn test_pkcs8_wrap_key_p521() {
- // Key material taken from `ec_521_key` in
- // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp
- let input = hex::decode(concat!(
- "3047", // SEQUENCE length 0xd3 (ECPrivateKey)
- "020101", // INTEGER length 1 value 1 (version)
- "0442", // OCTET STRING length 0x42 (privateKey)
- "0011458c586db5daa92afab03f4fe46a",
- "a9d9c3ce9a9b7a006a8384bec4c78e8e",
- "9d18d7d08b5bcfa0e53c75b064ad51c4",
- "49bae0258d54b94b1e885ded08ed4fb2",
- "5ce9",
- // } end SEQUENCE (ECPrivateKey)
- ))
- .unwrap();
- let want = hex::decode(concat!(
- // RFC 5208 s5
- "3060", // SEQUENCE (PrivateKeyInfo) {
- "020100", // INTEGER length 1 value 0 (version)
- "3010", // SEQUENCE length 0x10 (AlgorithmIdentifier) {
- "0607", // OBJECT IDENTIFIER length 7 (algorithm)
- "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey)
- "0605", // OBJECT IDENTIFIER length 5 (param)
- "2b81040023", // 1.3.132.0.35 (secp521r1)
- // } end SEQUENCE (AlgorithmIdentifier)
- "0449", // OCTET STRING (privateKey) holding...
- "3047", // SEQUENCE (ECPrivateKey)
- "020101", // INTEGER length 1 value 1 (version)
- "0442", // OCTET STRING length 0x42 (privateKey)
- "0011458c586db5daa92afab03f4fe46a",
- "a9d9c3ce9a9b7a006a8384bec4c78e8e",
- "9d18d7d08b5bcfa0e53c75b064ad51c4",
- "49bae0258d54b94b1e885ded08ed4fb2",
- "5ce9",
- // } end SEQUENCE (ECPrivateKey)
- // } end SEQUENCE (PrivateKeyInfo)
- ))
- .unwrap();
- let got = pkcs8_wrap_nist_key(&input, EcCurve::P_521).unwrap();
- assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input));
- }
-}
diff --git a/keystore2/src/sw_keyblob/tests.rs b/keystore2/src/sw_keyblob/tests.rs
new file mode 100644
index 0000000..fe01112
--- /dev/null
+++ b/keystore2/src/sw_keyblob/tests.rs
@@ -0,0 +1,449 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Tests for software-backed keyblobs.
+use super::*;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
+ KeyOrigin::KeyOrigin, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue as KPV,
+ KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, Tag::Tag,
+};
+
+macro_rules! expect_err {
+ ($result:expr, $err_msg:expr) => {
+ assert!(
+ $result.is_err(),
+ "Expected error containing '{}', got success {:?}",
+ $err_msg,
+ $result
+ );
+ let err = $result.err();
+ assert!(
+ format!("{:?}", err).contains($err_msg),
+ "Unexpected error {:?}, doesn't contain '{}'",
+ err,
+ $err_msg
+ );
+ };
+}
+
+#[test]
+fn test_consume_u8() {
+ let buffer = [1, 2];
+ let mut data = &buffer[..];
+ assert_eq!(1u8, consume_u8(&mut data).unwrap());
+ assert_eq!(2u8, consume_u8(&mut data).unwrap());
+ let result = consume_u8(&mut data);
+ expect_err!(result, "failed to find 1 byte");
+}
+
+#[test]
+fn test_consume_u32() {
+ // All supported platforms are little-endian.
+ let buffer = [
+ 0x01, 0x02, 0x03, 0x04, // little-endian u32
+ 0x04, 0x03, 0x02, 0x01, // little-endian u32
+ 0x11, 0x12, 0x13,
+ ];
+ let mut data = &buffer[..];
+ assert_eq!(0x04030201u32, consume_u32(&mut data).unwrap());
+ assert_eq!(0x01020304u32, consume_u32(&mut data).unwrap());
+ let result = consume_u32(&mut data);
+ expect_err!(result, "failed to find 4 bytes");
+}
+
+#[test]
+fn test_consume_i64() {
+ // All supported platforms are little-endian.
+ let buffer = [
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // little-endian i64
+ 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // little-endian i64
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ ];
+ let mut data = &buffer[..];
+ assert_eq!(0x0807060504030201i64, consume_i64(&mut data).unwrap());
+ assert_eq!(0x0102030405060708i64, consume_i64(&mut data).unwrap());
+ let result = consume_i64(&mut data);
+ expect_err!(result, "failed to find 8 bytes");
+}
+
+#[test]
+fn test_consume_vec() {
+ let buffer = [
+ 0x01, 0x00, 0x00, 0x00, 0xaa, //
+ 0x00, 0x00, 0x00, 0x00, //
+ 0x01, 0x00, 0x00, 0x00, 0xbb, //
+ 0x07, 0x00, 0x00, 0x00, 0xbb, // not enough data
+ ];
+ let mut data = &buffer[..];
+ assert_eq!(vec![0xaa], consume_vec(&mut data).unwrap());
+ assert_eq!(Vec::<u8>::new(), consume_vec(&mut data).unwrap());
+ assert_eq!(vec![0xbb], consume_vec(&mut data).unwrap());
+ let result = consume_vec(&mut data);
+ expect_err!(result, "failed to find 7 bytes");
+
+ let buffer = [
+ 0x01, 0x00, 0x00, //
+ ];
+ let mut data = &buffer[..];
+ let result = consume_vec(&mut data);
+ expect_err!(result, "failed to find 4 bytes");
+}
+
+#[test]
+fn test_key_new_from_serialized() {
+ let hidden = hidden_params(&[], &[SOFTWARE_ROOT_OF_TRUST]);
+ // Test data originally generated by instrumenting Cuttlefish C++ KeyMint while running VTS
+ // tests.
+ let tests = [
+ (
+ concat!(
+ "0010000000d43c2f04f948521b81bdbf001310f5920000000000000000000000",
+ "00000000000c0000006400000002000010200000000300003080000000010000",
+ "2000000000010000200100000004000020020000000600002001000000be0200",
+ "1000000000c1020030b0ad0100c20200307b150300bd020060a8bb52407b0100",
+ "00ce02003011643401cf020030000000003b06b13ae6ae6671",
+ ),
+ KeyBlob {
+ key_material: hex::decode("d43c2f04f948521b81bdbf001310f592").unwrap(),
+ hw_enforced: vec![],
+ sw_enforced: vec![
+ KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::AES) },
+ KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(128) },
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::ENCRYPT) },
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::DECRYPT) },
+ KeyParameter { tag: Tag::BLOCK_MODE, value: KPV::BlockMode(BlockMode::CBC) },
+ KeyParameter { tag: Tag::PADDING, value: KPV::PaddingMode(PaddingMode::NONE) },
+ KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+ KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
+ KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
+ KeyParameter {
+ tag: Tag::CREATION_DATETIME,
+ value: KPV::DateTime(1628871769000),
+ },
+ KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
+ KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
+ ],
+ },
+ Some(KeyFormat::RAW),
+ ),
+ (
+ concat!(
+ "00df0000003081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6b",
+ "a14e46ab7ca532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654",
+ "e9e79413cd503eae3d9cf68ed24f47a00706052b81040023a181890381860004",
+ "006b840f0db0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d14",
+ "0743bdd028db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b7",
+ "45e30142e90685646661550344113aaf28bdee6cb02d19df1faab4398556a909",
+ "7d6f64b95209601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3",
+ "b877984d000000000000000000000000000000000c0000006400000002000010",
+ "030000000a000010030000000100002002000000010000200300000005000020",
+ "000000000300003009020000be02001000000000c1020030b0ad0100c2020030",
+ "7b150300bd02006018d352407b010000ce02003011643401cf02003000000000",
+ "2f69002e55e9b0a3"
+ ),
+ KeyBlob {
+ key_material: hex::decode(concat!(
+ "3081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6ba14e46ab7c",
+ "a532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654e9e79413cd",
+ "503eae3d9cf68ed24f47a00706052b81040023a181890381860004006b840f0d",
+ "b0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d140743bdd028",
+ "db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b745e30142e9",
+ "0685646661550344113aaf28bdee6cb02d19df1faab4398556a9097d6f64b952",
+ "09601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3b877984d",
+ ))
+ .unwrap(),
+ hw_enforced: vec![],
+ sw_enforced: vec![
+ KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) },
+ KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::P_521) },
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) },
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) },
+ KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
+ KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(521) },
+ KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+ KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
+ KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
+ KeyParameter {
+ tag: Tag::CREATION_DATETIME,
+ value: KPV::DateTime(1628871775000),
+ },
+ KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
+ KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
+ ],
+ },
+ Some(KeyFormat::PKCS8),
+ ),
+ (
+ concat!(
+ "0037000000541d4c440223650d5f51753c1abd80c725034485551e874d62327c",
+ "65f6247a057f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc00000000",
+ "0000000000000000000000000c00000064000000020000108000000003000030",
+ "b801000001000020020000000100002003000000050000200400000008000030",
+ "00010000be02001000000000c1020030b0ad0100c20200307b150300bd020060",
+ "00d752407b010000ce02003011643401cf0200300000000036e6986ffc45fbb0",
+ ),
+ KeyBlob {
+ key_material: hex::decode(concat!(
+ "541d4c440223650d5f51753c1abd80c725034485551e874d62327c65f6247a05",
+ "7f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc"
+ ))
+ .unwrap(),
+ hw_enforced: vec![],
+ sw_enforced: vec![
+ KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::HMAC) },
+ KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(440) },
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) },
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) },
+ KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::SHA_2_256) },
+ KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KPV::Integer(256) },
+ KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+ KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
+ KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
+ KeyParameter {
+ tag: Tag::CREATION_DATETIME,
+ value: KPV::DateTime(1628871776000),
+ },
+ KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
+ KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
+ ],
+ },
+ Some(KeyFormat::RAW),
+ ),
+ (
+ concat!(
+ "00a8040000308204a40201000282010100bc47b5c71116766669b91fa747df87",
+ "a1963df83956569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082",
+ "825c7c6e482a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31",
+ "c1ced84878edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c3973",
+ "6838151642eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d",
+ "5337c8bf9245d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0de",
+ "b0957d61dbba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2",
+ "540c84e45c4a99fb338b76bba7722856b5113341c349708937228f167d238ed8",
+ "efb9cc19547dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af",
+ "59fe87421af9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d3703",
+ "8bf9f588ae20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178",
+ "a848e06d558c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88e",
+ "e610260e406c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30a",
+ "e7a31f8262da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73a",
+ "e6f8b29a9144eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b",
+ "4217c8db50db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817",
+ "d9380b190bd382aaffa37785759f285194c11a188bccde0e2e2902818100fb23",
+ "3335770c9f3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a63",
+ "3f7790d1011bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f2",
+ "7a74852d6c7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd78009490",
+ "4c2856d2b944fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f45990281",
+ "8100bfecf2bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a7158458",
+ "18e01181ff06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb",
+ "0078b77fb5b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc84",
+ "20fcbf48d1eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b57",
+ "298902818100ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd",
+ "3765559994a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55",
+ "dad696d3821def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2",
+ "d8480ed07a7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f6332",
+ "38f521ba764902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6",
+ "603c6265f70018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d46",
+ "93fa48e3a9abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b",
+ "2a57e18a2e131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc",
+ "2af4ed75760858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1",
+ "c0a258d882984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4",
+ "340a9fae64a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c5526",
+ "1941b3654533b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a2",
+ "47f56a337e3b9845b4f2b61356000000000000000000000000000000000d0000",
+ "007000000002000010010000000300003000080000c800005001000100000000",
+ "0001000020020000000100002003000000050000200000000006000020010000",
+ "00be02001000000000c1020030b0ad0100c20200307b150300bd020060a8bb52",
+ "407b010000ce02003011643401cf02003000000000544862e9c961e857",
+ ),
+ KeyBlob {
+ key_material: hex::decode(concat!(
+ "308204a40201000282010100bc47b5c71116766669b91fa747df87a1963df839",
+ "56569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082825c7c6e48",
+ "2a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31c1ced84878",
+ "edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c39736838151642",
+ "eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d5337c8bf92",
+ "45d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0deb0957d61db",
+ "ba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2540c84e45c",
+ "4a99fb338b76bba7722856b5113341c349708937228f167d238ed8efb9cc1954",
+ "7dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af59fe87421a",
+ "f9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d37038bf9f588ae",
+ "20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178a848e06d55",
+ "8c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88ee610260e40",
+ "6c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30ae7a31f8262",
+ "da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73ae6f8b29a91",
+ "44eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b4217c8db50",
+ "db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817d9380b190b",
+ "d382aaffa37785759f285194c11a188bccde0e2e2902818100fb233335770c9f",
+ "3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a633f7790d101",
+ "1bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f27a74852d6c",
+ "7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd780094904c2856d2b9",
+ "44fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f459902818100bfecf2",
+ "bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a715845818e01181ff",
+ "06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb0078b77fb5",
+ "b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc8420fcbf48d1",
+ "eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b572989028181",
+ "00ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd3765559994",
+ "a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55dad696d382",
+ "1def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2d8480ed07a",
+ "7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f633238f521ba76",
+ "4902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6603c6265f7",
+ "0018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d4693fa48e3a9",
+ "abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b2a57e18a2e",
+ "131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc2af4ed7576",
+ "0858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1c0a258d882",
+ "984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4340a9fae64",
+ "a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c55261941b36545",
+ "33b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a247f56a337e",
+ "3b9845b4f2b61356",
+ ))
+ .unwrap(),
+ hw_enforced: vec![],
+ sw_enforced: vec![
+ KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::RSA) },
+ KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(2048) },
+ KeyParameter { tag: Tag::RSA_PUBLIC_EXPONENT, value: KPV::LongInteger(65537) },
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) },
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) },
+ KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
+ KeyParameter { tag: Tag::PADDING, value: KPV::PaddingMode(PaddingMode::NONE) },
+ KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+ KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
+ KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
+ KeyParameter {
+ tag: Tag::CREATION_DATETIME,
+ value: KPV::DateTime(1628871769000),
+ },
+ KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
+ KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
+ ],
+ },
+ // No support for RSA keys in export_key().
+ None,
+ ),
+ ];
+
+ for (input, want, want_format) in tests {
+ let input = hex::decode(input).unwrap();
+ let got = KeyBlob::new_from_serialized(&input, &hidden).expect("invalid keyblob!");
+ assert!(got == want);
+
+ if let Some(want_format) = want_format {
+ let (got_format, _key_material, params) =
+ export_key(&input, &[]).expect("invalid keyblob!");
+ assert_eq!(got_format, want_format);
+ // All the test cases are software-only keys.
+ assert_eq!(params, got.sw_enforced);
+ }
+ }
+}
+
+#[test]
+fn test_add_der_len() {
+ let tests = [
+ (0, "00"),
+ (1, "01"),
+ (126, "7e"),
+ (127, "7f"),
+ (128, "8180"),
+ (129, "8181"),
+ (255, "81ff"),
+ (256, "820100"),
+ (257, "820101"),
+ (65535, "82ffff"),
+ ];
+ for (input, want) in tests {
+ let mut got = Vec::new();
+ add_der_len(&mut got, input).unwrap();
+ assert_eq!(hex::encode(got), want, " for input length {input}");
+ }
+}
+
+#[test]
+fn test_pkcs8_wrap_key_p256() {
+ // Key material taken from `ec_256_key` in
+ // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp
+ let input = hex::decode(concat!(
+ "3025", // SEQUENCE (ECPrivateKey)
+ "020101", // INTEGER length 1 value 1 (version)
+ "0420", // OCTET STRING (privateKey)
+ "737c2ecd7b8d1940bf2930aa9b4ed3ff",
+ "941eed09366bc03299986481f3a4d859",
+ ))
+ .unwrap();
+ let want = hex::decode(concat!(
+ // RFC 5208 s5
+ "3041", // SEQUENCE (PrivateKeyInfo) {
+ "020100", // INTEGER length 1 value 0 (version)
+ "3013", // SEQUENCE length 0x13 (AlgorithmIdentifier) {
+ "0607", // OBJECT IDENTIFIER length 7 (algorithm)
+ "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey)
+ "0608", // OBJECT IDENTIFIER length 8 (param)
+ "2a8648ce3d030107", // 1.2.840.10045.3.1.7 (secp256r1)
+ // } end SEQUENCE (AlgorithmIdentifier)
+ "0427", // OCTET STRING (privateKey) holding...
+ "3025", // SEQUENCE (ECPrivateKey)
+ "020101", // INTEGER length 1 value 1 (version)
+ "0420", // OCTET STRING length 0x20 (privateKey)
+ "737c2ecd7b8d1940bf2930aa9b4ed3ff",
+ "941eed09366bc03299986481f3a4d859",
+ // } end SEQUENCE (ECPrivateKey)
+ // } end SEQUENCE (PrivateKeyInfo)
+ ))
+ .unwrap();
+ let got = pkcs8_wrap_nist_key(&input, EcCurve::P_256).unwrap();
+ assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input));
+}
+
+#[test]
+fn test_pkcs8_wrap_key_p521() {
+ // Key material taken from `ec_521_key` in
+ // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp
+ let input = hex::decode(concat!(
+ "3047", // SEQUENCE length 0xd3 (ECPrivateKey)
+ "020101", // INTEGER length 1 value 1 (version)
+ "0442", // OCTET STRING length 0x42 (privateKey)
+ "0011458c586db5daa92afab03f4fe46a",
+ "a9d9c3ce9a9b7a006a8384bec4c78e8e",
+ "9d18d7d08b5bcfa0e53c75b064ad51c4",
+ "49bae0258d54b94b1e885ded08ed4fb2",
+ "5ce9",
+ // } end SEQUENCE (ECPrivateKey)
+ ))
+ .unwrap();
+ let want = hex::decode(concat!(
+ // RFC 5208 s5
+ "3060", // SEQUENCE (PrivateKeyInfo) {
+ "020100", // INTEGER length 1 value 0 (version)
+ "3010", // SEQUENCE length 0x10 (AlgorithmIdentifier) {
+ "0607", // OBJECT IDENTIFIER length 7 (algorithm)
+ "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey)
+ "0605", // OBJECT IDENTIFIER length 5 (param)
+ "2b81040023", // 1.3.132.0.35 (secp521r1)
+ // } end SEQUENCE (AlgorithmIdentifier)
+ "0449", // OCTET STRING (privateKey) holding...
+ "3047", // SEQUENCE (ECPrivateKey)
+ "020101", // INTEGER length 1 value 1 (version)
+ "0442", // OCTET STRING length 0x42 (privateKey)
+ "0011458c586db5daa92afab03f4fe46a",
+ "a9d9c3ce9a9b7a006a8384bec4c78e8e",
+ "9d18d7d08b5bcfa0e53c75b064ad51c4",
+ "49bae0258d54b94b1e885ded08ed4fb2",
+ "5ce9",
+ // } end SEQUENCE (ECPrivateKey)
+ // } end SEQUENCE (PrivateKeyInfo)
+ ))
+ .unwrap();
+ let got = pkcs8_wrap_nist_key(&input, EcCurve::P_521).unwrap();
+ assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input));
+}
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index 54f382d..35290df 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -38,16 +38,23 @@
};
use android_system_keystore2::aidl::android::system::keystore2::{
Authorization::Authorization, Domain::Domain, KeyDescriptor::KeyDescriptor,
+ ResponseCode::ResponseCode,
};
use anyhow::{Context, Result};
-use binder::{Strong, ThreadState};
+use binder::{FromIBinder, StatusCode, Strong, ThreadState};
use keystore2_apc_compat::{
ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED,
APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
APC_COMPAT_ERROR_SYSTEM_ERROR,
};
use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt, ZVec};
+use log::{info, warn};
use std::iter::IntoIterator;
+use std::thread::sleep;
+use std::time::Duration;
+
+#[cfg(test)]
+mod tests;
/// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to GeneralizedTime
/// 999912312359559, which is 253402300799000 ms from Jan 1, 1970.
@@ -73,6 +80,7 @@
pub fn check_grant_permission(access_vec: KeyPermSet, key: &KeyDescriptor) -> anyhow::Result<()> {
ThreadState::with_calling_sid(|calling_sid| {
permission::check_grant_permission(
+ ThreadState::get_calling_uid(),
calling_sid
.ok_or_else(Error::sys)
.context(ks_err!("Cannot check permission without calling_sid."))?,
@@ -119,14 +127,20 @@
/// identifiers. It throws an error if the permissions cannot be verified or if the caller doesn't
/// have the right permissions. Otherwise it returns silently.
pub fn check_device_attestation_permissions() -> anyhow::Result<()> {
- check_android_permission("android.permission.READ_PRIVILEGED_PHONE_STATE")
+ check_android_permission(
+ "android.permission.READ_PRIVILEGED_PHONE_STATE",
+ Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+ )
}
/// This function checks whether the calling app has the Android permissions needed to attest the
/// device-unique identifier. It throws an error if the permissions cannot be verified or if the
/// caller doesn't have the right permissions. Otherwise it returns silently.
pub fn check_unique_id_attestation_permissions() -> anyhow::Result<()> {
- check_android_permission("android.permission.REQUEST_UNIQUE_ID_ATTESTATION")
+ check_android_permission(
+ "android.permission.REQUEST_UNIQUE_ID_ATTESTATION",
+ Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+ )
}
/// This function checks whether the calling app has the Android permissions needed to manage
@@ -135,16 +149,24 @@
/// It throws an error if the permissions cannot be verified or if the caller doesn't
/// have the right permissions. Otherwise it returns silently.
pub fn check_get_app_uids_affected_by_sid_permissions() -> anyhow::Result<()> {
- check_android_permission("android.permission.MANAGE_USERS")
+ check_android_permission(
+ "android.permission.MANAGE_USERS",
+ Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+ )
}
-fn check_android_permission(permission: &str) -> anyhow::Result<()> {
+/// This function checks whether the calling app has the Android permission needed to dump
+/// Keystore state to logcat.
+pub fn check_dump_permission() -> anyhow::Result<()> {
+ check_android_permission("android.permission.DUMP", Error::Rc(ResponseCode::PERMISSION_DENIED))
+}
+
+fn check_android_permission(permission: &str, err: Error) -> anyhow::Result<()> {
let permission_controller: Strong<dyn IPermissionController::IPermissionController> =
binder::get_interface("permission")?;
let binder_result = {
- let _wp =
- watchdog::watch("In check_device_attestation_permissions: calling checkPermission.");
+ let _wp = watchdog::watch("check_android_permission: calling checkPermission");
permission_controller.checkPermission(
permission,
ThreadState::get_calling_pid(),
@@ -155,8 +177,7 @@
map_binder_status(binder_result).context(ks_err!("checkPermission failed"))?;
match has_permissions {
true => Ok(()),
- false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS))
- .context(ks_err!("caller does not have the permission to attest device IDs")),
+ false => Err(err).context(ks_err!("caller does not have the '{permission}' permission")),
}
}
@@ -261,7 +282,9 @@
log::debug!("import parameters={import_params:?}");
let creation_result = {
- let _wp = watchdog::watch("In utils::import_keyblob_and_perform_op: calling importKey.");
+ let _wp = watchdog::watch(
+ "utils::import_keyblob_and_perform_op: calling IKeyMintDevice::importKey",
+ );
map_km_error(km_dev.importKey(&import_params, format, &key_material, None))
}
.context(ks_err!("Upgrade failed."))?;
@@ -301,7 +324,9 @@
NewBlobHandler: FnOnce(&[u8]) -> Result<()>,
{
let upgraded_blob = {
- let _wp = watchdog::watch("In utils::upgrade_keyblob_and_perform_op: calling upgradeKey.");
+ let _wp = watchdog::watch(
+ "utils::upgrade_keyblob_and_perform_op: calling IKeyMintDevice::upgradeKey.",
+ );
map_km_error(km_dev.upgradeKey(key_blob, upgrade_params))
}
.context(ks_err!("Upgrade failed."))?;
@@ -513,44 +538,48 @@
result
}
-fn estimate_safe_amount_to_return(
+pub(crate) fn estimate_safe_amount_to_return(
domain: Domain,
namespace: i64,
+ start_past_alias: Option<&str>,
key_descriptors: &[KeyDescriptor],
response_size_limit: usize,
) -> usize {
- let mut items_to_return = 0;
- let mut returned_bytes: usize = 0;
+ let mut count = 0;
+ let mut bytes: usize = 0;
// Estimate the transaction size to avoid returning more items than what
// could fit in a binder transaction.
for kd in key_descriptors.iter() {
// 4 bytes for the Domain enum
// 8 bytes for the Namespace long.
- returned_bytes += 4 + 8;
+ bytes += 4 + 8;
// Size of the alias string. Includes 4 bytes for length encoding.
if let Some(alias) = &kd.alias {
- returned_bytes += 4 + alias.len();
+ bytes += 4 + alias.len();
}
// Size of the blob. Includes 4 bytes for length encoding.
if let Some(blob) = &kd.blob {
- returned_bytes += 4 + blob.len();
+ bytes += 4 + blob.len();
}
// The binder transaction size limit is 1M. Empirical measurements show
// that the binder overhead is 60% (to be confirmed). So break after
// 350KB and return a partial list.
- if returned_bytes > response_size_limit {
+ if bytes > response_size_limit {
log::warn!(
- "{domain:?}:{namespace}: Key descriptors list ({} items) may exceed binder \
- size, returning {items_to_return} items est {returned_bytes} bytes.",
+ "{domain:?}:{namespace}: Key descriptors list ({} items after {start_past_alias:?}) \
+ may exceed binder size, returning {count} items est. {bytes} bytes",
key_descriptors.len(),
);
break;
}
- items_to_return += 1;
+ count += 1;
}
- items_to_return
+ count
}
+/// Estimate for maximum size of a Binder response in bytes.
+pub(crate) const RESPONSE_SIZE_LIMIT: usize = 358400;
+
/// List all key aliases for a given domain + namespace. whose alias is greater
/// than start_past_alias (if provided).
pub fn list_key_entries(
@@ -574,9 +603,13 @@
start_past_alias,
);
- const RESPONSE_SIZE_LIMIT: usize = 358400;
- let safe_amount_to_return =
- estimate_safe_amount_to_return(domain, namespace, &merged_key_entries, RESPONSE_SIZE_LIMIT);
+ let safe_amount_to_return = estimate_safe_amount_to_return(
+ domain,
+ namespace,
+ start_past_alias,
+ &merged_key_entries,
+ RESPONSE_SIZE_LIMIT,
+ );
Ok(merged_key_entries[..safe_amount_to_return].to_vec())
}
@@ -629,129 +662,24 @@
}
}
-#[cfg(test)]
-mod tests {
- use super::*;
- use anyhow::Result;
+pub(crate) fn retry_get_interface<T: FromIBinder + ?Sized>(
+ name: &str,
+) -> Result<Strong<T>, StatusCode> {
+ let retry_count = if cfg!(early_vm) { 5 } else { 1 };
- #[test]
- fn check_device_attestation_permissions_test() -> Result<()> {
- check_device_attestation_permissions().or_else(|error| {
- match error.root_cause().downcast_ref::<Error>() {
- // Expected: the context for this test might not be allowed to attest device IDs.
- Some(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) => Ok(()),
- // Other errors are unexpected
- _ => Err(error),
+ let mut wait_time = Duration::from_secs(5);
+ for i in 1..retry_count {
+ match binder::get_interface(name) {
+ Ok(res) => return Ok(res),
+ Err(e) => {
+ warn!("failed to get interface {name}. Retry {i}/{retry_count}: {e:?}");
+ sleep(wait_time);
+ wait_time *= 2;
}
- })
+ }
}
-
- fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> {
- key_aliases
- .iter()
- .map(|key_alias| KeyDescriptor {
- domain: Domain::APP,
- nspace: 0,
- alias: Some(key_alias.to_string()),
- blob: None,
- })
- .collect::<Vec<KeyDescriptor>>()
+ if retry_count > 1 {
+ info!("{retry_count}-th (last) retry to get interface: {name}");
}
-
- fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> {
- key_descriptors
- .iter()
- .map(
- |kd| {
- if let Some(alias) = &kd.alias {
- String::from(alias)
- } else {
- String::from("")
- }
- },
- )
- .collect::<Vec<String>>()
- }
-
- #[test]
- fn test_safe_amount_to_return() -> Result<()> {
- let key_aliases = vec!["key1", "key2", "key3"];
- let key_descriptors = create_key_descriptors_from_aliases(&key_aliases);
-
- assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 20), 1);
- assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 50), 2);
- assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 100), 3);
- Ok(())
- }
-
- #[test]
- fn test_merge_and_sort_lists_without_filtering() -> Result<()> {
- let legacy_key_aliases = vec!["key_c", "key_a", "key_b"];
- let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
- let db_key_aliases = vec!["key_a", "key_d"];
- let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
- let result =
- merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None);
- assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]);
- Ok(())
- }
-
- #[test]
- fn test_merge_and_sort_lists_with_filtering() -> Result<()> {
- let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
- let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
- let db_key_aliases = vec!["key_c", "key_g"];
- let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
- let result = merge_and_filter_key_entry_lists(
- &legacy_key_descriptors,
- &db_key_descriptors,
- Some("key_b"),
- );
- assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]);
- Ok(())
- }
-
- #[test]
- fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> {
- let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
- let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
- let db_key_aliases = vec!["key_d", "key_e", "key_g"];
- let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
- let result = merge_and_filter_key_entry_lists(
- &legacy_key_descriptors,
- &db_key_descriptors,
- Some("key_c"),
- );
- assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]);
- Ok(())
- }
-
- #[test]
- fn test_list_key_parameters_with_filter_on_security_sensitive_info() -> Result<()> {
- let params = vec![
- KmKeyParameter { tag: Tag::APPLICATION_ID, value: KeyParameterValue::Integer(0) },
- KmKeyParameter { tag: Tag::APPLICATION_DATA, value: KeyParameterValue::Integer(0) },
- KmKeyParameter {
- tag: Tag::CERTIFICATE_NOT_AFTER,
- value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
- },
- KmKeyParameter {
- tag: Tag::CERTIFICATE_NOT_BEFORE,
- value: KeyParameterValue::DateTime(0),
- },
- ];
- let wanted = vec![
- KmKeyParameter {
- tag: Tag::CERTIFICATE_NOT_AFTER,
- value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
- },
- KmKeyParameter {
- tag: Tag::CERTIFICATE_NOT_BEFORE,
- value: KeyParameterValue::DateTime(0),
- },
- ];
-
- assert_eq!(log_security_safe_params(¶ms), wanted);
- Ok(())
- }
+ binder::get_interface(name)
}
diff --git a/keystore2/src/utils/tests.rs b/keystore2/src/utils/tests.rs
new file mode 100644
index 0000000..e514b2a
--- /dev/null
+++ b/keystore2/src/utils/tests.rs
@@ -0,0 +1,125 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Utility functions tests.
+
+use super::*;
+use anyhow::Result;
+
+#[test]
+fn check_device_attestation_permissions_test() -> Result<()> {
+ check_device_attestation_permissions().or_else(|error| {
+ match error.root_cause().downcast_ref::<Error>() {
+ // Expected: the context for this test might not be allowed to attest device IDs.
+ Some(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) => Ok(()),
+ // Other errors are unexpected
+ _ => Err(error),
+ }
+ })
+}
+
+fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> {
+ key_aliases
+ .iter()
+ .map(|key_alias| KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(key_alias.to_string()),
+ blob: None,
+ })
+ .collect::<Vec<KeyDescriptor>>()
+}
+
+fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> {
+ key_descriptors
+ .iter()
+ .map(|kd| if let Some(alias) = &kd.alias { String::from(alias) } else { String::from("") })
+ .collect::<Vec<String>>()
+}
+
+#[test]
+fn test_safe_amount_to_return() -> Result<()> {
+ let key_aliases = vec!["key1", "key2", "key3"];
+ let key_descriptors = create_key_descriptors_from_aliases(&key_aliases);
+
+ assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, None, &key_descriptors, 20), 1);
+ assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, None, &key_descriptors, 50), 2);
+ assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, None, &key_descriptors, 100), 3);
+ Ok(())
+}
+
+#[test]
+fn test_merge_and_sort_lists_without_filtering() -> Result<()> {
+ let legacy_key_aliases = vec!["key_c", "key_a", "key_b"];
+ let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
+ let db_key_aliases = vec!["key_a", "key_d"];
+ let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
+ let result =
+ merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None);
+ assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]);
+ Ok(())
+}
+
+#[test]
+fn test_merge_and_sort_lists_with_filtering() -> Result<()> {
+ let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
+ let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
+ let db_key_aliases = vec!["key_c", "key_g"];
+ let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
+ let result = merge_and_filter_key_entry_lists(
+ &legacy_key_descriptors,
+ &db_key_descriptors,
+ Some("key_b"),
+ );
+ assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]);
+ Ok(())
+}
+
+#[test]
+fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> {
+ let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
+ let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
+ let db_key_aliases = vec!["key_d", "key_e", "key_g"];
+ let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
+ let result = merge_and_filter_key_entry_lists(
+ &legacy_key_descriptors,
+ &db_key_descriptors,
+ Some("key_c"),
+ );
+ assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]);
+ Ok(())
+}
+
+#[test]
+fn test_list_key_parameters_with_filter_on_security_sensitive_info() -> Result<()> {
+ let params = vec![
+ KmKeyParameter { tag: Tag::APPLICATION_ID, value: KeyParameterValue::Integer(0) },
+ KmKeyParameter { tag: Tag::APPLICATION_DATA, value: KeyParameterValue::Integer(0) },
+ KmKeyParameter {
+ tag: Tag::CERTIFICATE_NOT_AFTER,
+ value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
+ },
+ KmKeyParameter { tag: Tag::CERTIFICATE_NOT_BEFORE, value: KeyParameterValue::DateTime(0) },
+ ];
+ let wanted = vec![
+ KmKeyParameter {
+ tag: Tag::CERTIFICATE_NOT_AFTER,
+ value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
+ },
+ KmKeyParameter { tag: Tag::CERTIFICATE_NOT_BEFORE, value: KeyParameterValue::DateTime(0) },
+ ];
+
+ assert_eq!(log_security_safe_params(¶ms), wanted);
+ Ok(())
+}
diff --git a/keystore2/src/watchdog_helper.rs b/keystore2/src/watchdog_helper.rs
index 1072ac0..63383aa 100644
--- a/keystore2/src/watchdog_helper.rs
+++ b/keystore2/src/watchdog_helper.rs
@@ -17,8 +17,7 @@
/// This module provides helpers for simplified use of the watchdog module.
#[cfg(feature = "watchdog")]
pub mod watchdog {
- use lazy_static::lazy_static;
- use std::sync::Arc;
+ use std::sync::{Arc, LazyLock};
use std::time::Duration;
pub use watchdog_rs::WatchPoint;
use watchdog_rs::Watchdog;
@@ -28,10 +27,8 @@
const DEFAULT_TIMEOUT: Duration = Duration::from_millis(DEFAULT_TIMEOUT_MS);
- lazy_static! {
- /// A Watchdog thread, that can be used to create watch points.
- static ref WD: Arc<Watchdog> = Watchdog::new(Duration::from_secs(10));
- }
+ /// A Watchdog thread, that can be used to create watch points.
+ static WD: LazyLock<Arc<Watchdog>> = LazyLock::new(|| Watchdog::new(Duration::from_secs(10)));
/// Sets a watch point with `id` and a timeout of `millis` milliseconds.
pub fn watch_millis(id: &'static str, millis: u64) -> Option<WatchPoint> {
diff --git a/keystore2/test_utils/Android.bp b/keystore2/test_utils/Android.bp
index 4c7c18a..57da27f 100644
--- a/keystore2/test_utils/Android.bp
+++ b/keystore2/test_utils/Android.bp
@@ -42,15 +42,15 @@
"libthiserror",
],
static_libs: [
+ "libcppbor",
+ "libkeymaster_portable",
+ "libkeymint_support",
"libkeystore-engine",
"libkeystore2_ffi_test_utils",
],
shared_libs: [
- "android.system.keystore2-V4-ndk",
"libbase",
"libcrypto",
- "libkeymaster_portable",
- "libkeymint_support",
],
}
@@ -59,6 +59,12 @@
crate_name: "keystore2_test_utils",
srcs: ["lib.rs"],
defaults: ["libkeystore2_test_utils_defaults"],
+ static_libs: [
+ // Also include static_libs for the NDK variants so that they are available
+ // for dependencies.
+ "android.system.keystore2-V5-ndk",
+ "android.hardware.security.keymint-V4-ndk",
+ ],
}
rust_test {
@@ -75,20 +81,22 @@
name: "libkeystore2_ffi_test_utils",
srcs: ["ffi_test_utils.cpp"],
defaults: [
- "keymint_use_latest_hal_aidl_ndk_shared",
- "keystore2_use_latest_aidl_ndk_shared",
+ "keymint_use_latest_hal_aidl_ndk_static",
+ "keystore2_use_latest_aidl_ndk_static",
],
generated_headers: [
"cxx-bridge-header",
"libkeystore2_ffi_test_utils_bridge_header",
],
generated_sources: ["libkeystore2_ffi_test_utils_bridge_code"],
- static_libs: ["libkeystore-engine"],
+ static_libs: [
+ "libkeymaster_portable",
+ "libkeymint_support",
+ "libkeystore-engine",
+ ],
shared_libs: [
"libbase",
"libcrypto",
- "libkeymaster_portable",
- "libkeymint_support",
],
}
diff --git a/keystore2/test_utils/attestation/Android.bp b/keystore2/test_utils/attestation/Android.bp
new file mode 100644
index 0000000..dbf02d0
--- /dev/null
+++ b/keystore2/test_utils/attestation/Android.bp
@@ -0,0 +1,54 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_defaults {
+ name: "libkeystore_attestation_defaults",
+ crate_name: "keystore_attestation",
+ srcs: ["lib.rs"],
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ ],
+ rustlibs: [
+ "libbinder_rs",
+ "libder",
+ "liblog_rust",
+ "libspki",
+ "libx509_cert",
+ ],
+}
+
+rust_library {
+ name: "libkeystore_attestation",
+ defaults: ["libkeystore_attestation_defaults"],
+}
+
+rust_test {
+ name: "libkeystore_attestation_test",
+ defaults: ["libkeystore_attestation_defaults"],
+ rustlibs: [
+ "libhex",
+ ],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ compile_multilib: "first",
+}
diff --git a/keystore2/test_utils/attestation/lib.rs b/keystore2/test_utils/attestation/lib.rs
new file mode 100644
index 0000000..31d3314
--- /dev/null
+++ b/keystore2/test_utils/attestation/lib.rs
@@ -0,0 +1,693 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Attestation parsing.
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
+ HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin,
+ KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue as KPV,
+ KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, Tag::Tag, TagType::TagType,
+};
+use der::asn1::{Null, ObjectIdentifier, OctetStringRef, SetOfVec};
+use der::{oid::AssociatedOid, DerOrd, Enumerated, Reader, Sequence, SliceReader};
+use der::{Decode, EncodeValue, Length};
+use std::borrow::Cow;
+
+/// Determine the tag type for a tag, based on the top 4 bits of the tag number.
+fn tag_type(tag: Tag) -> TagType {
+ let raw_type = (tag.0 as u32) & 0xf0000000;
+ TagType(raw_type as i32)
+}
+
+/// Determine the raw tag value with tag type information stripped out.
+fn raw_tag_value(tag: Tag) -> u32 {
+ (tag.0 as u32) & 0x0fffffffu32
+}
+
+/// OID value for the Android Attestation extension.
+pub const ATTESTATION_EXTENSION_OID: ObjectIdentifier =
+ ObjectIdentifier::new_unwrap("1.3.6.1.4.1.11129.2.1.17");
+
+/// Attestation extension contents
+#[derive(Debug, Clone, Sequence, PartialEq)]
+pub struct AttestationExtension<'a> {
+ /// Attestation version.
+ pub attestation_version: i32,
+ /// Security level that created the attestation.
+ pub attestation_security_level: SecurityLevel,
+ /// Keymint version.
+ pub keymint_version: i32,
+ /// Security level of the KeyMint instance holding the key.
+ pub keymint_security_level: SecurityLevel,
+ /// Attestation challenge.
+ #[asn1(type = "OCTET STRING")]
+ pub attestation_challenge: &'a [u8],
+ /// Unique ID.
+ #[asn1(type = "OCTET STRING")]
+ pub unique_id: &'a [u8],
+ /// Software-enforced key characteristics.
+ pub sw_enforced: AuthorizationList<'a>,
+ /// Hardware-enforced key characteristics.
+ pub hw_enforced: AuthorizationList<'a>,
+}
+
+impl AssociatedOid for AttestationExtension<'_> {
+ const OID: ObjectIdentifier = ATTESTATION_EXTENSION_OID;
+}
+
+/// Security level enumeration
+#[repr(u32)]
+#[derive(Debug, Clone, Copy, Enumerated, PartialEq)]
+pub enum SecurityLevel {
+ /// Software.
+ Software = 0,
+ /// TEE.
+ TrustedEnvironment = 1,
+ /// StrongBox.
+ Strongbox = 2,
+}
+
+/// Root of Trust ASN.1 structure
+#[derive(Debug, Clone, Sequence)]
+pub struct RootOfTrust<'a> {
+ /// Verified boot key hash.
+ #[asn1(type = "OCTET STRING")]
+ pub verified_boot_key: &'a [u8],
+ /// Device bootloader lock state.
+ pub device_locked: bool,
+ /// Verified boot state.
+ pub verified_boot_state: VerifiedBootState,
+ /// Verified boot hash
+ #[asn1(type = "OCTET STRING")]
+ pub verified_boot_hash: &'a [u8],
+}
+
+/// Attestation Application ID ASN.1 structure
+#[derive(Debug, Clone, Sequence)]
+pub struct AttestationApplicationId<'a> {
+ /// Package info.
+ pub package_info_records: SetOfVec<PackageInfoRecord<'a>>,
+ /// Signatures.
+ pub signature_digests: SetOfVec<OctetStringRef<'a>>,
+}
+
+/// Package record
+#[derive(Debug, Clone, Sequence)]
+pub struct PackageInfoRecord<'a> {
+ /// Package name
+ pub package_name: OctetStringRef<'a>,
+ /// Package version
+ pub version: i64,
+}
+
+impl DerOrd for PackageInfoRecord<'_> {
+ fn der_cmp(&self, other: &Self) -> Result<std::cmp::Ordering, der::Error> {
+ self.package_name.der_cmp(&other.package_name)
+ }
+}
+
+/// Verified Boot State as ASN.1 ENUMERATED type.
+#[repr(u32)]
+#[derive(Debug, Clone, Copy, Enumerated)]
+pub enum VerifiedBootState {
+ /// Verified.
+ Verified = 0,
+ /// Self-signed.
+ SelfSigned = 1,
+ /// Unverified.
+ Unverified = 2,
+ /// Failed.
+ Failed = 3,
+}
+
+/// Struct corresponding to an ASN.1 DER-serialized `AuthorizationList`.
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct AuthorizationList<'a> {
+ /// Key authorizations.
+ pub auths: Cow<'a, [KeyParameter]>,
+}
+
+impl From<Vec<KeyParameter>> for AuthorizationList<'_> {
+ /// Build an `AuthorizationList` using a set of key parameters.
+ fn from(auths: Vec<KeyParameter>) -> Self {
+ AuthorizationList { auths: auths.into() }
+ }
+}
+
+impl<'a> Sequence<'a> for AuthorizationList<'a> {}
+
+/// Stub (non-)implementation of DER-encoding, needed to implement [`Sequence`].
+impl EncodeValue for AuthorizationList<'_> {
+ fn value_len(&self) -> der::Result<Length> {
+ unimplemented!("Only decoding is implemented");
+ }
+ fn encode_value(&self, _writer: &mut impl der::Writer) -> der::Result<()> {
+ unimplemented!("Only decoding is implemented");
+ }
+}
+
+/// Implementation of [`der::DecodeValue`] which constructs an [`AuthorizationList`] from bytes.
+impl<'a> der::DecodeValue<'a> for AuthorizationList<'a> {
+ fn decode_value<R: der::Reader<'a>>(decoder: &mut R, header: der::Header) -> der::Result<Self> {
+ // Decode tags in the expected order.
+ let contents = decoder.read_slice(header.length)?;
+ let mut reader = SliceReader::new(contents)?;
+ let decoder = &mut reader;
+ let mut auths = Vec::new();
+ let mut next: Option<u32> = None;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::PURPOSE)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ALGORITHM)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::KEY_SIZE)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::BLOCK_MODE)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::DIGEST)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::PADDING)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::CALLER_NONCE)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::MIN_MAC_LENGTH)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::EC_CURVE)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::RSA_PUBLIC_EXPONENT)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::RSA_OAEP_MGF_DIGEST)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ROLLBACK_RESISTANCE)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::EARLY_BOOT_ONLY)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ACTIVE_DATETIME)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ORIGINATION_EXPIRE_DATETIME)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::USAGE_EXPIRE_DATETIME)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::USAGE_COUNT_LIMIT)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::USER_SECURE_ID)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::NO_AUTH_REQUIRED)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::USER_AUTH_TYPE)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::AUTH_TIMEOUT)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ALLOW_WHILE_ON_BODY)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::TRUSTED_USER_PRESENCE_REQUIRED)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::TRUSTED_CONFIRMATION_REQUIRED)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::UNLOCKED_DEVICE_REQUIRED)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::CREATION_DATETIME)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::CREATION_DATETIME)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ORIGIN)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ROOT_OF_TRUST)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::OS_VERSION)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::OS_PATCHLEVEL)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_APPLICATION_ID)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_BRAND)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_DEVICE)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_PRODUCT)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_SERIAL)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_SERIAL)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_SERIAL)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_IMEI)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_MEID)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_MANUFACTURER)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_MODEL)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::VENDOR_PATCHLEVEL)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::BOOT_PATCHLEVEL)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::DEVICE_UNIQUE_ATTESTATION)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_SECOND_IMEI)?;
+ next = decode_opt_field(decoder, next, &mut auths, Tag::MODULE_HASH)?;
+
+ if next.is_some() {
+ // Extra tag encountered.
+ return Err(decoder.error(der::ErrorKind::Incomplete {
+ expected_len: Length::ZERO,
+ actual_len: decoder.remaining_len(),
+ }));
+ }
+
+ Ok(auths.into())
+ }
+}
+
+/// Attempt to decode an optional field associated with `expected_tag` from the `decoder`.
+///
+/// If `already_read_asn1_tag` is provided, then that ASN.1 tag has already been read from the
+/// `decoder` and its associated data is next.
+///
+/// (Because the field is optional, we might not read the tag we expect, but instead a later tag
+/// from the list. If this happens, the actual decoded ASN.1 tag value is returned to the caller to
+/// be passed in on the next call to this function.)
+///
+/// If the decoded or re-used ASN.1 tag is the expected one, continue on to read the associated
+/// value and populate it in `auths`.
+fn decode_opt_field<'a, R: der::Reader<'a>>(
+ decoder: &mut R,
+ already_read_asn1_tag: Option<u32>,
+ auths: &mut Vec<KeyParameter>,
+ expected_tag: Tag,
+) -> Result<Option<u32>, der::Error> {
+ // Decode the ASN.1 tag if no tag is provided
+ let asn1_tag = match already_read_asn1_tag {
+ Some(tag) => Some(tag),
+ None => decode_explicit_tag_from_bytes(decoder)?,
+ };
+ let expected_asn1_tag = raw_tag_value(expected_tag);
+ match asn1_tag {
+ Some(v) if v == expected_asn1_tag => {
+ // Decode the length of the inner encoding
+ let inner_len = Length::decode(decoder)?;
+ if decoder.remaining_len() < inner_len {
+ return Err(der::ErrorKind::Incomplete {
+ expected_len: inner_len,
+ actual_len: decoder.remaining_len(),
+ }
+ .into());
+ }
+ let next_tlv = decoder.tlv_bytes()?;
+ decode_value_from_bytes(expected_tag, next_tlv, auths)?;
+ Ok(None)
+ }
+ Some(tag) => Ok(Some(tag)), // Return the tag for which the value is unread.
+ None => Ok(None),
+ }
+}
+
+/// Decode one or more `KeyParameterValue`s of the type associated with `tag` from the `decoder`,
+/// and add them to `auths`.
+fn decode_value_from_bytes(
+ tag: Tag,
+ data: &[u8],
+ auths: &mut Vec<KeyParameter>,
+) -> Result<(), der::Error> {
+ match tag_type(tag) {
+ TagType::ENUM_REP => {
+ let values = SetOfVec::<i32>::from_der(data)?;
+ for value in values.as_slice() {
+ auths.push(KeyParameter {
+ tag,
+ value: match tag {
+ Tag::BLOCK_MODE => KPV::BlockMode(BlockMode(*value)),
+ Tag::PADDING => KPV::PaddingMode(PaddingMode(*value)),
+ Tag::DIGEST => KPV::Digest(Digest(*value)),
+ Tag::RSA_OAEP_MGF_DIGEST => KPV::Digest(Digest(*value)),
+ Tag::PURPOSE => KPV::KeyPurpose(KeyPurpose(*value)),
+ _ => return Err(der::ErrorKind::TagNumberInvalid.into()),
+ },
+ });
+ }
+ }
+ TagType::UINT_REP => {
+ let values = SetOfVec::<i32>::from_der(data)?;
+ for value in values.as_slice() {
+ auths.push(KeyParameter { tag, value: KPV::Integer(*value) });
+ }
+ }
+ TagType::ENUM => {
+ let value = i32::from_der(data)?;
+ auths.push(KeyParameter {
+ tag,
+ value: match tag {
+ Tag::ALGORITHM => KPV::Algorithm(Algorithm(value)),
+ Tag::EC_CURVE => KPV::EcCurve(EcCurve(value)),
+ Tag::ORIGIN => KPV::Origin(KeyOrigin(value)),
+ Tag::USER_AUTH_TYPE => {
+ KPV::HardwareAuthenticatorType(HardwareAuthenticatorType(value))
+ }
+ _ => return Err(der::ErrorKind::TagNumberInvalid.into()),
+ },
+ });
+ }
+ TagType::UINT => {
+ let value = i32::from_der(data)?;
+ auths.push(KeyParameter { tag, value: KPV::Integer(value) });
+ }
+ TagType::ULONG => {
+ let value = i64::from_der(data)?;
+ auths.push(KeyParameter { tag, value: KPV::LongInteger(value) });
+ }
+ TagType::DATE => {
+ let value = i64::from_der(data)?;
+ auths.push(KeyParameter { tag, value: KPV::DateTime(value) });
+ }
+ TagType::BOOL => {
+ let _value = Null::from_der(data)?;
+ auths.push(KeyParameter { tag, value: KPV::BoolValue(true) });
+ }
+ TagType::BYTES if tag == Tag::ROOT_OF_TRUST => {
+ // Special case: root of trust is an ASN.1 `SEQUENCE` not an `OCTET STRING` so don't
+ // decode the bytes.
+ auths.push(KeyParameter { tag: Tag::ROOT_OF_TRUST, value: KPV::Blob(data.to_vec()) });
+ }
+ TagType::BYTES | TagType::BIGNUM => {
+ let value = OctetStringRef::from_der(data)?.as_bytes().to_vec();
+ auths.push(KeyParameter { tag, value: KPV::Blob(value) });
+ }
+ _ => {
+ return Err(der::ErrorKind::TagNumberInvalid.into());
+ }
+ }
+ Ok(())
+}
+
+/// Decode an explicit ASN.1 tag value, coping with large (>=31) tag values
+/// (which the `der` crate doesn't deal with). Returns `Ok(None)` if the
+/// decoder is empty.
+fn decode_explicit_tag_from_bytes<'a, R: der::Reader<'a>>(
+ decoder: &mut R,
+) -> Result<Option<u32>, der::Error> {
+ if decoder.remaining_len() == Length::ZERO {
+ return Ok(None);
+ }
+ let b1 = decoder.read_byte()?;
+ let tag = if b1 & 0b00011111 == 0b00011111u8 {
+ // The initial byte of 0xbf indicates a larger (>=31) value for the ASN.1 tag:
+ // - 0bXY...... = class
+ // - 0b..C..... = constructed/primitive bit
+ // - 0b...11111 = marker indicating high tag form, tag value to follow
+ //
+ // The top three bits should be 0b101 = constructed context-specific
+ if b1 & 0b11100000 != 0b10100000 {
+ return Err(der::ErrorKind::TagNumberInvalid.into());
+ }
+
+ // The subsequent encoded tag value is broken down into 7-bit chunks (in big-endian order),
+ // and each chunk gets a high bit of 1 except the last, which gets a high bit of zero.
+ let mut bit_count = 0;
+ let mut tag: u32 = 0;
+ loop {
+ let b = decoder.read_byte()?;
+ let low_b = b & 0b01111111;
+ if bit_count == 0 && low_b == 0 {
+ // The first part of the tag number is zero, implying it is not miminally encoded.
+ return Err(der::ErrorKind::TagNumberInvalid.into());
+ }
+
+ bit_count += 7;
+ if bit_count > 32 {
+ // Tag value has more bits than the output type can hold.
+ return Err(der::ErrorKind::TagNumberInvalid.into());
+ }
+ tag = (tag << 7) | (low_b as u32);
+ if b & 0x80u8 == 0x00u8 {
+ // Top bit clear => this is the final part of the value.
+ if tag < 31 {
+ // Tag is small enough that it should have been in short form.
+ return Err(der::ErrorKind::TagNumberInvalid.into());
+ }
+ break tag;
+ }
+ }
+ } else {
+ // Get the tag value from the low 5 bits.
+ (b1 & 0b00011111u8) as u32
+ };
+ Ok(Some(tag))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use der::Encode;
+
+ const SIG: &[u8; 32] = &[
+ 0xa4, 0x0d, 0xa8, 0x0a, 0x59, 0xd1, 0x70, 0xca, 0xa9, 0x50, 0xcf, 0x15, 0xc1, 0x8c, 0x45,
+ 0x4d, 0x47, 0xa3, 0x9b, 0x26, 0x98, 0x9d, 0x8b, 0x64, 0x0e, 0xcd, 0x74, 0x5b, 0xa7, 0x1b,
+ 0xf5, 0xdc,
+ ];
+ const VB_KEY: &[u8; 32] = &[0; 32];
+ const VB_HASH: &[u8; 32] = &[
+ 0x6f, 0x84, 0xe6, 0x02, 0x73, 0x9d, 0x86, 0x2c, 0x93, 0x2a, 0x28, 0xf0, 0xa5, 0x27, 0x65,
+ 0xa4, 0xae, 0xc2, 0x27, 0x8c, 0xb6, 0x3b, 0xe9, 0xbb, 0x63, 0xc7, 0xa8, 0xc7, 0x03, 0xad,
+ 0x8e, 0xc1,
+ ];
+
+ /// Build a sample `AuthorizationList` suitable for use as `sw_enforced`.
+ fn sw_enforced() -> AuthorizationList<'static> {
+ let sig = OctetStringRef::new(SIG).unwrap();
+ let package = PackageInfoRecord {
+ package_name: OctetStringRef::new(b"android.keystore.cts").unwrap(),
+ version: 34,
+ };
+ let mut package_info_records = SetOfVec::new();
+ package_info_records.insert(package).unwrap();
+ let mut signature_digests = SetOfVec::new();
+ signature_digests.insert(sig).unwrap();
+ let aaid = AttestationApplicationId { package_info_records, signature_digests };
+ AuthorizationList {
+ auths: vec![
+ KeyParameter { tag: Tag::CREATION_DATETIME, value: KPV::DateTime(0x01903116c71f) },
+ KeyParameter {
+ tag: Tag::ATTESTATION_APPLICATION_ID,
+ value: KPV::Blob(aaid.to_der().unwrap()),
+ },
+ ]
+ .into(),
+ }
+ }
+
+ /// Build a sample `AuthorizationList` suitable for use as `hw_enforced`.
+ fn hw_enforced() -> AuthorizationList<'static> {
+ let rot = RootOfTrust {
+ verified_boot_key: VB_KEY,
+ device_locked: false,
+ verified_boot_state: VerifiedBootState::Unverified,
+ verified_boot_hash: VB_HASH,
+ };
+ AuthorizationList {
+ auths: vec![
+ KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::AGREE_KEY) },
+ KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) },
+ KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(256) },
+ KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
+ KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::CURVE_25519) },
+ KeyParameter { tag: Tag::NO_AUTH_REQUIRED, value: KPV::BoolValue(true) },
+ KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+ KeyParameter { tag: Tag::ROOT_OF_TRUST, value: KPV::Blob(rot.to_der().unwrap()) },
+ KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(140000) },
+ KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202404) },
+ KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20240405) },
+ KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(20240405) },
+ ]
+ .into(),
+ }
+ }
+
+ #[test]
+ fn test_decode_auth_list_1() {
+ let want = sw_enforced();
+ let data = hex::decode(concat!(
+ "3055", // SEQUENCE
+ "bf853d08", // [701]
+ "0206", // INTEGER
+ "01903116c71f",
+ "bf854545", // [709]
+ "0443", // OCTET STRING
+ "3041", // SEQUENCE
+ "311b", // SET
+ "3019", // SEQUENCE
+ "0414", // OCTET STRING
+ "616e64726f69642e6b657973746f72652e637473", // "android.keystore.cts"
+ "020122", // INTEGER
+ "3122", // SET
+ "0420", // OCTET STRING
+ "a40da80a59d170caa950cf15c18c454d",
+ "47a39b26989d8b640ecd745ba71bf5dc",
+ ))
+ .unwrap();
+ let got = AuthorizationList::from_der(&data).unwrap();
+ assert_eq!(got, want);
+ }
+
+ #[test]
+ fn test_decode_auth_list_2() {
+ let want = hw_enforced();
+ let data = hex::decode(concat!(
+ "3081a1", // SEQUENCE
+ "a105", // [1]
+ "3103", // SET
+ "020106", // INTEGER
+ "a203", // [2]
+ "020103", // INTEGER 3
+ "a304", // [4]
+ "02020100", // INTEGER 256
+ "a505", // [5]
+ "3103", // SET
+ "020100", // INTEGER 0
+ "aa03", // [10]
+ "020104", // INTEGER 4
+ "bf837702", // [503]
+ "0500", // NULL
+ "bf853e03", // [702]
+ "020100", // INTEGER 0
+ "bf85404c", // [704]
+ "304a", // SEQUENCE
+ "0420", // OCTET STRING
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "010100", // BOOLEAN
+ "0a0102", // ENUMERATED
+ "0420", // OCTET STRING
+ "6f84e602739d862c932a28f0a52765a4",
+ "aec2278cb63be9bb63c7a8c703ad8ec1",
+ "bf854105", // [705]
+ "02030222e0", // INTEGER
+ "bf854205", // [706]
+ "02030316a4", // INTEGER
+ "bf854e06", // [718]
+ "02040134d815", // INTEGER
+ "bf854f06", // [709]
+ "02040134d815", // INTEGER
+ ))
+ .unwrap();
+ let got = AuthorizationList::from_der(&data).unwrap();
+ assert_eq!(got, want);
+ }
+
+ #[test]
+ fn test_decode_extension() {
+ let zeroes = [0; 128];
+ let want = AttestationExtension {
+ attestation_version: 300,
+ attestation_security_level: SecurityLevel::TrustedEnvironment,
+ keymint_version: 300,
+ keymint_security_level: SecurityLevel::TrustedEnvironment,
+ attestation_challenge: &zeroes,
+ unique_id: &[],
+ sw_enforced: sw_enforced(),
+ hw_enforced: hw_enforced(),
+ };
+
+ let data = hex::decode(concat!(
+ // Full extension would include the following prefix:
+ // "308201a2", // SEQUENCE
+ // "060a", // OBJECT IDENTIFIER
+ // "2b06010401d679020111", // Android attestation extension (1.3.6.1.4.1.11129.2.1.17)
+ // "04820192", // OCTET STRING
+ "3082018e", // SEQUENCE
+ "0202012c", // INTEGER 300
+ "0a0101", // ENUMERATED 1
+ "0202012c", // INTEGER 300
+ "0a0101", // ENUMERATED 1
+ "048180", // OCTET STRING
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "0400", // OCTET STRING
+ // softwareEnforced
+ "3055", // SEQUENCE
+ "bf853d08", // [701]
+ "0206", // INTEGER
+ "01903116c71f",
+ "bf854545", // [709]
+ "0443", // OCTET STRING
+ "3041", // SEQUENCE
+ "311b", // SET
+ "3019", // SEQUENCE
+ "0414", // OCTET STRING
+ "616e64726f69642e6b657973746f72652e637473", // "android.keystore.cts"
+ "020122", // INTEGER
+ "3122", // SET
+ "0420", // OCTET STRING
+ "a40da80a59d170caa950cf15c18c454d",
+ "47a39b26989d8b640ecd745ba71bf5dc",
+ // softwareEnforced
+ "3081a1", // SEQUENCE
+ "a105", // [1]
+ "3103", // SET
+ "020106", // INTEGER
+ "a203", // [2]
+ "020103", // INTEGER 3
+ "a304", // [4]
+ "02020100", // INTEGER 256
+ "a505", // [5]
+ "3103", // SET
+ "020100", // INTEGER 0
+ "aa03", // [10]
+ "020104", // INTEGER 4
+ "bf837702", // [503]
+ "0500", // NULL
+ "bf853e03", // [702]
+ "020100", // INTEGER 0
+ "bf85404c", // [704]
+ "304a", // SEQUENCE
+ "0420", // OCTET STRING
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "010100", // BOOLEAN
+ "0a0102", // ENUMERATED
+ "0420", // OCTET STRING
+ "6f84e602739d862c932a28f0a52765a4",
+ "aec2278cb63be9bb63c7a8c703ad8ec1",
+ "bf854105", // [705]
+ "02030222e0", // INTEGER
+ "bf854205", // [706]
+ "02030316a4", // INTEGER
+ "bf854e06", // [718]
+ "02040134d815", // INTEGER
+ "bf854f06", // [719]
+ "02040134d815", // INTEGER
+ ))
+ .unwrap();
+ let got = AttestationExtension::from_der(&data).unwrap();
+ assert_eq!(got, want);
+ }
+
+ #[test]
+ fn test_decode_empty_auth_list() {
+ let want = AuthorizationList::default();
+ let data = hex::decode(
+ "3000", // SEQUENCE
+ )
+ .unwrap();
+ let got = AuthorizationList::from_der(&data).unwrap();
+ assert_eq!(got, want);
+ }
+
+ #[test]
+ fn test_decode_explicit_tag() {
+ let err = Err(der::ErrorKind::TagNumberInvalid.into());
+ let tests = [
+ (vec![], Ok(None)),
+ (vec![0b10100000], Ok(Some(0))),
+ (vec![0b10100001], Ok(Some(1))),
+ (vec![0b10100010], Ok(Some(2))),
+ (vec![0b10111110], Ok(Some(30))),
+ (vec![0b10111111, 0b00011111], Ok(Some(31))),
+ (vec![0b10111111, 0b00100000], Ok(Some(32))),
+ (vec![0b10111111, 0b01111111], Ok(Some(127))),
+ (vec![0b10111111, 0b10000001, 0b00000000], Ok(Some(128))),
+ (vec![0b10111111, 0b10000010, 0b00000000], Ok(Some(256))),
+ (vec![0b10111111, 0b10000001, 0b10000000, 0b00000001], Ok(Some(16385))),
+ (vec![0b10111111, 0b10010000, 0b10000000, 0b10000000, 0b00000000], Ok(Some(33554432))),
+ // Top bits ignored for low tag numbers
+ (vec![0b00000000], Ok(Some(0))),
+ (vec![0b00000001], Ok(Some(1))),
+ // High tag numbers should start with 0b101
+ (vec![0b10011111, 0b00100000], err),
+ (vec![0b11111111, 0b00100000], err),
+ (vec![0b00111111, 0b00100000], err),
+ // High tag numbers should be minimally encoded
+ (vec![0b10111111, 0b10000000, 0b10000001, 0b00000000], err),
+ (vec![0b10111111, 0b00011110], err),
+ // Bigger than u32
+ (
+ vec![
+ 0b10111111, 0b10000001, 0b10000000, 0b10000000, 0b10000000, 0b10000000,
+ 0b00000000,
+ ],
+ err,
+ ),
+ // Incomplete tag
+ (vec![0b10111111, 0b10000001], Err(der::Error::incomplete(der::Length::new(2)))),
+ ];
+
+ for (input, want) in tests {
+ let mut reader = SliceReader::new(&input).unwrap();
+ let got = decode_explicit_tag_from_bytes(&mut reader);
+ assert_eq!(got, want, "for input {}", hex::encode(input));
+ }
+ }
+}
diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs
index a96d994..d3d6fc4 100644
--- a/keystore2/test_utils/authorizations.rs
+++ b/keystore2/test_utils/authorizations.rs
@@ -18,8 +18,9 @@
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
- KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose,
- PaddingMode::PaddingMode, Tag::Tag,
+ HardwareAuthenticatorType::HardwareAuthenticatorType, KeyParameter::KeyParameter,
+ KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
+ Tag::Tag,
};
/// Helper struct to create set of Authorizations.
@@ -369,6 +370,33 @@
});
self
}
+
+ /// Set user secure ID.
+ pub fn user_secure_id(mut self, sid: i64) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::USER_SECURE_ID,
+ value: KeyParameterValue::LongInteger(sid),
+ });
+ self
+ }
+
+ /// Set user auth type.
+ pub fn user_auth_type(mut self, auth_type: HardwareAuthenticatorType) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::USER_AUTH_TYPE,
+ value: KeyParameterValue::HardwareAuthenticatorType(auth_type),
+ });
+ self
+ }
+
+ /// Set auth timeout.
+ pub fn auth_timeout(mut self, timeout_secs: i32) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::AUTH_TIMEOUT,
+ value: KeyParameterValue::Integer(timeout_secs),
+ });
+ self
+ }
}
impl Deref for AuthSetBuilder {
diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs
index fdb2afb..98b227b 100644
--- a/keystore2/test_utils/key_generations.rs
+++ b/keystore2/test_utils/key_generations.rs
@@ -38,6 +38,7 @@
use nix::unistd::getuid;
use std::collections::HashSet;
use std::fmt::Write;
+use std::path::PathBuf;
/// Shell namespace.
pub const SELINUX_SHELL_NAMESPACE: i64 = 1;
@@ -50,6 +51,10 @@
/// Vold context
pub const TARGET_VOLD_CTX: &str = "u:r:vold:s0";
+const TEE_KEYMINT_RKP_ONLY: &str = "remote_provisioning.tee.rkp_only";
+
+const STRONGBOX_KEYMINT_RKP_ONLY: &str = "remote_provisioning.strongbox.rkp_only";
+
/// Allowed tags in generated/imported key authorizations.
/// See hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl for the
/// list feature tags.
@@ -387,6 +392,57 @@
})
}
+/// Check for a specific KeyMint error.
+#[macro_export]
+macro_rules! expect_km_error {
+ { $result:expr, $want:expr } => {
+ match $result {
+ Ok(_) => return Err(format!(
+ "{}:{}: Expected KeyMint error {:?}, found success",
+ file!(),
+ line!(),
+ $want
+ ).into()),
+ Err(s) if s.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+ && s.service_specific_error() == $want.0 => {}
+ Err(e) => return Err(format!(
+ "{}:{}: Expected KeyMint service-specific error {:?}, got {e:?}",
+ file!(),
+ line!(),
+ $want
+ ).into()),
+ }
+
+ };
+}
+
+/// Get the value of the given system property, if the given system property doesn't exist
+/// then returns an empty byte vector.
+pub fn get_system_prop(name: &str) -> Vec<u8> {
+ match rustutils::system_properties::read(name) {
+ Ok(Some(value)) => value.as_bytes().to_vec(),
+ _ => vec![],
+ }
+}
+
+/// Determines whether test is running on GSI.
+pub fn is_gsi() -> bool {
+ // This file is only present on GSI builds.
+ PathBuf::from("/system/system_ext/etc/init/init.gsi.rc").as_path().is_file()
+}
+
+/// Determines whether the test is on a GSI build where the rkp-only status of the device is
+/// unknown. GSI replaces the values for remote_prov_prop properties (since they’re
+/// system_internal_prop properties), so on GSI the properties are not reliable indicators of
+/// whether StrongBox/TEE is RKP-only or not.
+pub fn is_rkp_only_unknown_on_gsi(sec_level: SecurityLevel) -> bool {
+ if sec_level == SecurityLevel::TRUSTED_ENVIRONMENT {
+ is_gsi() && get_system_prop(TEE_KEYMINT_RKP_ONLY).is_empty()
+ } else {
+ is_gsi() && get_system_prop(STRONGBOX_KEYMINT_RKP_ONLY).is_empty()
+ }
+}
+
/// Verify that given key param is listed in given authorizations list.
pub fn check_key_param(authorizations: &[Authorization], key_param: &KeyParameter) -> bool {
authorizations.iter().any(|auth| &auth.keyParameter == key_param)
@@ -401,9 +457,8 @@
) {
// Make sure key authorizations contains only `ALLOWED_TAGS_IN_KEY_AUTHS`
authorizations.iter().all(|auth| {
- // Ignore `INVALID` tag if the backend is Keymaster and not KeyMint.
- // Keymaster allows INVALID tag for unsupported key parameters.
- if sl.is_keymaster() && auth.keyParameter.tag == Tag::INVALID {
+ // Ignore `INVALID` tag
+ if auth.keyParameter.tag == Tag::INVALID {
return true;
}
assert!(
@@ -424,13 +479,30 @@
return true;
}
- // Ignore below parameters if the backend is Keymaster and not KeyMint.
- // Keymaster does not support these parameters. These key parameters are introduced in
- // KeyMint1.0.
+ // `Tag::RSA_OAEP_MGF_DIGEST` was added in KeyMint 1.0, but the KeyMint VTS tests didn't
+ // originally check for its presence and so some implementations of early versions (< 3) of
+ // the KeyMint HAL don't include it (cf. b/297306437 and aosp/2758513).
+ //
+ // Given that Keymaster implementations will also omit this tag, skip the check for it
+ // altogether (and rely on the updated KeyMint VTS tests to ensure that up-level KeyMint
+ // implementations correctly populate this tag).
+ if matches!(key_param.tag, Tag::RSA_OAEP_MGF_DIGEST) {
+ return true;
+ }
+
+ // Don't check these parameters if the underlying device is a Keymaster implementation.
if sl.is_keymaster() {
- if matches!(key_param.tag, Tag::RSA_OAEP_MGF_DIGEST | Tag::USAGE_COUNT_LIMIT) {
+ if matches!(
+ key_param.tag,
+ // `Tag::USAGE_COUNT_LIMIT` was added in KeyMint 1.0.
+ Tag::USAGE_COUNT_LIMIT |
+ // Keymaster implementations may not consistently include `Tag::VENDOR_PATCHLEVEL`
+ // in generated key characteristics.
+ Tag::VENDOR_PATCHLEVEL
+ ) {
return true;
}
+ // `KeyPurpose::ATTEST_KEY` was added in KeyMint 1.0.
if key_param.tag == Tag::PURPOSE
&& key_param.value == KeyParameterValue::KeyPurpose(KeyPurpose::ATTEST_KEY)
{
@@ -464,26 +536,28 @@
value: KeyParameterValue::Integer(get_os_version().try_into().unwrap())
}
));
- assert!(check_key_param(
- authorizations,
- &KeyParameter {
- tag: Tag::OS_PATCHLEVEL,
- value: KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())
- }
- ));
-
- // Access denied for finding vendor-patch-level ("ro.vendor.build.security_patch") property
- // in a test running with `untrusted_app` context. Keeping this check to verify
- // vendor-patch-level in tests running with `su` context.
- if getuid().is_root() {
+ if is_gsi() && sl.is_keymaster() {
+ // The expected value of TAG::OS_PATCHLEVEL should match the system's reported
+ // OS patch level (obtained via get_os_patchlevel()). However, booting a Generic System
+ // Image (GSI) with a newer patch level is permitted. Therefore, the generated key's
+ // TAG::OS_PATCHLEVEL may be less than or equal to the current system's OS patch level.
+ assert!(authorizations.iter().map(|auth| &auth.keyParameter).any(|key_param| key_param
+ .tag
+ == Tag::OS_PATCHLEVEL
+ && key_param.value
+ <= KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())));
+ } else {
+ // The KeyMint spec required that the patch-levels match that of the running system, even
+ // under GSI.
assert!(check_key_param(
authorizations,
&KeyParameter {
- tag: Tag::VENDOR_PATCHLEVEL,
- value: KeyParameterValue::Integer(get_vendor_patchlevel().try_into().unwrap())
+ tag: Tag::OS_PATCHLEVEL,
+ value: KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())
}
));
}
+
assert!(check_key_param(
authorizations,
&KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(expected_key_origin) }
@@ -505,6 +579,22 @@
.iter()
.map(|auth| &auth.keyParameter)
.any(|key_param| key_param.tag == Tag::CREATION_DATETIME));
+
+ // Access denied for finding vendor-patch-level ("ro.vendor.build.security_patch") property
+ // in a test running with `untrusted_app` context. Keeping this check to verify
+ // vendor-patch-level in tests running with `su` context.
+ if getuid().is_root() {
+ // Keymaster implementations may not consistently include `Tag::VENDOR_PATCHLEVEL`
+ // in generated key characteristics. So, checking this if the underlying device is a
+ // KeyMint implementation.
+ assert!(check_key_param(
+ authorizations,
+ &KeyParameter {
+ tag: Tag::VENDOR_PATCHLEVEL,
+ value: KeyParameterValue::Integer(get_vendor_patchlevel().try_into().unwrap())
+ }
+ ));
+ }
}
}
@@ -623,7 +713,7 @@
alias: Option<String>,
key_params: &KeyParams,
attest_key: Option<&KeyDescriptor>,
-) -> binder::Result<KeyMetadata> {
+) -> binder::Result<Option<KeyMetadata>> {
let mut gen_params = AuthSetBuilder::new()
.no_auth_required()
.algorithm(Algorithm::RSA)
@@ -649,13 +739,29 @@
gen_params = gen_params.attestation_challenge(value.to_vec())
}
- let key_metadata = sl.binder.generateKey(
+ let key_metadata = match sl.binder.generateKey(
&KeyDescriptor { domain, nspace, alias, blob: None },
attest_key,
&gen_params,
0,
b"entropy",
- )?;
+ ) {
+ Ok(metadata) => metadata,
+ Err(e) => {
+ return if is_rkp_only_unknown_on_gsi(sl.level)
+ && e.service_specific_error() == ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED.0
+ {
+ // GSI replaces the values for remote_prov_prop properties (since they’re
+ // system_internal_prop properties), so on GSI the properties are not
+ // reliable indicators of whether StrongBox/TEE are RKP-only or not.
+ // Test can be skipped if it generates a key with attestation but doesn't provide
+ // an ATTEST_KEY and rkp-only property is undetermined.
+ Ok(None)
+ } else {
+ Err(e)
+ };
+ }
+ };
// Must have a public key.
assert!(key_metadata.certificate.is_some());
@@ -686,7 +792,7 @@
}
));
}
- Ok(key_metadata)
+ Ok(Some(key_metadata))
}
/// Generate AES/3DES key.
@@ -784,12 +890,12 @@
sl: &SecLevel,
algorithm: Algorithm,
att_challenge: &[u8],
-) -> binder::Result<KeyMetadata> {
+) -> binder::Result<Option<KeyMetadata>> {
assert!(algorithm == Algorithm::RSA || algorithm == Algorithm::EC);
if algorithm == Algorithm::RSA {
let alias = "ks_rsa_attest_test_key";
- let metadata = generate_rsa_key(
+ generate_rsa_key(
sl,
Domain::APP,
-1,
@@ -805,14 +911,8 @@
},
None,
)
- .unwrap();
- Ok(metadata)
} else {
- let metadata =
- generate_ec_attestation_key(sl, att_challenge, Digest::SHA_2_256, EcCurve::P_256)
- .unwrap();
-
- Ok(metadata)
+ generate_ec_attestation_key(sl, att_challenge, Digest::SHA_2_256, EcCurve::P_256)
}
}
@@ -823,7 +923,7 @@
att_challenge: &[u8],
digest: Digest,
ec_curve: EcCurve,
-) -> binder::Result<KeyMetadata> {
+) -> binder::Result<Option<KeyMetadata>> {
let alias = "ks_attest_ec_test_key";
let gen_params = AuthSetBuilder::new()
.no_auth_required()
@@ -833,7 +933,7 @@
.digest(digest)
.attestation_challenge(att_challenge.to_vec());
- let attestation_key_metadata = sl.binder.generateKey(
+ let attestation_key_metadata = match sl.binder.generateKey(
&KeyDescriptor {
domain: Domain::APP,
nspace: -1,
@@ -844,7 +944,23 @@
&gen_params,
0,
b"entropy",
- )?;
+ ) {
+ Ok(metadata) => metadata,
+ Err(e) => {
+ return if is_rkp_only_unknown_on_gsi(sl.level)
+ && e.service_specific_error() == ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED.0
+ {
+ // GSI replaces the values for remote_prov_prop properties (since they’re
+ // system_internal_prop properties), so on GSI the properties are not
+ // reliable indicators of whether StrongBox/TEE are RKP-only or not.
+ // Test can be skipped if it generates a key with attestation but doesn't provide
+ // an ATTEST_KEY and rkp-only property is undetermined.
+ Ok(None)
+ } else {
+ Err(e)
+ };
+ }
+ };
// Should have public certificate.
assert!(attestation_key_metadata.certificate.is_some());
@@ -857,7 +973,7 @@
&gen_params,
KeyOrigin::GENERATED,
);
- Ok(attestation_key_metadata)
+ Ok(Some(attestation_key_metadata))
}
/// Generate EC-P-256 key and attest it with given attestation key.
@@ -1421,8 +1537,8 @@
sl: &SecLevel,
gen_params: &AuthSetBuilder,
alias: &str,
-) -> binder::Result<KeyMetadata> {
- let key_metadata = sl.binder.generateKey(
+) -> binder::Result<Option<KeyMetadata>> {
+ let key_metadata = match sl.binder.generateKey(
&KeyDescriptor {
domain: Domain::APP,
nspace: -1,
@@ -1433,7 +1549,23 @@
gen_params,
0,
b"entropy",
- )?;
+ ) {
+ Ok(metadata) => metadata,
+ Err(e) => {
+ return if is_rkp_only_unknown_on_gsi(sl.level)
+ && e.service_specific_error() == ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED.0
+ {
+ // GSI replaces the values for remote_prov_prop properties (since they’re
+ // system_internal_prop properties), so on GSI the properties are not
+ // reliable indicators of whether StrongBox/TEE are RKP-only or not.
+ // Test can be skipped if it generates a key with attestation but doesn't provide
+ // an ATTEST_KEY and rkp-only property is undetermined.
+ Ok(None)
+ } else {
+ Err(e)
+ };
+ }
+ };
if gen_params.iter().any(|kp| {
matches!(
@@ -1478,7 +1610,7 @@
}
check_key_authorizations(sl, &key_metadata.authorizations, gen_params, KeyOrigin::GENERATED);
- Ok(key_metadata)
+ Ok(Some(key_metadata))
}
/// Generate a key using given authorizations and create an operation using the generated key.
@@ -1487,8 +1619,10 @@
gen_params: &AuthSetBuilder,
op_params: &AuthSetBuilder,
alias: &str,
-) -> binder::Result<CreateOperationResponse> {
- let key_metadata = generate_key(sl, gen_params, alias)?;
+) -> binder::Result<Option<CreateOperationResponse>> {
+ let Some(key_metadata) = generate_key(sl, gen_params, alias)? else {
+ return Ok(None);
+ };
- sl.binder.createOperation(&key_metadata.key, op_params, false)
+ sl.binder.createOperation(&key_metadata.key, op_params, false).map(Some)
}
diff --git a/keystore2/test_utils/lib.rs b/keystore2/test_utils/lib.rs
index 8b766dd..8e74f92 100644
--- a/keystore2/test_utils/lib.rs
+++ b/keystore2/test_utils/lib.rs
@@ -21,10 +21,10 @@
use android_system_keystore2::aidl::android::system::keystore2::{
IKeystoreService::IKeystoreService,
- IKeystoreSecurityLevel::IKeystoreSecurityLevel,
+ IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- ErrorCode::ErrorCode, SecurityLevel::SecurityLevel,
+ ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel,
};
use android_security_authorization::aidl::android::security::authorization::IKeystoreAuthorization::IKeystoreAuthorization;
@@ -176,4 +176,35 @@
pub fn is_keymaster(&self) -> bool {
!self.is_keymint()
}
+
+ /// Get KeyMint version.
+ /// Returns 0 if the underlying device is Keymaster not KeyMint.
+ pub fn get_keymint_version(&self) -> i32 {
+ let instance = match self.level {
+ SecurityLevel::TRUSTED_ENVIRONMENT => "default",
+ SecurityLevel::STRONGBOX => "strongbox",
+ l => panic!("unexpected level {l:?}"),
+ };
+ let name = format!("android.hardware.security.keymint.IKeyMintDevice/{instance}");
+ if binder::is_declared(&name).expect("Could not check for declared keymint interface") {
+ let km: binder::Strong<dyn IKeyMintDevice> = binder::get_interface(&name).unwrap();
+ km.getInterfaceVersion().unwrap()
+ } else {
+ 0
+ }
+ }
+
+ /// Delete a key.
+ pub fn delete_key(&self, key: &KeyDescriptor) -> binder::Result<()> {
+ match self.binder.deleteKey(key) {
+ Ok(()) => Ok(()),
+ Err(s)
+ if s.exception_code() == binder::ExceptionCode::SERVICE_SPECIFIC
+ && s.service_specific_error() == ErrorCode::UNIMPLEMENTED.0 =>
+ {
+ Ok(())
+ }
+ Err(e) => Err(e),
+ }
+ }
}
diff --git a/keystore2/test_utils/run_as.rs b/keystore2/test_utils/run_as.rs
index d39d069..7a9acb7 100644
--- a/keystore2/test_utils/run_as.rs
+++ b/keystore2/test_utils/run_as.rs
@@ -32,12 +32,104 @@
fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write, ForkResult, Gid,
Pid, Uid,
};
-use serde::{de::DeserializeOwned, Serialize};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::os::fd::AsRawFd;
use std::os::fd::OwnedFd;
+/// Newtype string error, which can be serialized and transferred out from a sub-process.
+#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
+pub struct Error(pub String);
+
+/// Allow ergonomic use of [`anyhow::Error`].
+impl From<anyhow::Error> for Error {
+ fn from(err: anyhow::Error) -> Self {
+ // Use the debug format of [`anyhow::Error`] to include backtrace.
+ Self(format!("{:?}", err))
+ }
+}
+impl From<String> for Error {
+ fn from(val: String) -> Self {
+ Self(val)
+ }
+}
+impl From<&str> for Error {
+ fn from(val: &str) -> Self {
+ Self(val.to_string())
+ }
+}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl std::error::Error for Error {}
+
+/// Equivalent to the [`assert!`] macro which returns an [`Error`] rather than emitting a panic.
+/// This is useful for test code that is `run_as`, so failures are more accessible.
+#[macro_export]
+macro_rules! expect {
+ ($cond:expr $(,)?) => {{
+ let result = $cond;
+ if !result {
+ return Err($crate::run_as::Error(format!(
+ "{}:{}: check '{}' failed",
+ file!(),
+ line!(),
+ stringify!($cond)
+ )));
+ }
+ }};
+ ($cond:expr, $($arg:tt)+) => {{
+ let result = $cond;
+ if !result {
+ return Err($crate::run_as::Error(format!(
+ "{}:{}: check '{}' failed: {}",
+ file!(),
+ line!(),
+ stringify!($cond),
+ format_args!($($arg)+)
+ )));
+ }
+ }};
+}
+
+/// Equivalent to the [`assert_eq!`] macro which returns an [`Error`] rather than emitting a panic.
+/// This is useful for test code that is `run_as`, so failures are more accessible.
+#[macro_export]
+macro_rules! expect_eq {
+ ($left:expr, $right:expr $(,)?) => {{
+ let left = $left;
+ let right = $right;
+ if left != right {
+ return Err($crate::run_as::Error(format!(
+ "{}:{}: assertion {} == {} failed\n left: {left:?}\n right: {right:?}\n",
+ file!(),
+ line!(),
+ stringify!($left),
+ stringify!($right),
+ )));
+ }
+ }};
+ ($left:expr, $right:expr, $($arg:tt)+) => {{
+ let left = $left;
+ let right = $right;
+ if left != right {
+ return Err($crate::run_as::Error(format!(
+ "{}:{}: assertion {} == {} failed: {}\n left: {left:?}\n right: {right:?}\n",
+ file!(),
+ line!(),
+ stringify!($left),
+ stringify!($right),
+ format_args!($($arg)+)
+ )));
+ }
+ }};
+}
+
fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) {
setgid(gid).expect("Failed to set GID. This test might need more privileges.");
setuid(uid).expect("Failed to set UID. This test might need more privileges.");
@@ -119,31 +211,48 @@
/// Receiving blocks until an object of type T has been read from the channel.
/// Panics if an error occurs during io or deserialization.
pub fn recv(&mut self) -> T {
+ match self.recv_err() {
+ Ok(val) => val,
+ Err(e) => panic!("{e}"),
+ }
+ }
+
+ /// Receives a serializable object from the corresponding ChannelWriter.
+ /// Receiving blocks until an object of type T has been read from the channel.
+ pub fn recv_err(&mut self) -> Result<T, Error> {
let mut size_buffer = [0u8; std::mem::size_of::<usize>()];
match self.0.read(&mut size_buffer).expect("In ChannelReader::recv: Failed to read size.") {
r if r != size_buffer.len() => {
- panic!("In ChannelReader::recv: Failed to read size. Insufficient data: {}", r);
+ return Err(format!(
+ "In ChannelReader::recv: Failed to read size. Insufficient data: {}",
+ r
+ )
+ .into());
}
_ => {}
};
let size = usize::from_be_bytes(size_buffer);
let mut data_buffer = vec![0u8; size];
- match self
- .0
- .read(&mut data_buffer)
- .expect("In ChannelReader::recv: Failed to read serialized data.")
- {
- r if r != data_buffer.len() => {
- panic!(
+ match self.0.read(&mut data_buffer) {
+ Ok(r) if r != data_buffer.len() => {
+ return Err(format!(
"In ChannelReader::recv: Failed to read serialized data. Insufficient data: {}",
r
- );
+ )
+ .into());
}
- _ => {}
+ Ok(_) => {}
+ Err(e) => {
+ return Err(format!(
+ "In ChannelReader::recv: Failed to read serialized data: {e:?}"
+ )
+ .into())
+ }
};
- serde_cbor::from_slice(&data_buffer)
- .expect("In ChannelReader::recv: Failed to deserialize data.")
+ serde_cbor::from_slice(&data_buffer).map_err(|e| {
+ format!("In ChannelReader::recv: Failed to deserialize data: {e:?}").into()
+ })
}
}
@@ -186,6 +295,11 @@
/// Get child result. Panics if the child did not exit with status 0 or if a serialization
/// error occurred.
pub fn get_result(mut self) -> R {
+ self.get_death_result()
+ }
+
+ /// Get child result via a mutable reference.
+ fn get_death_result(&mut self) -> R {
let status =
waitpid(self.pid, None).expect("ChildHandle::wait: Failed while waiting for child.");
match status {
@@ -205,6 +319,31 @@
}
}
+impl<R, M> ChildHandle<Result<R, Error>, M>
+where
+ R: Serialize + DeserializeOwned,
+ M: Serialize + DeserializeOwned,
+{
+ /// Receive a response from the child. If the child has closed the response
+ /// channel, assume it has terminated and read the final result.
+ /// Panics on child failure, but will display the child error value.
+ pub fn recv_or_die(&mut self) -> M {
+ match self.response_reader.recv_err() {
+ Ok(v) => v,
+ Err(_e) => {
+ // We have failed to read from the `response_reader` channel.
+ // Assume this is because the child completed early with an error.
+ match self.get_death_result() {
+ Ok(_) => {
+ panic!("Child completed OK despite failure to read a response!")
+ }
+ Err(e) => panic!("Child failed with:\n{e}"),
+ }
+ }
+ }
+ }
+}
+
impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> Drop for ChildHandle<R, M> {
fn drop(&mut self) {
if self.exit_status.is_none() {
@@ -213,6 +352,40 @@
}
}
+/// Run the given closure in a new process running as an untrusted app with the given `uid` and
+/// `gid`. Parent process will run without waiting for child status.
+///
+/// # Safety
+/// run_as_child runs the given closure in the client branch of fork. And it uses non
+/// async signal safe API. This means that calling this function in a multi threaded program
+/// yields undefined behavior in the child. As of this writing, it is safe to call this function
+/// from a Rust device test, because every test itself is spawned as a separate process.
+///
+/// # Safety Binder
+/// It is okay for the closure to use binder services, however, this does not work
+/// if the parent initialized libbinder already. So do not use binder outside of the closure
+/// in your test.
+pub unsafe fn run_as_child_app<F, R, M>(
+ uid: u32,
+ gid: u32,
+ f: F,
+) -> Result<ChildHandle<R, M>, nix::Error>
+where
+ R: Serialize + DeserializeOwned,
+ M: Serialize + DeserializeOwned,
+ F: 'static + Send + FnOnce(&mut ChannelReader<M>, &mut ChannelWriter<M>) -> R,
+{
+ // Safety: Caller guarantees that the process only has a single thread.
+ unsafe {
+ run_as_child(
+ "u:r:untrusted_app:s0:c91,c256,c10,c20",
+ Uid::from_raw(uid),
+ Gid::from_raw(gid),
+ f,
+ )
+ }
+}
+
/// Run the given closure in a new process running with the new identity given as
/// `uid`, `gid`, and `se_context`. Parent process will run without waiting for child status.
///
@@ -244,7 +417,7 @@
let (response_reader, mut response_writer) =
pipe_channel().expect("Failed to create cmd pipe.");
- // SAFETY: Our caller guarantees that the process only has a single thread, so calling
+ // Safety: Our caller guarantees that the process only has a single thread, so calling
// non-async-signal-safe functions in the child is in fact safe.
match unsafe { fork() } {
Ok(ForkResult::Parent { child, .. }) => {
@@ -283,6 +456,50 @@
}
}
+/// Run the given closure in a new process running with the root identity.
+///
+/// # Safety
+/// run_as runs the given closure in the client branch of fork. And it uses non
+/// async signal safe API. This means that calling this function in a multi threaded program
+/// yields undefined behavior in the child. As of this writing, it is safe to call this function
+/// from a Rust device test, because every test itself is spawned as a separate process.
+///
+/// # Safety Binder
+/// It is okay for the closure to use binder services, however, this does not work
+/// if the parent initialized libbinder already. So do not use binder outside of the closure
+/// in your test.
+pub unsafe fn run_as_root<F, R>(f: F) -> R
+where
+ R: Serialize + DeserializeOwned,
+ F: 'static + Send + FnOnce() -> R,
+{
+ // SAFETY: Our caller guarantees that the process only has a single thread.
+ unsafe { run_as("u:r:su:s0", Uid::from_raw(0), Gid::from_raw(0), f) }
+}
+
+/// Run the given closure in a new `untrusted_app` process running with the given `uid` and `gid`.
+///
+/// # Safety
+/// run_as runs the given closure in the client branch of fork. And it uses non
+/// async signal safe API. This means that calling this function in a multi threaded program
+/// yields undefined behavior in the child. As of this writing, it is safe to call this function
+/// from a Rust device test, because every test itself is spawned as a separate process.
+///
+/// # Safety Binder
+/// It is okay for the closure to use binder services, however, this does not work
+/// if the parent initialized libbinder already. So do not use binder outside of the closure
+/// in your test.
+pub unsafe fn run_as_app<F, R>(uid: u32, gid: u32, f: F) -> R
+where
+ R: Serialize + DeserializeOwned,
+ F: 'static + Send + FnOnce() -> R,
+{
+ // SAFETY: Our caller guarantees that the process only has a single thread.
+ unsafe {
+ run_as("u:r:untrusted_app:s0:c91,c256,c10,c20", Uid::from_raw(uid), Gid::from_raw(gid), f)
+ }
+}
+
/// Run the given closure in a new process running with the new identity given as
/// `uid`, `gid`, and `se_context`.
///
@@ -357,9 +574,12 @@
// Safety: run_as must be called from a single threaded process.
// This device test is run as a separate single threaded process.
unsafe {
- run_as(selinux::getcon().unwrap().to_str().unwrap(), getuid(), getgid(), || {
- panic!("Closure must panic.")
- })
+ run_as::<_, ()>(
+ selinux::getcon().unwrap().to_str().unwrap(),
+ getuid(),
+ getgid(),
+ || panic!("Closure must panic."),
+ )
};
}
diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp
index ca9f5e3..8ec5238 100644
--- a/keystore2/tests/Android.bp
+++ b/keystore2/tests/Android.bp
@@ -24,32 +24,45 @@
rust_test {
name: "keystore2_client_tests",
- compile_multilib: "first",
defaults: [
"keymint_use_latest_hal_aidl_rust",
"keystore2_use_latest_aidl_rust",
],
+ static_libs: [
+ // Also include static_libs for the NDK variants so that they are available
+ // for dependencies.
+ "android.system.keystore2-V5-ndk",
+ "android.hardware.security.keymint-V4-ndk",
+ ],
srcs: ["keystore2_client_tests.rs"],
test_suites: [
+ "automotive-sdv-tests",
"general-tests",
"vts",
],
test_config: "AndroidTest.xml",
rustlibs: [
+ "android.hardware.gatekeeper-V1-rust",
"android.hardware.security.secureclock-V1-rust",
"android.security.authorization-rust",
"android.security.maintenance-rust",
"libaconfig_android_hardware_biometrics_rust",
"libandroid_logger",
"libandroid_security_flags_rust",
+ "libanyhow",
"libbinder_rs",
+ "libbssl_crypto",
+ "libkeystore_attestation",
"libkeystore2_test_utils",
+ "libhex",
"liblog_rust",
+ "libkeystore2_flags_rust",
"libnix",
"libopenssl",
"librustutils",
"libserde",
+ "libx509_cert",
"packagemanager_aidl-rust",
],
require_root: true,
diff --git a/keystore2/tests/AndroidTest.xml b/keystore2/tests/AndroidTest.xml
index dde18a9..7db36f7 100644
--- a/keystore2/tests/AndroidTest.xml
+++ b/keystore2/tests/AndroidTest.xml
@@ -14,7 +14,6 @@
limitations under the License.
-->
<configuration description="Config to run keystore2_client_tests device tests.">
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
</target_preparer>
diff --git a/keystore2/tests/keystore2_client_3des_key_tests.rs b/keystore2/tests/keystore2_client_3des_key_tests.rs
index 29f1617..4cb81d1 100644
--- a/keystore2/tests/keystore2_client_3des_key_tests.rs
+++ b/keystore2/tests/keystore2_client_3des_key_tests.rs
@@ -72,6 +72,7 @@
/// Generate 3DES keys with various block modes and paddings.
/// - Block Modes: ECB, CBC
/// - Padding Modes: NONE, PKCS7
+///
/// Test should generate keys and perform operation successfully.
#[test]
fn keystore2_3des_ecb_cbc_generate_key_success() {
diff --git a/keystore2/tests/keystore2_client_aes_key_tests.rs b/keystore2/tests/keystore2_client_aes_key_tests.rs
index 9f85c38..7128911 100644
--- a/keystore2/tests/keystore2_client_aes_key_tests.rs
+++ b/keystore2/tests/keystore2_client_aes_key_tests.rs
@@ -75,6 +75,7 @@
/// Generate AES keys with various block modes and paddings.
/// - Block Modes: ECB, CBC
/// - Padding Modes: NONE, PKCS7
+///
/// Test should generate keys and perform operation successfully.
#[test]
fn keystore2_aes_ecb_cbc_generate_key() {
@@ -106,6 +107,7 @@
/// Generate AES keys with -
/// - Block Modes: `CTR, GCM`
/// - Padding Modes: `NONE`
+///
/// Test should generate keys and perform operation successfully.
#[test]
fn keystore2_aes_ctr_gcm_generate_key_success() {
@@ -133,6 +135,7 @@
/// Generate AES keys with -
/// - Block Modes: `CTR, GCM`
/// - Padding Modes: `PKCS7`
+///
/// Try to create an operation using generated keys, test should fail to create an operation
/// with an error code `INCOMPATIBLE_PADDING_MODE`.
#[test]
@@ -200,7 +203,7 @@
}
/// Try to create an operation using AES key with multiple block modes. Test should fail to create
-/// an operation with `UNSUPPORTED_BLOCK_MODE` error code.
+/// an operation.
#[test]
fn keystore2_aes_key_op_fails_multi_block_modes() {
let sl = SecLevel::tee();
@@ -244,11 +247,16 @@
false,
));
assert!(result.is_err());
- assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_BLOCK_MODE), result.unwrap_err());
+ assert!(matches!(
+ result.unwrap_err(),
+ Error::Km(ErrorCode::INCOMPATIBLE_BLOCK_MODE)
+ | Error::Km(ErrorCode::UNSUPPORTED_BLOCK_MODE)
+ | Error::Km(ErrorCode::INVALID_ARGUMENT)
+ ));
}
/// Try to create an operation using AES key with multiple padding modes. Test should fail to create
-/// an operation with `UNSUPPORTED_PADDING_MODE` error code.
+/// an operation.
#[test]
fn keystore2_aes_key_op_fails_multi_padding_modes() {
let sl = SecLevel::tee();
@@ -292,7 +300,12 @@
false,
));
assert!(result.is_err());
- assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err());
+ assert!(matches!(
+ result.unwrap_err(),
+ Error::Km(ErrorCode::INCOMPATIBLE_PADDING_MODE)
+ | Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE)
+ | Error::Km(ErrorCode::INVALID_ARGUMENT)
+ ));
}
/// Generate a AES-ECB key with unpadded mode. Try to create an operation using generated key
diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs
index b51896a..02dfd3f 100644
--- a/keystore2/tests/keystore2_client_attest_key_tests.rs
+++ b/keystore2/tests/keystore2_client_attest_key_tests.rs
@@ -33,7 +33,7 @@
use keystore2_test_utils::{
authorizations, key_generations, key_generations::Error, run_as, SecLevel,
};
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
use rustutils::users::AID_USER_OFFSET;
/// Generate RSA and EC attestation keys and use them for signing RSA-signing keys.
@@ -47,8 +47,12 @@
for algo in [Algorithm::RSA, Algorithm::EC] {
// Create attestation key.
- let attestation_key_metadata =
- key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap();
+ let Some(attestation_key_metadata) = key_generations::map_ks_error(
+ key_generations::generate_attestation_key(&sl, algo, att_challenge),
+ )
+ .unwrap() else {
+ return;
+ };
let mut cert_chain: Vec<u8> = Vec::new();
cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -57,7 +61,7 @@
// Create RSA signing key and use attestation key to sign it.
let sign_key_alias = format!("ks_attest_rsa_signing_key_{}", getuid());
- let sign_key_metadata = key_generations::generate_rsa_key(
+ let Some(sign_key_metadata) = key_generations::generate_rsa_key(
&sl,
Domain::APP,
-1,
@@ -73,7 +77,9 @@
},
Some(&attestation_key_metadata.key),
)
- .unwrap();
+ .unwrap() else {
+ return;
+ };
let mut cert_chain: Vec<u8> = Vec::new();
cert_chain.extend(sign_key_metadata.certificate.as_ref().unwrap());
@@ -94,8 +100,12 @@
for algo in [Algorithm::RSA, Algorithm::EC] {
// Create attestation key.
- let attestation_key_metadata =
- key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap();
+ let Some(attestation_key_metadata) = key_generations::map_ks_error(
+ key_generations::generate_attestation_key(&sl, algo, att_challenge),
+ )
+ .unwrap() else {
+ return;
+ };
let mut cert_chain: Vec<u8> = Vec::new();
cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -104,7 +114,7 @@
// Create RSA encrypt/decrypt key and use attestation key to sign it.
let decrypt_key_alias = format!("ks_attest_rsa_encrypt_key_{}", getuid());
- let decrypt_key_metadata = key_generations::generate_rsa_key(
+ let Some(decrypt_key_metadata) = key_generations::generate_rsa_key(
&sl,
Domain::APP,
-1,
@@ -120,7 +130,9 @@
},
Some(&attestation_key_metadata.key),
)
- .unwrap();
+ .unwrap() else {
+ return;
+ };
let mut cert_chain: Vec<u8> = Vec::new();
cert_chain.extend(decrypt_key_metadata.certificate.as_ref().unwrap());
@@ -142,8 +154,12 @@
for algo in [Algorithm::RSA, Algorithm::EC] {
// Create attestation key.
- let attestation_key_metadata =
- key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap();
+ let Some(attestation_key_metadata) = key_generations::map_ks_error(
+ key_generations::generate_attestation_key(&sl, algo, att_challenge),
+ )
+ .unwrap() else {
+ return;
+ };
let mut cert_chain: Vec<u8> = Vec::new();
cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -177,16 +193,27 @@
skip_test_if_no_app_attest_key_feature!();
let sl = SecLevel::tee();
+ if sl.get_keymint_version() < 2 {
+ // Curve 25519 was included in version 2 of the KeyMint interface.
+ // For device with KeyMint-V1 or Keymaster in backend, emulated Ed25519 key can't attest
+ // to a "real" RSA key.
+ return;
+ }
+
let att_challenge: &[u8] = b"foo";
// Create EcCurve::CURVE_25519 attestation key.
- let attestation_key_metadata = key_generations::generate_ec_attestation_key(
- &sl,
- att_challenge,
- Digest::NONE,
- EcCurve::CURVE_25519,
- )
- .unwrap();
+ let Some(attestation_key_metadata) =
+ key_generations::map_ks_error(key_generations::generate_ec_attestation_key(
+ &sl,
+ att_challenge,
+ Digest::NONE,
+ EcCurve::CURVE_25519,
+ ))
+ .unwrap()
+ else {
+ return;
+ };
let mut cert_chain: Vec<u8> = Vec::new();
cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -195,7 +222,7 @@
// Create RSA signing key and use attestation key to sign it.
let sign_key_alias = format!("ksrsa_attested_sign_test_key_{}", getuid());
- let sign_key_metadata = key_generations::generate_rsa_key(
+ let Some(sign_key_metadata) = key_generations::generate_rsa_key(
&sl,
Domain::APP,
-1,
@@ -211,7 +238,9 @@
},
Some(&attestation_key_metadata.key),
)
- .unwrap();
+ .unwrap() else {
+ return;
+ };
let mut cert_chain: Vec<u8> = Vec::new();
cert_chain.extend(sign_key_metadata.certificate.as_ref().unwrap());
@@ -226,6 +255,13 @@
fn keystore2_generate_rsa_attest_key_with_multi_purpose_fail() {
skip_test_if_no_app_attest_key_feature!();
let sl = SecLevel::tee();
+ if sl.get_keymint_version() < 2 {
+ // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined
+ // with other key purposes. However, this was not checked at the time
+ // so we can only be strict about checking this for implementations of KeyMint
+ // version 2 and above.
+ return;
+ }
let digest = Digest::SHA_2_256;
let padding = PaddingMode::RSA_PKCS1_1_5_SIGN;
@@ -267,6 +303,13 @@
fn keystore2_ec_attest_key_with_multi_purpose_fail() {
skip_test_if_no_app_attest_key_feature!();
let sl = SecLevel::tee();
+ if sl.get_keymint_version() < 2 {
+ // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined
+ // with other key purposes. However, this was not checked at the time
+ // so we can only be strict about checking this for implementations of KeyMint
+ // version 2 and above.
+ return;
+ }
let attest_key_alias = format!("ks_ec_attest_multipurpose_key_{}", getuid());
@@ -306,8 +349,12 @@
let att_challenge: &[u8] = b"foo";
// Create RSA attestation key.
- let attestation_key_metadata =
- key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap();
+ let Some(attestation_key_metadata) = key_generations::map_ks_error(
+ key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge),
+ )
+ .unwrap() else {
+ return;
+ };
let mut cert_chain: Vec<u8> = Vec::new();
cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -416,55 +463,6 @@
assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
}
-/// Generate RSA attestation key and try to use it as attestation key while generating symmetric
-/// key. Test should generate symmetric key successfully. Verify that generated symmetric key
-/// should not have attestation record or certificate.
-#[test]
-fn keystore2_attest_symmetric_key_fail_sys_error() {
- skip_test_if_no_app_attest_key_feature!();
-
- let sl = SecLevel::tee();
- let att_challenge: &[u8] = b"foo";
-
- // Create attestation key.
- let attestation_key_metadata =
- key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap();
-
- let mut cert_chain: Vec<u8> = Vec::new();
- cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
- cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
- validate_certchain(&cert_chain).expect("Error while validating cert chain.");
-
- // Generate symmetric key with above generated key as attestation key.
- let gen_params = authorizations::AuthSetBuilder::new()
- .no_auth_required()
- .algorithm(Algorithm::AES)
- .purpose(KeyPurpose::ENCRYPT)
- .purpose(KeyPurpose::DECRYPT)
- .key_size(128)
- .padding_mode(PaddingMode::NONE)
- .block_mode(BlockMode::ECB)
- .attestation_challenge(att_challenge.to_vec());
-
- let alias = format!("ks_test_sym_key_attest_{}", getuid());
- let aes_key_metadata = sl
- .binder
- .generateKey(
- &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
- Some(&attestation_key_metadata.key),
- &gen_params,
- 0,
- b"entropy",
- )
- .unwrap();
-
- // Should not have public certificate.
- assert!(aes_key_metadata.certificate.is_none());
-
- // Should not have an attestation record.
- assert!(aes_key_metadata.certificateChain.is_none());
-}
-
fn get_attestation_ids(keystore2: &binder::Strong<dyn IKeystoreService>) -> Vec<(Tag, Vec<u8>)> {
let attest_ids = vec![
(Tag::ATTESTATION_ID_BRAND, "brand"),
@@ -506,9 +504,12 @@
let sl = SecLevel::tee();
let att_challenge: &[u8] = b"foo";
-
- let attest_key_metadata =
- key_generations::generate_attestation_key(&sl, algorithm, att_challenge).unwrap();
+ let Some(attest_key_metadata) = key_generations::map_ks_error(
+ key_generations::generate_attestation_key(&sl, algorithm, att_challenge),
+ )
+ .unwrap() else {
+ return;
+ };
let attest_id_params = get_attestation_ids(&sl.keystore2);
@@ -561,6 +562,8 @@
#[test]
fn keystore2_attest_key_fails_with_invalid_attestation_id() {
skip_test_if_no_device_id_attestation_feature!();
+ skip_device_id_attestation_tests!();
+ skip_test_if_no_app_attest_key_feature!();
let sl = SecLevel::tee();
@@ -568,9 +571,12 @@
let att_challenge: &[u8] = b"foo";
// Create EC-Attestation key.
- let attest_key_metadata =
- key_generations::generate_ec_attestation_key(&sl, att_challenge, digest, EcCurve::P_256)
- .unwrap();
+ let Some(attest_key_metadata) = key_generations::map_ks_error(
+ key_generations::generate_ec_attestation_key(&sl, att_challenge, digest, EcCurve::P_256),
+ )
+ .unwrap() else {
+ return;
+ };
let attest_id_params = vec![
(Tag::ATTESTATION_ID_BRAND, b"invalid-brand".to_vec()),
@@ -609,12 +615,18 @@
// Skip this test on device supporting `DEVICE_ID_ATTESTATION_FEATURE`.
return;
}
+ skip_device_id_attestation_tests!();
+ skip_test_if_no_app_attest_key_feature!();
let sl = SecLevel::tee();
let att_challenge: &[u8] = b"foo";
- let attest_key_metadata =
- key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap();
+ let Some(attest_key_metadata) = key_generations::map_ks_error(
+ key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge),
+ )
+ .unwrap() else {
+ return;
+ };
let attest_id_params = get_attestation_ids(&sl.keystore2);
for (attest_id, value) in attest_id_params {
@@ -643,42 +655,47 @@
/// should return error response code - `GET_ATTESTATION_APPLICATION_ID_FAILED`.
#[test]
fn keystore2_generate_attested_key_fail_to_get_aaid() {
- static APP_USER_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
const USER_ID: u32 = 99;
- const APPLICATION_ID: u32 = 10001;
+ const APPLICATION_ID: u32 = 19901;
static APP_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static APP_GID: u32 = APP_UID;
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(APP_USER_CTX, Uid::from_raw(APP_UID), Gid::from_raw(APP_GID), || {
- skip_test_if_no_app_attest_key_feature!();
- let sl = SecLevel::tee();
- let att_challenge: &[u8] = b"foo";
- let alias = format!("ks_attest_rsa_encrypt_key_aaid_fail{}", getuid());
+ let gen_key_fn = || {
+ skip_test_if_no_app_attest_key_feature!();
+ let sl = SecLevel::tee();
+ if sl.keystore2.getInterfaceVersion().unwrap() < 4 {
+ // `GET_ATTESTATION_APPLICATION_ID_FAILED` is supported on devices with
+ // `IKeystoreService` version >= 4.
+ return;
+ }
+ let att_challenge: &[u8] = b"foo";
+ let alias = format!("ks_attest_rsa_encrypt_key_aaid_fail{}", getuid());
- let result = key_generations::map_ks_error(key_generations::generate_rsa_key(
- &sl,
- Domain::APP,
- -1,
- Some(alias),
- &key_generations::KeyParams {
- key_size: 2048,
- purpose: vec![KeyPurpose::ATTEST_KEY],
- padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
- digest: Some(Digest::SHA_2_256),
- mgf_digest: None,
- block_mode: None,
- att_challenge: Some(att_challenge.to_vec()),
- },
- None,
- ));
+ let result = key_generations::map_ks_error(key_generations::generate_rsa_key(
+ &sl,
+ Domain::APP,
+ -1,
+ Some(alias),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ATTEST_KEY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: Some(att_challenge.to_vec()),
+ },
+ None,
+ ));
- assert!(result.is_err());
- assert_eq!(
- result.unwrap_err(),
- Error::Rc(ResponseCode::GET_ATTESTATION_APPLICATION_ID_FAILED)
- );
- })
+ assert!(result.is_err());
+ assert_eq!(
+ result.unwrap_err(),
+ Error::Rc(ResponseCode::GET_ATTESTATION_APPLICATION_ID_FAILED)
+ );
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(APP_UID, APP_GID, gen_key_fn) };
}
diff --git a/keystore2/tests/keystore2_client_authorizations_tests.rs b/keystore2/tests/keystore2_client_authorizations_tests.rs
index 0981783..6d105cc 100644
--- a/keystore2/tests/keystore2_client_authorizations_tests.rs
+++ b/keystore2/tests/keystore2_client_authorizations_tests.rs
@@ -13,13 +13,12 @@
// limitations under the License.
use crate::keystore2_client_test_utils::{
- app_attest_key_feature_exists, delete_app_key, perform_sample_asym_sign_verify_op,
- perform_sample_hmac_sign_verify_op, perform_sample_sym_key_decrypt_op,
- perform_sample_sym_key_encrypt_op, verify_certificate_serial_num,
- verify_certificate_subject_name, SAMPLE_PLAIN_TEXT,
+ app_attest_key_feature_exists, delete_app_key, get_vsr_api_level,
+ perform_sample_asym_sign_verify_op, perform_sample_hmac_sign_verify_op,
+ perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op,
+ verify_certificate_serial_num, verify_certificate_subject_name, SAMPLE_PLAIN_TEXT,
};
use crate::{require_keymint, skip_test_if_no_app_attest_key_feature};
-use aconfig_android_hardware_biometrics_rust;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
@@ -27,6 +26,7 @@
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
+ KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue,
};
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
Timestamp::Timestamp
@@ -35,6 +35,8 @@
Domain::Domain, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata,
ResponseCode::ResponseCode,
};
+use bssl_crypto::digest;
+use keystore_attestation::{AttestationExtension, ATTESTATION_EXTENSION_OID};
use keystore2_test_utils::ffi_test_utils::get_value_from_attest_record;
use keystore2_test_utils::{
authorizations, get_keystore_auth_service, key_generations,
@@ -43,8 +45,9 @@
use openssl::bn::{BigNum, MsbOption};
use openssl::x509::X509NameBuilder;
use std::time::SystemTime;
+use x509_cert::{certificate::Certificate, der::Decode};
-fn gen_key_including_unique_id(sl: &SecLevel, alias: &str) -> Vec<u8> {
+fn gen_key_including_unique_id(sl: &SecLevel, alias: &str) -> Option<Vec<u8>> {
let gen_params = authorizations::AuthSetBuilder::new()
.no_auth_required()
.algorithm(Algorithm::EC)
@@ -55,7 +58,9 @@
.attestation_challenge(b"foo".to_vec())
.include_unique_id();
- let key_metadata = key_generations::generate_key(sl, &gen_params, alias).unwrap();
+ let key_metadata =
+ key_generations::map_ks_error(key_generations::generate_key(sl, &gen_params, alias))
+ .unwrap()?;
let unique_id = get_value_from_attest_record(
key_metadata.certificate.as_ref().unwrap(),
@@ -64,7 +69,7 @@
)
.expect("Unique id not found.");
assert!(!unique_id.is_empty());
- unique_id
+ Some(unique_id)
}
fn generate_key_and_perform_sign_verify_op_max_times(
@@ -72,8 +77,10 @@
gen_params: &authorizations::AuthSetBuilder,
alias: &str,
max_usage_count: i32,
-) -> binder::Result<KeyMetadata> {
- let key_metadata = key_generations::generate_key(sl, gen_params, alias)?;
+) -> binder::Result<Option<KeyMetadata>> {
+ let Some(key_metadata) = key_generations::generate_key(sl, gen_params, alias)? else {
+ return Ok(None);
+ };
// Use above generated key `max_usage_count` times.
for _ in 0..max_usage_count {
@@ -85,7 +92,7 @@
);
}
- Ok(key_metadata)
+ Ok(Some(key_metadata))
}
/// Generate a key with `USAGE_COUNT_LIMIT` and verify the key characteristics. Test should be able
@@ -100,9 +107,12 @@
check_attestation: bool,
) {
// Generate a key and use the key for `max_usage_count` times.
- let key_metadata =
+ let Some(key_metadata) =
generate_key_and_perform_sign_verify_op_max_times(sl, gen_params, alias, max_usage_count)
- .unwrap();
+ .unwrap()
+ else {
+ return;
+ };
let auth = key_generations::get_key_auth(&key_metadata.authorizations, Tag::USAGE_COUNT_LIMIT)
.unwrap();
@@ -158,7 +168,6 @@
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
.ec_curve(EcCurve::P_256)
- .attestation_challenge(b"foo".to_vec())
.active_date_time(active_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_test";
@@ -189,7 +198,6 @@
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
.ec_curve(EcCurve::P_256)
- .attestation_challenge(b"foo".to_vec())
.active_date_time(future_active_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_test";
@@ -220,7 +228,6 @@
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
.ec_curve(EcCurve::P_256)
- .attestation_challenge(b"foo".to_vec())
.origination_expire_date_time(origination_expire_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_test";
@@ -251,7 +258,6 @@
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
.ec_curve(EcCurve::P_256)
- .attestation_challenge(b"foo".to_vec())
.origination_expire_date_time(origination_expire_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_test";
@@ -286,7 +292,9 @@
.usage_expire_date_time(usage_expire_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_hmac_verify_success";
- let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+ let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+ return;
+ };
perform_sample_hmac_sign_verify_op(&sl.binder, &key_metadata.key);
delete_app_key(&sl.keystore2, alias).unwrap();
@@ -313,7 +321,9 @@
.usage_expire_date_time(usage_expire_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_hamc_verify_fail";
- let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+ let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+ return;
+ };
let result = key_generations::map_ks_error(
sl.binder.createOperation(
@@ -349,7 +359,9 @@
.usage_expire_date_time(usage_expire_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_test";
- let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+ let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+ return;
+ };
let cipher_text = perform_sample_sym_key_encrypt_op(
&sl.binder,
PaddingMode::PKCS7,
@@ -398,7 +410,9 @@
.usage_expire_date_time(usage_expire_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_test";
- let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+ let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+ return;
+ };
let cipher_text = perform_sample_sym_key_encrypt_op(
&sl.binder,
PaddingMode::PKCS7,
@@ -440,7 +454,6 @@
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
.ec_curve(EcCurve::P_256)
- .attestation_challenge(b"foo".to_vec())
.early_boot_only();
let alias = "ks_test_auth_tags_test";
@@ -462,6 +475,13 @@
#[test]
fn keystore2_gen_key_auth_max_uses_per_boot() {
let sl = SecLevel::tee();
+ if sl.is_keymaster() {
+ // Older devices with Keymaster implementation may use the key during generateKey to export
+ // the generated public key (EC Key), leading to an unnecessary increment of the
+ // key-associated counter. This can cause the test to fail, so skipping this test on older
+ // devices to avoid test failure.
+ return;
+ }
const MAX_USES_COUNT: i32 = 3;
let gen_params = authorizations::AuthSetBuilder::new()
@@ -471,14 +491,16 @@
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
.ec_curve(EcCurve::P_256)
- .attestation_challenge(b"foo".to_vec())
.max_uses_per_boot(MAX_USES_COUNT);
let alias = "ks_test_auth_tags_test";
// Generate a key and use the key for `MAX_USES_COUNT` times.
- let key_metadata =
+ let Some(key_metadata) =
generate_key_and_perform_sign_verify_op_max_times(&sl, &gen_params, alias, MAX_USES_COUNT)
- .unwrap();
+ .unwrap()
+ else {
+ return;
+ };
// Try to use the key one more time.
let result = key_generations::map_ks_error(sl.binder.createOperation(
@@ -499,6 +521,10 @@
#[test]
fn keystore2_gen_key_auth_usage_count_limit() {
let sl = SecLevel::tee();
+ if sl.is_keymaster() {
+ // `USAGE_COUNT_LIMIT` is supported from KeyMint1.0
+ return;
+ }
const MAX_USES_COUNT: i32 = 3;
let gen_params = authorizations::AuthSetBuilder::new()
@@ -523,6 +549,10 @@
#[test]
fn keystore2_gen_key_auth_usage_count_limit_one() {
let sl = SecLevel::tee();
+ if sl.is_keymaster() {
+ // `USAGE_COUNT_LIMIT` is supported from KeyMint1.0
+ return;
+ }
const MAX_USES_COUNT: i32 = 1;
let gen_params = authorizations::AuthSetBuilder::new()
@@ -546,6 +576,10 @@
#[test]
fn keystore2_gen_non_attested_key_auth_usage_count_limit() {
let sl = SecLevel::tee();
+ if sl.is_keymaster() {
+ // `USAGE_COUNT_LIMIT` is supported from KeyMint1.0
+ return;
+ }
const MAX_USES_COUNT: i32 = 2;
let gen_params = authorizations::AuthSetBuilder::new()
@@ -583,7 +617,6 @@
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
.ec_curve(EcCurve::P_256)
- .attestation_challenge(b"foo".to_vec())
.creation_date_time(creation_datetime.try_into().unwrap());
let alias = "ks_test_auth_tags_test";
@@ -609,92 +642,44 @@
#[test]
fn keystore2_gen_key_auth_include_unique_id_success() {
let sl = SecLevel::tee();
+ if sl.is_keymaster() {
+ // b/387208956 - Some older devices with Keymaster implementations fail to generate an
+ // attestation key with `INCLUDE_UNIQUE_ID`, but this was not previously tested. Skip this
+ // test on devices with Keymaster implementation.
+ return;
+ }
let alias_first = "ks_test_auth_tags_test_1";
- let unique_id_first = gen_key_including_unique_id(&sl, alias_first);
+ if let Some(unique_id_first) = gen_key_including_unique_id(&sl, alias_first) {
+ let alias_second = "ks_test_auth_tags_test_2";
+ let unique_id_second = gen_key_including_unique_id(&sl, alias_second).unwrap();
- let alias_second = "ks_test_auth_tags_test_2";
- let unique_id_second = gen_key_including_unique_id(&sl, alias_second);
+ assert_eq!(unique_id_first, unique_id_second);
- assert_eq!(unique_id_first, unique_id_second);
-
- delete_app_key(&sl.keystore2, alias_first).unwrap();
- delete_app_key(&sl.keystore2, alias_second).unwrap();
+ delete_app_key(&sl.keystore2, alias_first).unwrap();
+ delete_app_key(&sl.keystore2, alias_second).unwrap();
+ }
}
-/// Generate a key with `APPLICATION_DATA`. Test should create an operation using the
-/// same `APPLICATION_DATA` successfully.
+/// Generate a key with `APPLICATION_DATA` and `APPLICATION_ID`. Test should create an operation
+/// successfully using the same `APPLICATION_DATA` and `APPLICATION_ID`.
#[test]
-fn keystore2_gen_key_auth_app_data_test_success() {
+fn keystore2_gen_key_auth_app_data_app_id_test_success() {
let sl = SecLevel::tee();
+ if sl.is_keymaster() && get_vsr_api_level() < 35 {
+ // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+ // test to run on older devices.
+ return;
+ }
let gen_params = authorizations::AuthSetBuilder::new()
.no_auth_required()
.algorithm(Algorithm::EC)
.purpose(KeyPurpose::SIGN)
.purpose(KeyPurpose::VERIFY)
- .digest(Digest::SHA_2_256)
+ .digest(Digest::NONE)
.ec_curve(EcCurve::P_256)
- .app_data(b"app-data".to_vec());
-
- let alias = "ks_test_auth_tags_test";
- let result = key_generations::create_key_and_operation(
- &sl,
- &gen_params,
- &authorizations::AuthSetBuilder::new()
- .purpose(KeyPurpose::SIGN)
- .digest(Digest::SHA_2_256)
- .app_data(b"app-data".to_vec()),
- alias,
- );
- assert!(result.is_ok());
- delete_app_key(&sl.keystore2, alias).unwrap();
-}
-
-/// Generate a key with `APPLICATION_DATA`. Try to create an operation using the
-/// different `APPLICATION_DATA`, test should fail to create an operation with error code
-/// `INVALID_KEY_BLOB`.
-#[test]
-fn keystore2_gen_key_auth_app_data_test_fail() {
- let sl = SecLevel::tee();
-
- let gen_params = authorizations::AuthSetBuilder::new()
- .no_auth_required()
- .algorithm(Algorithm::EC)
- .purpose(KeyPurpose::SIGN)
- .purpose(KeyPurpose::VERIFY)
- .digest(Digest::SHA_2_256)
- .ec_curve(EcCurve::P_256)
- .app_data(b"app-data".to_vec());
-
- let alias = "ks_test_auth_tags_test";
- let result = key_generations::map_ks_error(key_generations::create_key_and_operation(
- &sl,
- &gen_params,
- &authorizations::AuthSetBuilder::new()
- .purpose(KeyPurpose::SIGN)
- .digest(Digest::SHA_2_256)
- .app_data(b"invalid-app-data".to_vec()),
- alias,
- ));
- assert!(result.is_err());
- assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err());
- delete_app_key(&sl.keystore2, alias).unwrap();
-}
-
-/// Generate a key with `APPLICATION_ID`. Test should create an operation using the
-/// same `APPLICATION_ID` successfully.
-#[test]
-fn keystore2_gen_key_auth_app_id_test_success() {
- let sl = SecLevel::tee();
-
- let gen_params = authorizations::AuthSetBuilder::new()
- .no_auth_required()
- .algorithm(Algorithm::EC)
- .purpose(KeyPurpose::SIGN)
- .purpose(KeyPurpose::VERIFY)
- .digest(Digest::SHA_2_256)
- .ec_curve(EcCurve::P_256)
+ .app_data(b"app-data".to_vec())
.app_id(b"app-id".to_vec());
let alias = "ks_test_auth_tags_test";
@@ -703,7 +688,8 @@
&gen_params,
&authorizations::AuthSetBuilder::new()
.purpose(KeyPurpose::SIGN)
- .digest(Digest::SHA_2_256)
+ .digest(Digest::NONE)
+ .app_data(b"app-data".to_vec())
.app_id(b"app-id".to_vec()),
alias,
);
@@ -711,20 +697,25 @@
delete_app_key(&sl.keystore2, alias).unwrap();
}
-/// Generate a key with `APPLICATION_ID`. Try to create an operation using the
-/// different `APPLICATION_ID`, test should fail to create an operation with error code
-/// `INVALID_KEY_BLOB`.
+/// Generate a key with `APPLICATION_DATA` and `APPLICATION_ID`. Try to create an operation using
+/// the different `APPLICATION_DATA` and `APPLICATION_ID`, test should fail to create an operation.
#[test]
-fn keystore2_gen_key_auth_app_id_test_fail() {
+fn keystore2_op_auth_invalid_app_data_app_id_test_fail() {
let sl = SecLevel::tee();
+ if sl.is_keymaster() && get_vsr_api_level() < 35 {
+ // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+ // test to run on older devices.
+ return;
+ }
let gen_params = authorizations::AuthSetBuilder::new()
.no_auth_required()
.algorithm(Algorithm::EC)
.purpose(KeyPurpose::SIGN)
.purpose(KeyPurpose::VERIFY)
- .digest(Digest::SHA_2_256)
+ .digest(Digest::NONE)
.ec_curve(EcCurve::P_256)
+ .app_data(b"app-data".to_vec())
.app_id(b"app-id".to_vec());
let alias = "ks_test_auth_tags_test";
@@ -733,7 +724,8 @@
&gen_params,
&authorizations::AuthSetBuilder::new()
.purpose(KeyPurpose::SIGN)
- .digest(Digest::SHA_2_256)
+ .digest(Digest::NONE)
+ .app_data(b"invalid-app-data".to_vec())
.app_id(b"invalid-app-id".to_vec()),
alias,
));
@@ -742,6 +734,79 @@
delete_app_key(&sl.keystore2, alias).unwrap();
}
+/// Generate a key with `APPLICATION_DATA` and `APPLICATION_ID`. Try to create an operation using
+/// only `APPLICATION_ID`, test should fail to create an operation.
+#[test]
+fn keystore2_op_auth_missing_app_data_test_fail() {
+ let sl = SecLevel::tee();
+ if sl.is_keymaster() && get_vsr_api_level() < 35 {
+ // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+ // test to run on older devices.
+ return;
+ }
+
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::NONE)
+ .ec_curve(EcCurve::P_256)
+ .app_id(b"app-id".to_vec())
+ .app_data(b"app-data".to_vec());
+
+ let alias = "ks_test_auth_tags_test";
+ let result = key_generations::map_ks_error(key_generations::create_key_and_operation(
+ &sl,
+ &gen_params,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::NONE)
+ .app_id(b"app-id".to_vec()),
+ alias,
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err());
+ delete_app_key(&sl.keystore2, alias).unwrap();
+}
+
+/// Generate a key with `APPLICATION_DATA` and `APPLICATION_ID`. Try to create an operation using
+/// only `APPLICATION_DATA`, test should fail to create an operation.
+#[test]
+fn keystore2_op_auth_missing_app_id_test_fail() {
+ let sl = SecLevel::tee();
+ if sl.is_keymaster() && get_vsr_api_level() < 35 {
+ // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+ // test to run on older devices.
+ return;
+ }
+
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::NONE)
+ .ec_curve(EcCurve::P_256)
+ .app_data(b"app-data".to_vec())
+ .app_id(b"app-id".to_vec());
+
+ let alias = "ks_test_auth_tags_test";
+ let result = key_generations::map_ks_error(key_generations::create_key_and_operation(
+ &sl,
+ &gen_params,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::NONE)
+ .app_data(b"app-data".to_vec()),
+ alias,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err());
+ delete_app_key(&sl.keystore2, alias).unwrap();
+}
+
/// Generate an attestation-key without specifying `APPLICATION_ID` and `APPLICATION_DATA`.
/// Test should be able to generate a new key with specifying app-id and app-data using previously
/// generated attestation-key.
@@ -749,6 +814,11 @@
fn keystore2_gen_attested_key_auth_app_id_app_data_test_success() {
skip_test_if_no_app_attest_key_feature!();
let sl = SecLevel::tee();
+ if sl.is_keymaster() && get_vsr_api_level() < 35 {
+ // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+ // test to run on older devices.
+ return;
+ }
// Generate attestation key.
let attest_gen_params = authorizations::AuthSetBuilder::new()
@@ -759,8 +829,11 @@
.ec_curve(EcCurve::P_256)
.attestation_challenge(b"foo".to_vec());
let attest_alias = "ks_test_auth_tags_attest_key";
- let attest_key_metadata =
- key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap();
+ let Some(attest_key_metadata) =
+ key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap()
+ else {
+ return;
+ };
// Generate attested key.
let alias = "ks_test_auth_tags_attested_key";
@@ -795,14 +868,18 @@
/// Generate an attestation-key with specifying `APPLICATION_ID` and `APPLICATION_DATA`.
/// Test should try to generate an attested key using previously generated attestation-key without
-/// specifying app-id and app-data. Test should fail to generate a new key with error code
-/// `INVALID_KEY_BLOB`.
+/// specifying app-id and app-data. Test should fail to generate a new key.
/// It is an oversight of the Keystore API that `APPLICATION_ID` and `APPLICATION_DATA` tags cannot
/// be provided to generateKey for an attestation key that was generated with them.
#[test]
fn keystore2_gen_attestation_key_with_auth_app_id_app_data_test_fail() {
skip_test_if_no_app_attest_key_feature!();
let sl = SecLevel::tee();
+ if sl.is_keymaster() && get_vsr_api_level() < 35 {
+ // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+ // test to run on older devices.
+ return;
+ }
// Generate attestation key.
let attest_gen_params = authorizations::AuthSetBuilder::new()
@@ -815,8 +892,11 @@
.app_id(b"app-id".to_vec())
.app_data(b"app-data".to_vec());
let attest_alias = "ks_test_auth_tags_attest_key";
- let attest_key_metadata =
- key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap();
+ let Some(attest_key_metadata) =
+ key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap()
+ else {
+ return;
+ };
// Generate new key using above generated attestation key without providing app-id and app-data.
let alias = "ks_test_auth_tags_attested_key";
@@ -862,25 +942,7 @@
}
#[test]
-fn keystore2_flagged_off_get_last_auth_password_permission_denied() {
- if aconfig_android_hardware_biometrics_rust::last_authentication_time() {
- return;
- }
-
- let keystore_auth = get_keystore_auth_service();
-
- let result = keystore_auth.getLastAuthTime(0, &[HardwareAuthenticatorType::PASSWORD]);
-
- assert!(result.is_err());
- assert_eq!(result.unwrap_err().service_specific_error(), ResponseCode::PERMISSION_DENIED.0);
-}
-
-#[test]
-fn keystore2_flagged_on_get_last_auth_password_success() {
- if !aconfig_android_hardware_biometrics_rust::last_authentication_time() {
- return;
- }
-
+fn keystore2_get_last_auth_password_success() {
let keystore_auth = get_keystore_auth_service();
add_hardware_token(HardwareAuthenticatorType::PASSWORD);
@@ -888,11 +950,7 @@
}
#[test]
-fn keystore2_flagged_on_get_last_auth_fingerprint_success() {
- if !aconfig_android_hardware_biometrics_rust::last_authentication_time() {
- return;
- }
-
+fn keystore2_get_last_auth_fingerprint_success() {
let keystore_auth = get_keystore_auth_service();
add_hardware_token(HardwareAuthenticatorType::FINGERPRINT);
@@ -923,12 +981,13 @@
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
.ec_curve(EcCurve::P_256)
- .attestation_challenge(b"foo".to_vec())
.cert_subject_name(x509_name)
.cert_serial(serial.to_vec());
let alias = "ks_test_auth_tags_test";
- let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+ let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+ return;
+ };
verify_certificate_subject_name(
key_metadata.certificate.as_ref().unwrap(),
cert_subject.as_bytes(),
@@ -936,3 +995,71 @@
verify_certificate_serial_num(key_metadata.certificate.as_ref().unwrap(), &serial);
delete_app_key(&sl.keystore2, alias).unwrap();
}
+
+#[test]
+fn test_supplementary_attestation_info() {
+ if !keystore2_flags::attest_modules() {
+ // Module info is only populated if the flag is set.
+ return;
+ }
+
+ // Test should not run before MODULE_HASH supplementary info is populated.
+ assert!(rustutils::system_properties::read_bool("keystore.module_hash.sent", false)
+ .unwrap_or(false));
+
+ let sl = SecLevel::tee();
+
+ // Retrieve the input value that gets hashed into the attestation.
+ let module_info = sl
+ .keystore2
+ .getSupplementaryAttestationInfo(Tag::MODULE_HASH)
+ .expect("supplementary info for MODULE_HASH should be populated during startup");
+ let again = sl.keystore2.getSupplementaryAttestationInfo(Tag::MODULE_HASH).unwrap();
+ assert_eq!(again, module_info);
+ let want_hash = digest::Sha256::hash(&module_info).to_vec();
+
+ // Requesting other types of information should fail.
+ let result = key_generations::map_ks_error(
+ sl.keystore2.getSupplementaryAttestationInfo(Tag::BLOCK_MODE),
+ );
+ assert!(result.is_err());
+ assert_eq!(result.unwrap_err(), Error::Rc(ResponseCode::INVALID_ARGUMENT));
+
+ if sl.get_keymint_version() < 400 {
+ // Module hash will only be populated in KeyMint if the underlying device is KeyMint V4+.
+ return;
+ }
+
+ // Generate an attestation.
+ let alias = "ks_module_info_test";
+ let params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256)
+ .attestation_challenge(b"froop".to_vec());
+ let metadata = key_generations::generate_key(&sl, ¶ms, alias)
+ .expect("failed key generation")
+ .expect("no metadata");
+ let cert_data = metadata.certificate.as_ref().unwrap();
+ let cert = Certificate::from_der(cert_data).expect("failed to parse X509 cert");
+ let exts = cert.tbs_certificate.extensions.expect("no X.509 extensions");
+ let ext = exts
+ .iter()
+ .find(|ext| ext.extn_id == ATTESTATION_EXTENSION_OID)
+ .expect("no attestation extension");
+ let ext = AttestationExtension::from_der(ext.extn_value.as_bytes())
+ .expect("failed to parse attestation extension");
+
+ // Find the attested module hash value.
+ let mut got_hash = None;
+ for auth in ext.sw_enforced.auths.into_owned().iter() {
+ if let KeyParameter { tag: Tag::MODULE_HASH, value: KeyParameterValue::Blob(hash) } = auth {
+ got_hash = Some(hash.clone());
+ }
+ }
+ let got_hash = got_hash.expect("no MODULE_HASH in sw_enforced");
+ assert_eq!(hex::encode(got_hash), hex::encode(want_hash));
+}
diff --git a/keystore2/tests/keystore2_client_delete_key_tests.rs b/keystore2/tests/keystore2_client_delete_key_tests.rs
index a0fb9c2..13f8eef 100644
--- a/keystore2/tests/keystore2_client_delete_key_tests.rs
+++ b/keystore2/tests/keystore2_client_delete_key_tests.rs
@@ -100,7 +100,7 @@
)
.unwrap();
- let result = sl.binder.deleteKey(&key_metadata.key);
+ let result = sl.delete_key(&key_metadata.key);
assert!(result.is_ok());
}
@@ -110,7 +110,7 @@
fn keystore2_delete_key_fails_with_missing_key_blob() {
let sl = SecLevel::tee();
- let result = key_generations::map_ks_error(sl.binder.deleteKey(&KeyDescriptor {
+ let result = key_generations::map_ks_error(sl.delete_key(&KeyDescriptor {
domain: Domain::BLOB,
nspace: key_generations::SELINUX_SHELL_NAMESPACE,
alias: None,
@@ -132,7 +132,7 @@
key_generations::generate_ec_p256_signing_key(&sl, Domain::APP, -1, Some(alias), None)
.unwrap();
- let result = key_generations::map_ks_error(sl.binder.deleteKey(&key_metadata.key));
+ let result = key_generations::map_ks_error(sl.delete_key(&key_metadata.key));
assert!(result.is_err());
assert_eq!(Error::Km(ErrorCode::INVALID_ARGUMENT), result.unwrap_err());
}
diff --git a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
index cc5d85c..91370c7 100644
--- a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
+++ b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
@@ -130,7 +130,7 @@
let alias = "ks_test_device_unique_attest_id_test";
match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias))
{
- Ok(key_metadata) => {
+ Ok(Some(key_metadata)) => {
let attest_id_value = get_value_from_attest_record(
key_metadata.certificate.as_ref().unwrap(),
attest_id,
@@ -140,6 +140,7 @@
assert_eq!(attest_id_value, value);
delete_app_key(&sl.keystore2, alias).unwrap();
}
+ Ok(None) => {}
Err(e) => {
assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
}
@@ -196,7 +197,7 @@
let alias = "ks_device_unique_ec_key_attest_test";
match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)) {
- Ok(key_metadata) => {
+ Ok(Some(key_metadata)) => {
perform_sample_asym_sign_verify_op(
&sl.binder,
&key_metadata,
@@ -205,6 +206,7 @@
);
delete_app_key(&sl.keystore2, alias).unwrap();
}
+ Ok(None) => {}
Err(e) => {
assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
}
@@ -235,7 +237,7 @@
let alias = "ks_device_unique_rsa_key_attest_test";
match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)) {
- Ok(key_metadata) => {
+ Ok(Some(key_metadata)) => {
perform_sample_asym_sign_verify_op(
&sl.binder,
&key_metadata,
@@ -244,6 +246,7 @@
);
delete_app_key(&sl.keystore2, alias).unwrap();
}
+ Ok(None) => {}
Err(e) => {
assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
}
@@ -285,7 +288,7 @@
let result =
key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias));
assert!(result.is_err());
- assert!(matches!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS)));
+ assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
}
}
diff --git a/keystore2/tests/keystore2_client_ec_key_tests.rs b/keystore2/tests/keystore2_client_ec_key_tests.rs
index 8aa9bc4..17a88e7 100644
--- a/keystore2/tests/keystore2_client_ec_key_tests.rs
+++ b/keystore2/tests/keystore2_client_ec_key_tests.rs
@@ -211,7 +211,7 @@
assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
// Delete the generated key blob.
- sl.binder.deleteKey(&key_metadata.key).unwrap();
+ sl.delete_key(&key_metadata.key).unwrap();
}
/// Try to generate a key with invalid Domain. `INVALID_ARGUMENT` error response is expected.
@@ -425,7 +425,9 @@
// Client#1: Generate a key and create an operation using generated key.
// Wait until the parent notifies to continue. Once the parent notifies, this operation
// is expected to be completed successfully.
- // SAFETY: The test is run in a separate process with no other threads.
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
let mut child_handle = unsafe {
execute_op_run_as_child(
TARGET_CTX,
@@ -446,20 +448,23 @@
const APPLICATION_ID_2: u32 = 10602;
let uid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2;
let gid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2;
- // SAFETY: The test is run in a separate process with no other threads.
+
+ let get_key_fn = move || {
+ let keystore2_inst = get_keystore_service();
+ let result = key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ }));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
unsafe {
- run_as::run_as(TARGET_CTX, Uid::from_raw(uid2), Gid::from_raw(gid2), move || {
- let keystore2_inst = get_keystore_service();
- let result =
- key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(alias.to_string()),
- blob: None,
- }));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
- });
+ run_as::run_as_app(uid2, gid2, get_key_fn);
};
// Notify the child process (client#1) to resume and finish.
@@ -509,5 +514,5 @@
);
// Delete the generated key blob.
- sl.binder.deleteKey(&key_metadata.key).unwrap();
+ sl.delete_key(&key_metadata.key).unwrap();
}
diff --git a/keystore2/tests/keystore2_client_grant_key_tests.rs b/keystore2/tests/keystore2_client_grant_key_tests.rs
index 50b87b9..c171ab1 100644
--- a/keystore2/tests/keystore2_client_grant_key_tests.rs
+++ b/keystore2/tests/keystore2_client_grant_key_tests.rs
@@ -19,20 +19,35 @@
Digest::Digest, KeyPurpose::KeyPurpose,
};
use android_system_keystore2::aidl::android::system::keystore2::{
- Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission,
- ResponseCode::ResponseCode,
+ Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor,
+ KeyEntryResponse::KeyEntryResponse, KeyPermission::KeyPermission, ResponseCode::ResponseCode,
};
use keystore2_test_utils::{
- authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, SecLevel,
+ authorizations, get_keystore_service, key_generations,
+ key_generations::{map_ks_error, Error},
+ run_as, SecLevel,
};
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
use rustutils::users::AID_USER_OFFSET;
-/// Generate an EC signing key and grant it to the user with given access vector.
-fn generate_ec_key_and_grant_to_user(
- grantee_uid: i32,
+/// Produce a [`KeyDescriptor`] for a granted key.
+fn granted_key_descriptor(nspace: i64) -> KeyDescriptor {
+ KeyDescriptor { domain: Domain::GRANT, nspace, alias: None, blob: None }
+}
+
+fn get_granted_key(
+ ks2: &binder::Strong<dyn IKeystoreService>,
+ nspace: i64,
+) -> Result<KeyEntryResponse, Error> {
+ map_ks_error(ks2.getKeyEntry(&granted_key_descriptor(nspace)))
+}
+
+/// Generate an EC signing key in the SELINUX domain and grant it to the user with given access
+/// vector.
+fn generate_and_grant_selinux_key(
+ grantee_uid: u32,
access_vector: i32,
-) -> binder::Result<KeyDescriptor> {
+) -> Result<KeyDescriptor, Error> {
let sl = SecLevel::tee();
let alias = format!("{}{}", "ks_grant_test_key_1", getuid());
@@ -45,204 +60,287 @@
)
.unwrap();
- sl.keystore2.grant(&key_metadata.key, grantee_uid, access_vector)
+ map_ks_error(sl.keystore2.grant(
+ &key_metadata.key,
+ grantee_uid.try_into().unwrap(),
+ access_vector,
+ ))
}
-fn load_grant_key_and_perform_sign_operation(
- sl: &SecLevel,
- grant_key_nspace: i64,
-) -> Result<(), binder::Status> {
- let key_entry_response = sl.keystore2.getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- })?;
+/// Use a granted key to perform a signing operation.
+fn sign_with_granted_key(grant_key_nspace: i64) -> Result<(), Error> {
+ let sl = SecLevel::tee();
+ let key_entry_response = get_granted_key(&sl.keystore2, grant_key_nspace)?;
// Perform sample crypto operation using granted key.
- let op_response = sl.binder.createOperation(
+ let op_response = map_ks_error(sl.binder.createOperation(
&key_entry_response.metadata.key,
&authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256),
false,
- )?;
+ ))?;
assert!(op_response.iOperation.is_some());
assert_eq!(
Ok(()),
- key_generations::map_ks_error(perform_sample_sign_operation(
- &op_response.iOperation.unwrap()
- ))
+ map_ks_error(perform_sample_sign_operation(&op_response.iOperation.unwrap()))
);
Ok(())
}
-/// Try to grant a key with permission that does not map to any of the `KeyPermission` values.
-/// An error is expected with values that does not map to set of permissions listed in
+/// Try to grant an SELINUX key with permission that does not map to any of the `KeyPermission`
+/// values. An error is expected with values that does not map to set of permissions listed in
/// `KeyPermission`.
#[test]
-fn keystore2_grant_key_with_invalid_perm_expecting_syserror() {
+fn grant_selinux_key_with_invalid_perm() {
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
let grantee_uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
let invalid_access_vector = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0 << 19;
- let result = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
- grantee_uid.try_into().unwrap(),
- invalid_access_vector,
- ));
+ let result = generate_and_grant_selinux_key(grantee_uid, invalid_access_vector);
assert!(result.is_err());
assert_eq!(Error::Rc(ResponseCode::SYSTEM_ERROR), result.unwrap_err());
}
-/// Try to grant a key with empty access vector `KeyPermission::NONE`, should be able to grant a
-/// key with empty access vector successfully. In grantee context try to use the granted key, it
-/// should fail to load the key with permission denied error.
+/// Try to grant an SELINUX key with empty access vector `KeyPermission::NONE`, should be able to
+/// grant a key with empty access vector successfully. In grantee context try to use the granted
+/// key, it should fail to load the key with permission denied error.
#[test]
-fn keystore2_grant_key_with_perm_none() {
- static TARGET_SU_CTX: &str = "u:r:su:s0";
-
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn grant_selinux_key_with_perm_none() {
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static GRANTEE_GID: u32 = GRANTEE_UID;
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let empty_access_vector = KeyPermission::NONE.0;
+ let grantor_fn = || {
+ let empty_access_vector = KeyPermission::NONE.0;
- let grant_key = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
- GRANTEE_UID.try_into().unwrap(),
- empty_access_vector,
- ))
- .unwrap();
+ let grant_key = generate_and_grant_selinux_key(GRANTEE_UID, empty_access_vector).unwrap();
- assert_eq!(grant_key.domain, Domain::GRANT);
+ assert_eq!(grant_key.domain, Domain::GRANT);
- grant_key.nspace
- })
+ grant_key.nspace
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
// In grantee context try to load the key, it should fail to load the granted key as it is
// granted with empty access vector.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
- let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- }));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
- },
- )
+ let result = get_granted_key(&keystore2, grant_key_nspace);
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
}
-/// Grant a key to the user (grantee) with `GET_INFO|USE` key permissions. Verify whether grantee
-/// can succeed in loading the granted key and try to perform simple operation using this granted
-/// key. Grantee should be able to load the key and use the key to perform crypto operation
+/// Grant an SELINUX key to the user (grantee) with `GET_INFO|USE` key permissions. Verify whether
+/// grantee can succeed in loading the granted key and try to perform simple operation using this
+/// granted key. Grantee should be able to load the key and use the key to perform crypto operation
/// successfully. Try to delete the granted key in grantee context where it is expected to fail to
/// delete it as `DELETE` permission is not granted.
#[test]
-fn keystore2_grant_get_info_use_key_perm() {
- static TARGET_SU_CTX: &str = "u:r:su:s0";
-
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn grant_selinux_key_get_info_use_perms() {
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static GRANTEE_GID: u32 = GRANTEE_UID;
// Generate a key and grant it to a user with GET_INFO|USE key permissions.
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
- let grant_key = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
- GRANTEE_UID.try_into().unwrap(),
- access_vector,
- ))
- .unwrap();
+ let grantor_fn = || {
+ let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
+ let grant_key = generate_and_grant_selinux_key(GRANTEE_UID, access_vector).unwrap();
- assert_eq!(grant_key.domain, Domain::GRANT);
+ assert_eq!(grant_key.domain, Domain::GRANT);
- grant_key.nspace
- })
+ grant_key.nspace
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
// In grantee context load the key and try to perform crypto operation.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let sl = SecLevel::tee();
+ let grantee_fn = move || {
+ let sl = SecLevel::tee();
- // Load the granted key.
- let key_entry_response = sl
- .keystore2
- .getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- })
- .unwrap();
+ // Load the granted key.
+ let key_entry_response = get_granted_key(&sl.keystore2, grant_key_nspace).unwrap();
- // Perform sample crypto operation using granted key.
- let op_response = sl
- .binder
- .createOperation(
- &key_entry_response.metadata.key,
- &authorizations::AuthSetBuilder::new()
- .purpose(KeyPurpose::SIGN)
- .digest(Digest::SHA_2_256),
- false,
- )
- .unwrap();
- assert!(op_response.iOperation.is_some());
- assert_eq!(
- Ok(()),
- key_generations::map_ks_error(perform_sample_sign_operation(
- &op_response.iOperation.unwrap()
- ))
- );
+ // Perform sample crypto operation using granted key.
+ let op_response = sl
+ .binder
+ .createOperation(
+ &key_entry_response.metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ )
+ .unwrap();
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ map_ks_error(perform_sample_sign_operation(&op_response.iOperation.unwrap()))
+ );
- // Try to delete the key, it is expected to be fail with permission denied error.
- let result =
- key_generations::map_ks_error(sl.keystore2.deleteKey(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- }));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
- },
- )
+ // Try to delete the key, it is expected to be fail with permission denied error.
+ let result =
+ map_ks_error(sl.keystore2.deleteKey(&granted_key_descriptor(grant_key_nspace)));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
}
-/// Grant a key to the user with DELETE access. In grantee context load the key and delete it.
+/// Grant an SELINUX key to the user (grantee) with just `GET_INFO` key permissions. Verify whether
+/// grantee can succeed in loading the granted key and try to perform simple operation using this
+/// granted key.
+#[test]
+fn grant_selinux_key_get_info_only() {
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+
+ // Generate a key and grant it to a user with (just) GET_INFO key permissions.
+ let grantor_fn = || {
+ let access_vector = KeyPermission::GET_INFO.0;
+ let grant_key = generate_and_grant_selinux_key(GRANTEE_UID, access_vector).unwrap();
+
+ assert_eq!(grant_key.domain, Domain::GRANT);
+
+ grant_key.nspace
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder on the main thread.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
+ // In grantee context load the key and try to perform crypto operation.
+ let grantee_fn = move || {
+ let sl = SecLevel::tee();
+
+ // Load the granted key.
+ let key_entry_response = get_granted_key(&sl.keystore2, grant_key_nspace)
+ .expect("failed to get info for granted key");
+
+ // Attempt to perform sample crypto operation using granted key, now identified by <KEY_ID,
+ // key_id>.
+ let result = map_ks_error(
+ sl.binder.createOperation(
+ &key_entry_response.metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ ),
+ );
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+
+ // Try to delete the key using a <GRANT, grant_id> descriptor.
+ let result =
+ map_ks_error(sl.keystore2.deleteKey(&granted_key_descriptor(grant_key_nspace)));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+
+ // Try to delete the key using a <KEY_ID, key_id> descriptor.
+ let result = map_ks_error(sl.keystore2.deleteKey(&key_entry_response.metadata.key));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder on the main thread.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+}
+
+/// Grant an APP key to the user (grantee) with just `GET_INFO` key permissions. Verify whether
+/// grantee can succeed in loading the granted key and try to perform simple operation using this
+/// granted key.
+#[test]
+fn grant_app_key_get_info_only() {
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+ static ALIAS: &str = "ks_grant_key_info_only";
+
+ // Generate a key and grant it to a user with (just) GET_INFO key permissions.
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let access_vector = KeyPermission::GET_INFO.0;
+ let mut grant_keys = generate_ec_key_and_grant_to_users(
+ &sl,
+ Some(ALIAS.to_string()),
+ vec![GRANTEE_UID.try_into().unwrap()],
+ access_vector,
+ )
+ .unwrap();
+
+ grant_keys.remove(0)
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder on the main thread.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
+ // In grantee context load the key and try to perform crypto operation.
+ let grantee_fn = move || {
+ let sl = SecLevel::tee();
+
+ // Load the granted key.
+ let key_entry_response = get_granted_key(&sl.keystore2, grant_key_nspace)
+ .expect("failed to get info for granted key");
+
+ // Attempt to perform sample crypto operation using granted key, now identified by <KEY_ID,
+ // key_id>.
+ let result = map_ks_error(
+ sl.binder.createOperation(
+ &key_entry_response.metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ ),
+ );
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+
+ // Try to delete the key using a <GRANT, grant_id> descriptor.
+ let result =
+ map_ks_error(sl.keystore2.deleteKey(&granted_key_descriptor(grant_key_nspace)));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+
+ // Try to delete the key using a <KEY_ID, key_id> descriptor.
+ let result = map_ks_error(sl.keystore2.deleteKey(&key_entry_response.metadata.key));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder on the main thread.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+}
+
+/// Grant an APP key to the user with DELETE access. In grantee context load the key and delete it.
/// Verify that grantee should succeed in deleting the granted key and in grantor context test
/// should fail to find the key with error response `KEY_NOT_FOUND`.
#[test]
-fn keystore2_grant_delete_key_success() {
- static GRANTOR_SU_CTX: &str = "u:r:su:s0";
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn grant_app_key_delete_success() {
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
@@ -250,70 +348,63 @@
static ALIAS: &str = "ks_grant_key_delete_success";
// Generate a key and grant it to a user with DELETE permission.
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let access_vector = KeyPermission::DELETE.0;
- let mut grant_keys = generate_ec_key_and_grant_to_users(
- &sl,
- Some(ALIAS.to_string()),
- vec![GRANTEE_UID.try_into().unwrap()],
- access_vector,
- )
- .unwrap();
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let access_vector = KeyPermission::DELETE.0;
+ let mut grant_keys = generate_ec_key_and_grant_to_users(
+ &sl,
+ Some(ALIAS.to_string()),
+ vec![GRANTEE_UID.try_into().unwrap()],
+ access_vector,
+ )
+ .unwrap();
- grant_keys.remove(0)
- })
+ grant_keys.remove(0)
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
// Grantee context, delete the key.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
- keystore2
- .deleteKey(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- })
- .unwrap();
- },
- )
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ keystore2.deleteKey(&granted_key_descriptor(grant_key_nspace)).unwrap();
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+
// Verify whether key got deleted in grantor's context.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), move || {
- let keystore2_inst = get_keystore_service();
- let result =
- key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(ALIAS.to_string()),
- blob: None,
- }));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
- })
+ let grantor_fn = move || {
+ let keystore2_inst = get_keystore_service();
+ let result = map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(ALIAS.to_string()),
+ blob: None,
+ }));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_root(grantor_fn) };
}
-/// Grant a key to the user. In grantee context load the granted key and try to grant it to second
-/// user. Test should fail with a response code `PERMISSION_DENIED` to grant a key to second user
-/// from grantee context. Test should make sure second grantee should not have a access to granted
-/// key.
+/// Grant an APP key to the user. In grantee context load the granted key and try to grant it to
+/// second user. Test should fail with a response code `PERMISSION_DENIED` to grant a key to second
+/// user from grantee context. Test should make sure second grantee should not have a access to
+/// granted key.
#[test]
-fn keystore2_grant_key_fails_with_permission_denied() {
- static GRANTOR_SU_CTX: &str = "u:r:su:s0";
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn grant_granted_app_key_fails() {
+ const GRANTOR_USER_ID: u32 = 97;
+ const GRANTOR_APPLICATION_ID: u32 = 10003;
+ static GRANTOR_UID: u32 = GRANTOR_USER_ID * AID_USER_OFFSET + GRANTOR_APPLICATION_ID;
+ static GRANTOR_GID: u32 = GRANTOR_UID;
+
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
@@ -325,83 +416,148 @@
static SEC_GRANTEE_GID: u32 = SEC_GRANTEE_UID;
// Generate a key and grant it to a user with GET_INFO permission.
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let access_vector = KeyPermission::GET_INFO.0;
- let alias = format!("ks_grant_perm_denied_key_{}", getuid());
- let mut grant_keys = generate_ec_key_and_grant_to_users(
- &sl,
- Some(alias),
- vec![GRANTEE_UID.try_into().unwrap()],
- access_vector,
- )
- .unwrap();
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let access_vector = KeyPermission::GET_INFO.0;
+ let alias = format!("ks_grant_perm_denied_key_{}", getuid());
+ let mut grant_keys = generate_ec_key_and_grant_to_users(
+ &sl,
+ Some(alias),
+ vec![GRANTEE_UID.try_into().unwrap()],
+ access_vector,
+ )
+ .unwrap();
- grant_keys.remove(0)
- })
+ grant_keys.remove(0)
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_app(GRANTOR_UID, GRANTOR_GID, grantor_fn) };
// Grantee context, load the granted key and try to grant it to `SEC_GRANTEE_UID` grantee.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
- let access_vector = KeyPermission::GET_INFO.0;
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ let access_vector = KeyPermission::GET_INFO.0;
- let key_entry_response = keystore2
- .getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- })
- .unwrap();
+ // Try to grant when identifying the key with <GRANT, grant_nspace>.
+ let result = map_ks_error(keystore2.grant(
+ &granted_key_descriptor(grant_key_nspace),
+ SEC_GRANTEE_UID.try_into().unwrap(),
+ access_vector,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::SYSTEM_ERROR), result.unwrap_err());
- let result = key_generations::map_ks_error(keystore2.grant(
- &key_entry_response.metadata.key,
- SEC_GRANTEE_UID.try_into().unwrap(),
- access_vector,
- ));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
- },
- )
+ // Load the key info and try to grant when identifying the key with <KEY_ID, keyid>.
+ let key_entry_response = get_granted_key(&keystore2, grant_key_nspace).unwrap();
+ let result = map_ks_error(keystore2.grant(
+ &key_entry_response.metadata.key,
+ SEC_GRANTEE_UID.try_into().unwrap(),
+ access_vector,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+
// Make sure second grantee shouldn't have access to the above granted key.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(SEC_GRANTEE_UID),
- Gid::from_raw(SEC_GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
-
- let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- }));
-
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
- },
- )
+ let grantee2_fn = move || {
+ let keystore2 = get_keystore_service();
+ let result = get_granted_key(&keystore2, grant_key_nspace);
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(SEC_GRANTEE_UID, SEC_GRANTEE_GID, grantee2_fn) };
}
-/// Try to grant a key with `GRANT` access. Keystore2 system shouldn't allow to grant a key with
-/// `GRANT` access. Test should fail to grant a key with `PERMISSION_DENIED` error response code.
+/// Grant an APP key to one user, from a normal user. Check that grantee context can load the
+/// granted key, but that a second unrelated context cannot.
#[test]
-fn keystore2_grant_key_fails_with_grant_perm_expect_perm_denied() {
+fn grant_app_key_only_to_grantee() {
+ const GRANTOR_USER_ID: u32 = 97;
+ const GRANTOR_APPLICATION_ID: u32 = 10003;
+ static GRANTOR_UID: u32 = GRANTOR_USER_ID * AID_USER_OFFSET + GRANTOR_APPLICATION_ID;
+ static GRANTOR_GID: u32 = GRANTOR_UID;
+
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+
+ const SEC_USER_ID: u32 = 98;
+ const SEC_APPLICATION_ID: u32 = 10001;
+ static SEC_GRANTEE_UID: u32 = SEC_USER_ID * AID_USER_OFFSET + SEC_APPLICATION_ID;
+ static SEC_GRANTEE_GID: u32 = SEC_GRANTEE_UID;
+
+ // Child function to generate a key and grant it to a user with `GET_INFO` permission.
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let access_vector = KeyPermission::GET_INFO.0;
+ let alias = format!("ks_grant_single_{}", getuid());
+ let mut grant_keys = generate_ec_key_and_grant_to_users(
+ &sl,
+ Some(alias),
+ vec![GRANTEE_UID.try_into().unwrap()],
+ access_vector,
+ )
+ .unwrap();
+
+ grant_keys.remove(0)
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder on the main thread.
+ let grant_key_nspace = unsafe { run_as::run_as_app(GRANTOR_UID, GRANTOR_GID, grantor_fn) };
+
+ // Child function for the grantee context: can load the granted key.
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ let rsp = get_granted_key(&keystore2, grant_key_nspace).expect("failed to get granted key");
+
+ // Return the underlying key ID to simulate an ID leak.
+ assert_eq!(rsp.metadata.key.domain, Domain::KEY_ID);
+ rsp.metadata.key.nspace
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder on the main thread.
+ let key_id = unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+
+ // Second context does not have access to the above granted key, because it's identified
+ // by <uid, grant_nspace> and the implicit uid value is different. Also, even if the
+ // second context gets hold of the key ID somehow, that also doesn't work.
+ let non_grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ let result = get_granted_key(&keystore2, grant_key_nspace);
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+
+ let result = map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
+ domain: Domain::KEY_ID,
+ nspace: key_id,
+ alias: None,
+ blob: None,
+ }));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder on the main thread.
+ unsafe { run_as::run_as_app(SEC_GRANTEE_UID, SEC_GRANTEE_GID, non_grantee_fn) };
+}
+
+/// Try to grant an APP key with `GRANT` access. Keystore2 system shouldn't allow to grant a key
+/// with `GRANT` access. Test should fail to grant a key with `PERMISSION_DENIED` error response
+/// code.
+#[test]
+fn grant_app_key_with_grant_perm_fails() {
let sl = SecLevel::tee();
let access_vector = KeyPermission::GRANT.0;
let alias = format!("ks_grant_access_vec_key_{}", getuid());
@@ -409,7 +565,7 @@
let application_id = 10001;
let grantee_uid = user_id * AID_USER_OFFSET + application_id;
- let result = key_generations::map_ks_error(generate_ec_key_and_grant_to_users(
+ let result = map_ks_error(generate_ec_key_and_grant_to_users(
&sl,
Some(alias),
vec![grantee_uid.try_into().unwrap()],
@@ -419,10 +575,10 @@
assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
}
-/// Try to grant a non-existing key to the user. Test should fail with `KEY_NOT_FOUND` error
+/// Try to grant a non-existing SELINUX key to the user. Test should fail with `KEY_NOT_FOUND` error
/// response.
#[test]
-fn keystore2_grant_fails_with_non_existing_key_expect_key_not_found_err() {
+fn grant_fails_with_non_existing_selinux_key() {
let keystore2 = get_keystore_service();
let alias = format!("ks_grant_test_non_existing_key_5_{}", getuid());
let user_id = 98;
@@ -430,7 +586,7 @@
let grantee_uid = user_id * AID_USER_OFFSET + application_id;
let access_vector = KeyPermission::GET_INFO.0;
- let result = key_generations::map_ks_error(keystore2.grant(
+ let result = map_ks_error(keystore2.grant(
&KeyDescriptor {
domain: Domain::SELINUX,
nspace: key_generations::SELINUX_SHELL_NAMESPACE,
@@ -444,71 +600,56 @@
assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
}
-/// Grant a key to the user and immediately ungrant the granted key. In grantee context try to load
+/// Grant an APP key to the user and immediately ungrant the granted key. In grantee context try to load
/// the key. Grantee should fail to load the ungranted key with `KEY_NOT_FOUND` error response.
#[test]
-fn keystore2_ungrant_key_success() {
- static GRANTOR_SU_CTX: &str = "u:r:su:s0";
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn ungrant_app_key_success() {
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static GRANTEE_GID: u32 = GRANTEE_UID;
// Generate a key and grant it to a user with GET_INFO permission.
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let alias = format!("ks_ungrant_test_key_1{}", getuid());
- let access_vector = KeyPermission::GET_INFO.0;
- let mut grant_keys = generate_ec_key_and_grant_to_users(
- &sl,
- Some(alias.to_string()),
- vec![GRANTEE_UID.try_into().unwrap()],
- access_vector,
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let alias = format!("ks_ungrant_test_key_1{}", getuid());
+ let access_vector = KeyPermission::GET_INFO.0;
+ let mut grant_keys = generate_ec_key_and_grant_to_users(
+ &sl,
+ Some(alias.to_string()),
+ vec![GRANTEE_UID.try_into().unwrap()],
+ access_vector,
+ )
+ .unwrap();
+
+ let grant_key_nspace = grant_keys.remove(0);
+
+ // Ungrant above granted key.
+ sl.keystore2
+ .ungrant(
+ &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
+ GRANTEE_UID.try_into().unwrap(),
)
.unwrap();
- let grant_key_nspace = grant_keys.remove(0);
-
- // Ungrant above granted key.
- sl.keystore2
- .ungrant(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(alias),
- blob: None,
- },
- GRANTEE_UID.try_into().unwrap(),
- )
- .unwrap();
-
- grant_key_nspace
- })
+ grant_key_nspace
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
// Grantee context, try to load the ungranted key.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
- let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- }));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
- },
- )
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ let result = get_granted_key(&keystore2, grant_key_nspace);
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
}
/// Generate a key, grant it to the user and then delete the granted key. Try to ungrant
@@ -517,94 +658,78 @@
/// key in grantee context. Test should fail to load the granted key in grantee context as the
/// associated key is deleted from grantor context.
#[test]
-fn keystore2_ungrant_fails_with_non_existing_key_expect_key_not_found_error() {
- static GRANTOR_SU_CTX: &str = "u:r:su:s0";
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
+fn ungrant_deleted_app_key_fails() {
const APPLICATION_ID: u32 = 10001;
const USER_ID: u32 = 99;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static GRANTEE_GID: u32 = GRANTEE_UID;
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let alias = format!("{}{}", "ks_grant_delete_ungrant_test_key_1", getuid());
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let alias = format!("{}{}", "ks_grant_delete_ungrant_test_key_1", getuid());
- let key_metadata = key_generations::generate_ec_p256_signing_key(
- &sl,
- Domain::SELINUX,
- key_generations::SELINUX_SHELL_NAMESPACE,
- Some(alias.to_string()),
- None,
- )
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sl,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias.to_string()),
+ None,
+ )
+ .unwrap();
+
+ let access_vector = KeyPermission::GET_INFO.0;
+ let grant_key = sl
+ .keystore2
+ .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
.unwrap();
+ assert_eq!(grant_key.domain, Domain::GRANT);
- let access_vector = KeyPermission::GET_INFO.0;
- let grant_key = sl
- .keystore2
- .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
- .unwrap();
- assert_eq!(grant_key.domain, Domain::GRANT);
+ // Delete above granted key.
+ sl.keystore2.deleteKey(&key_metadata.key).unwrap();
- // Delete above granted key.
- sl.keystore2.deleteKey(&key_metadata.key).unwrap();
+ // Try to ungrant above granted key.
+ let result =
+ map_ks_error(sl.keystore2.ungrant(&key_metadata.key, GRANTEE_UID.try_into().unwrap()));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
- // Try to ungrant above granted key.
- let result = key_generations::map_ks_error(
- sl.keystore2.ungrant(&key_metadata.key, GRANTEE_UID.try_into().unwrap()),
- );
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+ // Generate a new key with the same alias and try to access the earlier granted key
+ // in grantee context.
+ let result = key_generations::generate_ec_p256_signing_key(
+ &sl,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias),
+ None,
+ );
+ assert!(result.is_ok());
- // Generate a new key with the same alias and try to access the earlier granted key
- // in grantee context.
- let result = key_generations::generate_ec_p256_signing_key(
- &sl,
- Domain::SELINUX,
- key_generations::SELINUX_SHELL_NAMESPACE,
- Some(alias),
- None,
- );
- assert!(result.is_ok());
-
- grant_key.nspace
- })
+ grant_key.nspace
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
// Make sure grant did not persist, try to access the earlier granted key in grantee context.
// Grantee context should fail to load the granted key as its associated key is deleted in
// grantor context.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
-
- let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key_nspace,
- alias: None,
- blob: None,
- }));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
- },
- )
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ let result = get_granted_key(&keystore2, grant_key_nspace);
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
}
/// Grant a key to multiple users. Verify that all grantees should succeed in loading the key and
/// use it for performing an operation successfully.
#[test]
-fn keystore2_grant_key_to_multi_users_success() {
- static GRANTOR_SU_CTX: &str = "u:r:su:s0";
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
+fn grant_app_key_to_multi_users_success() {
const APPLICATION_ID: u32 = 10001;
const USER_ID_1: u32 = 99;
static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID;
@@ -615,46 +740,34 @@
static GRANTEE_2_GID: u32 = GRANTEE_2_UID;
// Generate a key and grant it to multiple users with GET_INFO|USE permissions.
- // SAFETY: The test is run in a separate process with no other threads.
- let mut grant_keys = unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let alias = format!("ks_grant_test_key_2{}", getuid());
- let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let alias = format!("ks_grant_test_key_2{}", getuid());
+ let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
- generate_ec_key_and_grant_to_users(
- &sl,
- Some(alias),
- vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()],
- access_vector,
- )
- .unwrap()
- })
+ generate_ec_key_and_grant_to_users(
+ &sl,
+ Some(alias),
+ vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()],
+ access_vector,
+ )
+ .unwrap()
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let mut grant_keys = unsafe { run_as::run_as_root(grantor_fn) };
+
for (grantee_uid, grantee_gid) in
&[(GRANTEE_1_UID, GRANTEE_1_GID), (GRANTEE_2_UID, GRANTEE_2_GID)]
{
let grant_key_nspace = grant_keys.remove(0);
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(*grantee_uid),
- Gid::from_raw(*grantee_gid),
- move || {
- let sl = SecLevel::tee();
-
- assert_eq!(
- Ok(()),
- key_generations::map_ks_error(load_grant_key_and_perform_sign_operation(
- &sl,
- grant_key_nspace
- ))
- );
- },
- )
+ let grantee_fn = move || {
+ assert_eq!(Ok(()), sign_with_granted_key(grant_key_nspace));
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(*grantee_uid, *grantee_gid, grantee_fn) };
}
}
@@ -662,10 +775,7 @@
/// use the key and delete it. Try to load the granted key in another grantee context. Test should
/// fail to load the granted key with `KEY_NOT_FOUND` error response.
#[test]
-fn keystore2_grant_key_to_multi_users_delete_fails_with_key_not_found_error() {
- static GRANTOR_SU_CTX: &str = "u:r:su:s0";
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
+fn grant_app_key_to_multi_users_delete_then_key_not_found() {
const USER_ID_1: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID;
@@ -676,76 +786,50 @@
static GRANTEE_2_GID: u32 = GRANTEE_2_UID;
// Generate a key and grant it to multiple users with GET_INFO permission.
- // SAFETY: The test is run in a separate process with no other threads.
- let mut grant_keys = unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let alias = format!("ks_grant_test_key_2{}", getuid());
- let access_vector =
- KeyPermission::GET_INFO.0 | KeyPermission::USE.0 | KeyPermission::DELETE.0;
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let alias = format!("ks_grant_test_key_2{}", getuid());
+ let access_vector =
+ KeyPermission::GET_INFO.0 | KeyPermission::USE.0 | KeyPermission::DELETE.0;
- generate_ec_key_and_grant_to_users(
- &sl,
- Some(alias),
- vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()],
- access_vector,
- )
- .unwrap()
- })
+ generate_ec_key_and_grant_to_users(
+ &sl,
+ Some(alias),
+ vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()],
+ access_vector,
+ )
+ .unwrap()
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let mut grant_keys = unsafe { run_as::run_as_root(grantor_fn) };
+
// Grantee #1 context
let grant_key1_nspace = grant_keys.remove(0);
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_1_UID),
- Gid::from_raw(GRANTEE_1_GID),
- move || {
- let sl = SecLevel::tee();
+ let grantee1_fn = move || {
+ assert_eq!(Ok(()), sign_with_granted_key(grant_key1_nspace));
- assert_eq!(
- Ok(()),
- key_generations::map_ks_error(load_grant_key_and_perform_sign_operation(
- &sl,
- grant_key1_nspace
- ))
- );
-
- // Delete the granted key.
- sl.keystore2
- .deleteKey(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key1_nspace,
- alias: None,
- blob: None,
- })
- .unwrap();
- },
- )
+ // Delete the granted key.
+ get_keystore_service().deleteKey(&granted_key_descriptor(grant_key1_nspace)).unwrap();
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_1_UID, GRANTEE_1_GID, grantee1_fn) };
+
// Grantee #2 context
let grant_key2_nspace = grant_keys.remove(0);
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_2_UID),
- Gid::from_raw(GRANTEE_2_GID),
- move || {
- let keystore2 = get_keystore_service();
+ let grantee2_fn = move || {
+ let keystore2 = get_keystore_service();
- let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: grant_key2_nspace,
- alias: None,
- blob: None,
- }));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
- },
- )
+ let result =
+ map_ks_error(keystore2.getKeyEntry(&granted_key_descriptor(grant_key2_nspace)));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_2_UID, GRANTEE_2_GID, grantee2_fn) };
}
diff --git a/keystore2/tests/keystore2_client_import_keys_tests.rs b/keystore2/tests/keystore2_client_import_keys_tests.rs
index b06303e..f3a267b 100644
--- a/keystore2/tests/keystore2_client_import_keys_tests.rs
+++ b/keystore2/tests/keystore2_client_import_keys_tests.rs
@@ -247,8 +247,8 @@
}
/// Try to import a key with multiple purposes. Test should fail to import a key with
-/// `INCOMPATIBLE_PURPOSE` error code. If the backend is `keymaster` then `importKey` shall be
-/// successful.
+/// `INCOMPATIBLE_PURPOSE` error code. If the backend is `keymaster` or KeyMint-version-1 then
+/// `importKey` shall be successful.
#[test]
fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() {
let sl = SecLevel::tee();
@@ -276,8 +276,14 @@
));
if sl.is_keymint() {
- assert!(result.is_err());
- assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+ if sl.get_keymint_version() >= 2 {
+ // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined
+ // with other key purposes. However, this was not checked at the time
+ // so we can only be strict about checking this for implementations of KeyMint
+ // version 2 and above.
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+ }
} else {
assert!(result.is_ok());
}
diff --git a/keystore2/tests/keystore2_client_key_agreement_tests.rs b/keystore2/tests/keystore2_client_key_agreement_tests.rs
index 19f6210..6744b60 100644
--- a/keystore2/tests/keystore2_client_key_agreement_tests.rs
+++ b/keystore2/tests/keystore2_client_key_agreement_tests.rs
@@ -112,7 +112,7 @@
test_ec_key_agree!(test_ec_p384_key_agreement, EcCurve::P_384);
test_ec_key_agree!(test_ec_p521_key_agreement, EcCurve::P_521);
-/// Generate two EC keys with curve `CURVE_25519` from KeyMint and OpeanSSL.
+/// Generate two EC keys with curve `CURVE_25519` from KeyMint and OpenSSL.
/// Perform local ECDH between them and verify that the derived secrets are the same.
#[test]
fn keystore2_ec_25519_agree_key_success() {
diff --git a/keystore2/tests/keystore2_client_keystore_engine_tests.rs b/keystore2/tests/keystore2_client_keystore_engine_tests.rs
index 01f8917..a4d7f2c 100644
--- a/keystore2/tests/keystore2_client_keystore_engine_tests.rs
+++ b/keystore2/tests/keystore2_client_keystore_engine_tests.rs
@@ -24,7 +24,6 @@
use keystore2_test_utils::{
authorizations::AuthSetBuilder, get_keystore_service, run_as, SecLevel,
};
-use nix::unistd::{Gid, Uid};
use openssl::x509::X509;
use rustutils::users::AID_USER_OFFSET;
@@ -152,80 +151,65 @@
}
#[test]
-fn keystore2_perofrm_crypto_op_using_keystore2_engine_rsa_key_success() {
- static TARGET_SU_CTX: &str = "u:r:su:s0";
-
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn keystore2_perform_crypto_op_using_keystore2_engine_rsa_key_success() {
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static GRANTEE_GID: u32 = GRANTEE_UID;
// Generate a key and grant it to a user with GET_INFO|USE|DELETE key permissions.
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let alias = "keystore2_engine_rsa_key";
- generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap()
- })
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let alias = "keystore2_engine_rsa_key";
+ generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap()
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
// In grantee context load the key and try to perform crypto operation.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
- perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
- },
- )
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
}
#[test]
-fn keystore2_perofrm_crypto_op_using_keystore2_engine_ec_key_success() {
- static TARGET_SU_CTX: &str = "u:r:su:s0";
-
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn keystore2_perform_crypto_op_using_keystore2_engine_ec_key_success() {
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static GRANTEE_GID: u32 = GRANTEE_UID;
// Generate a key and grant it to a user with GET_INFO|USE|DELETE key permissions.
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let alias = "keystore2_engine_ec_test_key";
- generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::EC).unwrap()
- })
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let alias = "keystore2_engine_ec_test_key";
+ generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::EC).unwrap()
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
// In grantee context load the key and try to perform crypto operation.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
- perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
- },
- )
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
}
#[test]
-fn keystore2_perofrm_crypto_op_using_keystore2_engine_pem_pub_key_success() {
- static TARGET_SU_CTX: &str = "u:r:su:s0";
-
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn keystore2_perform_crypto_op_using_keystore2_engine_pem_pub_key_success() {
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
@@ -233,46 +217,43 @@
// Generate a key and re-encode it's certificate as PEM and update it and
// grant it to a user with GET_INFO|USE|DELETE key permissions.
- // SAFETY: The test is run in a separate process with no other threads.
- let grant_key_nspace = unsafe {
- run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let alias = "keystore2_engine_rsa_pem_pub_key";
- let grant_key_nspace =
- generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap();
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let alias = "keystore2_engine_rsa_pem_pub_key";
+ let grant_key_nspace =
+ generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap();
- // Update certificate with encodeed PEM data.
- let key_entry_response = sl
- .keystore2
- .getKeyEntry(&KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some(alias.to_string()),
- blob: None,
- })
- .unwrap();
- let cert_bytes = key_entry_response.metadata.certificate.as_ref().unwrap();
- let cert = X509::from_der(cert_bytes.as_ref()).unwrap();
- let cert_pem = cert.to_pem().unwrap();
- sl.keystore2
- .updateSubcomponent(&key_entry_response.metadata.key, Some(&cert_pem), None)
- .expect("updateSubcomponent failed.");
+ // Update certificate with encodeed PEM data.
+ let key_entry_response = sl
+ .keystore2
+ .getKeyEntry(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ })
+ .unwrap();
+ let cert_bytes = key_entry_response.metadata.certificate.as_ref().unwrap();
+ let cert = X509::from_der(cert_bytes.as_ref()).unwrap();
+ let cert_pem = cert.to_pem().unwrap();
+ sl.keystore2
+ .updateSubcomponent(&key_entry_response.metadata.key, Some(&cert_pem), None)
+ .expect("updateSubcomponent failed.");
- grant_key_nspace
- })
+ grant_key_nspace
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
// In grantee context load the key and try to perform crypto operation.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let keystore2 = get_keystore_service();
- perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
- },
- )
+ let grantee_fn = move || {
+ let keystore2 = get_keystore_service();
+ perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
}
diff --git a/keystore2/tests/keystore2_client_list_entries_tests.rs b/keystore2/tests/keystore2_client_list_entries_tests.rs
index 539dac2..bb1d6cf 100644
--- a/keystore2/tests/keystore2_client_list_entries_tests.rs
+++ b/keystore2/tests/keystore2_client_list_entries_tests.rs
@@ -20,7 +20,7 @@
use keystore2_test_utils::{
get_keystore_service, key_generations, key_generations::Error, run_as, SecLevel,
};
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
use rustutils::users::AID_USER_OFFSET;
use std::collections::HashSet;
use std::fmt::Write;
@@ -51,103 +51,97 @@
/// context. GRANT keys shouldn't be part of this list.
#[test]
fn keystore2_list_entries_success() {
- static GRANTOR_SU_CTX: &str = "u:r:su:s0";
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
const USER_ID: u32 = 91;
const APPLICATION_ID: u32 = 10006;
static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static GRANTEE_GID: u32 = GRANTEE_UID;
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
+ let gen_key_fn = || {
+ let sl = SecLevel::tee();
- let alias = format!("list_entries_grant_key1_{}", getuid());
+ let alias = format!("list_entries_grant_key1_{}", getuid());
- // Make sure there is no key exist with this `alias` in `SELINUX` domain and
- // `SELINUX_SHELL_NAMESPACE` namespace.
- if key_alias_exists(
- &sl.keystore2,
- Domain::SELINUX,
- key_generations::SELINUX_SHELL_NAMESPACE,
- alias.to_string(),
- ) {
- sl.keystore2
- .deleteKey(&KeyDescriptor {
- domain: Domain::SELINUX,
- nspace: key_generations::SELINUX_SHELL_NAMESPACE,
- alias: Some(alias.to_string()),
- blob: None,
- })
- .unwrap();
- }
-
- // Generate a key with above defined `alias`.
- let key_metadata = key_generations::generate_ec_p256_signing_key(
- &sl,
- Domain::SELINUX,
- key_generations::SELINUX_SHELL_NAMESPACE,
- Some(alias.to_string()),
- None,
- )
- .unwrap();
-
- // Verify that above generated key entry is listed with domain SELINUX and
- // namespace SELINUX_SHELL_NAMESPACE
- assert!(key_alias_exists(
- &sl.keystore2,
- Domain::SELINUX,
- key_generations::SELINUX_SHELL_NAMESPACE,
- alias,
- ));
-
- // Grant a key with GET_INFO permission.
- let access_vector = KeyPermission::GET_INFO.0;
+ // Make sure there is no key exist with this `alias` in `SELINUX` domain and
+ // `SELINUX_SHELL_NAMESPACE` namespace.
+ if key_alias_exists(
+ &sl.keystore2,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ alias.to_string(),
+ ) {
sl.keystore2
- .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
+ .deleteKey(&KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: key_generations::SELINUX_SHELL_NAMESPACE,
+ alias: Some(alias.to_string()),
+ blob: None,
+ })
.unwrap();
- })
+ }
+
+ // Generate a key with above defined `alias`.
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sl,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias.to_string()),
+ None,
+ )
+ .unwrap();
+
+ // Verify that above generated key entry is listed with domain SELINUX and
+ // namespace SELINUX_SHELL_NAMESPACE
+ assert!(key_alias_exists(
+ &sl.keystore2,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ alias,
+ ));
+
+ // Grant a key with GET_INFO permission.
+ let access_vector = KeyPermission::GET_INFO.0;
+ sl.keystore2
+ .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
+ .unwrap();
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_root(gen_key_fn) };
+
// In user context validate list of key entries associated with it.
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_UID),
- Gid::from_raw(GRANTEE_GID),
- move || {
- let sl = SecLevel::tee();
- let alias = format!("list_entries_success_key{}", getuid());
+ let list_keys_fn = move || {
+ let sl = SecLevel::tee();
+ let alias = format!("list_entries_success_key{}", getuid());
- let key_metadata = key_generations::generate_ec_p256_signing_key(
- &sl,
- Domain::APP,
- -1,
- Some(alias.to_string()),
- None,
- )
- .unwrap();
-
- // Make sure there is only one key entry exist and that should be the same key
- // generated in this user context. Granted key shouldn't be included in this list.
- let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
- assert_eq!(1, key_descriptors.len());
-
- let key = key_descriptors.first().unwrap();
- assert_eq!(key.alias, Some(alias));
- assert_eq!(key.nspace, GRANTEE_UID.try_into().unwrap());
- assert_eq!(key.domain, Domain::APP);
-
- sl.keystore2.deleteKey(&key_metadata.key).unwrap();
-
- let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
- assert_eq!(0, key_descriptors.len());
- },
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sl,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ None,
)
+ .unwrap();
+
+ // Make sure there is only one existing key entry and that should be the same key
+ // generated in this user context. Granted key shouldn't be included in this list.
+ let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
+ assert_eq!(1, key_descriptors.len());
+
+ let key = key_descriptors.first().unwrap();
+ assert_eq!(key.alias, Some(alias));
+ assert_eq!(key.nspace, GRANTEE_UID.try_into().unwrap());
+ assert_eq!(key.domain, Domain::APP);
+
+ sl.keystore2.deleteKey(&key_metadata.key).unwrap();
+
+ let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
+ assert_eq!(0, key_descriptors.len());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, list_keys_fn) };
}
/// Try to list the key entries with domain SELINUX from user context where user doesn't possesses
@@ -157,20 +151,19 @@
fn keystore2_list_entries_fails_perm_denied() {
let auid = 91 * AID_USER_OFFSET + 10001;
let agid = 91 * AID_USER_OFFSET + 10001;
- static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ let list_keys_fn = move || {
+ let keystore2 = get_keystore_service();
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
- let keystore2 = get_keystore_service();
-
- let result = key_generations::map_ks_error(
- keystore2.listEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
- );
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
- })
+ let result = key_generations::map_ks_error(
+ keystore2.listEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
+ );
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(auid, agid, list_keys_fn) };
}
/// Try to list key entries with domain BLOB. Test should fail with error repose code
@@ -190,64 +183,63 @@
/// of all the entries in the keystore.
#[test]
fn keystore2_list_entries_with_long_aliases_success() {
- static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
- let sl = SecLevel::tee();
+ let import_keys_fn = || {
+ let sl = SecLevel::tee();
- // Make sure there are no keystore entries exist before adding new entries.
+ // Make sure there are no keystore entries exist before adding new entries.
+ let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
+ if !key_descriptors.is_empty() {
+ key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
+ delete_app_key(&sl.keystore2, &alias).unwrap();
+ });
+ }
+
+ let mut imported_key_aliases = HashSet::new();
+
+ // Import 100 keys with aliases of length 6000.
+ for count in 1..101 {
+ let mut alias = String::new();
+ write!(alias, "{}_{}", "X".repeat(6000), count).unwrap();
+ imported_key_aliases.insert(alias.clone());
+
+ let result = key_generations::import_aes_key(&sl, Domain::APP, -1, Some(alias));
+ assert!(result.is_ok());
+ }
+
+ // b/222287335 Limiting Keystore `listEntries` API to return subset of the Keystore
+ // entries to avoid running out of binder buffer space.
+ // To verify that all the imported key aliases are present in Keystore,
+ // - get the list of entries from Keystore
+ // - check whether the retrieved key entries list is a subset of imported key aliases
+ // - delete this subset of keystore entries from Keystore as well as from imported
+ // list of key aliases
+ // - continue above steps till it cleanup all the imported keystore entries.
+ while !imported_key_aliases.is_empty() {
let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
- if !key_descriptors.is_empty() {
- key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
- delete_app_key(&sl.keystore2, &alias).unwrap();
- });
- }
- let mut imported_key_aliases = HashSet::new();
+ // Check retrieved key entries list is a subset of imported keys list.
+ assert!(key_descriptors
+ .iter()
+ .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
- // Import 100 keys with aliases of length 6000.
- for count in 1..101 {
- let mut alias = String::new();
- write!(alias, "{}_{}", "X".repeat(6000), count).unwrap();
- imported_key_aliases.insert(alias.clone());
+ // Delete the listed key entries from Keystore as well as from imported keys list.
+ key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
+ delete_app_key(&sl.keystore2, &alias).unwrap();
+ assert!(imported_key_aliases.remove(&alias));
+ });
+ }
- let result = key_generations::import_aes_key(&sl, Domain::APP, -1, Some(alias));
- assert!(result.is_ok());
- }
-
- // b/222287335 Limiting Keystore `listEntries` API to return subset of the Keystore
- // entries to avoid running out of binder buffer space.
- // To verify that all the imported key aliases are present in Keystore,
- // - get the list of entries from Keystore
- // - check whether the retrieved key entries list is a subset of imported key aliases
- // - delete this subset of keystore entries from Keystore as well as from imported
- // list of key aliases
- // - continue above steps till it cleanup all the imported keystore entries.
- while !imported_key_aliases.is_empty() {
- let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
-
- // Check retrieved key entries list is a subset of imported keys list.
- assert!(key_descriptors
- .iter()
- .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
-
- // Delete the listed key entries from Keystore as well as from imported keys list.
- key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
- delete_app_key(&sl.keystore2, &alias).unwrap();
- assert!(imported_key_aliases.remove(&alias));
- });
- }
-
- assert!(imported_key_aliases.is_empty());
- })
+ assert!(imported_key_aliases.is_empty());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, import_keys_fn) };
}
/// Import large number of Keystore entries with long aliases such that the
@@ -255,58 +247,57 @@
/// Try to list aliases of all the entries in the keystore using `listEntriesBatched` API.
#[test]
fn keystore2_list_entries_batched_with_long_aliases_success() {
- static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
- let sl = SecLevel::tee();
+ let import_keys_fn = || {
+ let sl = SecLevel::tee();
- // Make sure there are no keystore entries exist before adding new entries.
- delete_all_entries(&sl.keystore2);
+ // Make sure there are no keystore entries exist before adding new entries.
+ delete_all_entries(&sl.keystore2);
- // Import 100 keys with aliases of length 6000.
- let mut imported_key_aliases =
- key_generations::import_aes_keys(&sl, "X".repeat(6000), 1..101).unwrap();
- assert_eq!(
- sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
- 100,
- "Error while importing keys"
- );
+ // Import 100 keys with aliases of length 6000.
+ let mut imported_key_aliases =
+ key_generations::import_aes_keys(&sl, "X".repeat(6000), 1..101).unwrap();
+ assert_eq!(
+ sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+ 100,
+ "Error while importing keys"
+ );
- let mut start_past_alias = None;
- let mut alias;
- while !imported_key_aliases.is_empty() {
- let key_descriptors =
- sl.keystore2.listEntriesBatched(Domain::APP, -1, start_past_alias).unwrap();
+ let mut start_past_alias = None;
+ let mut alias;
+ while !imported_key_aliases.is_empty() {
+ let key_descriptors =
+ sl.keystore2.listEntriesBatched(Domain::APP, -1, start_past_alias).unwrap();
- // Check retrieved key entries list is a subset of imported keys list.
- assert!(key_descriptors
- .iter()
- .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
+ // Check retrieved key entries list is a subset of imported keys list.
+ assert!(key_descriptors
+ .iter()
+ .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
- alias = key_descriptors.last().unwrap().alias.clone().unwrap();
- start_past_alias = Some(alias.as_ref());
- // Delete the listed key entries from imported keys list.
- key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
- assert!(imported_key_aliases.remove(&alias));
- });
- }
+ alias = key_descriptors.last().unwrap().alias.clone().unwrap();
+ start_past_alias = Some(alias.as_ref());
+ // Delete the listed key entries from imported keys list.
+ key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
+ assert!(imported_key_aliases.remove(&alias));
+ });
+ }
- assert!(imported_key_aliases.is_empty());
- delete_all_entries(&sl.keystore2);
- assert_eq!(
- sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
- 0,
- "Error while doing cleanup"
- );
- })
+ assert!(imported_key_aliases.is_empty());
+ delete_all_entries(&sl.keystore2);
+ assert_eq!(
+ sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+ 0,
+ "Error while doing cleanup"
+ );
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, import_keys_fn) };
}
/// Import keys from multiple processes with same user context and try to list the keystore entries
@@ -321,128 +312,127 @@
/// `startingPastAlias` as None. It should list all the keys imported in process-1 and process-2.
#[test]
fn keystore2_list_entries_batched_with_multi_procs_success() {
- static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
static ALIAS_PREFIX: &str = "key_test_batch_list";
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
- let sl = SecLevel::tee();
+ let import_keys_fn = || {
+ let sl = SecLevel::tee();
- // Make sure there are no keystore entries exist before adding new entries.
- delete_all_entries(&sl.keystore2);
+ // Make sure there are no keystore entries exist before adding new entries.
+ delete_all_entries(&sl.keystore2);
- // Import 3 keys with below aliases -
- // [key_test_batch_list_1, key_test_batch_list_2, key_test_batch_list_3]
- let imported_key_aliases =
- key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..4).unwrap();
- assert_eq!(
- sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
- 3,
- "Error while importing keys"
- );
+ // Import 3 keys with below aliases -
+ // [key_test_batch_list_1, key_test_batch_list_2, key_test_batch_list_3]
+ let imported_key_aliases =
+ key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..4).unwrap();
+ assert_eq!(
+ sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+ 3,
+ "Error while importing keys"
+ );
- // List all entries in keystore for this user-id.
- let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
- assert_eq!(key_descriptors.len(), 3);
+ // List all entries in keystore for this user-id.
+ let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
+ assert_eq!(key_descriptors.len(), 3);
- // Makes sure all listed aliases are matching with imported keys aliases.
- assert!(key_descriptors
- .iter()
- .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
- })
+ // Makes sure all listed aliases are matching with imported keys aliases.
+ assert!(key_descriptors
+ .iter()
+ .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
};
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
- let sl = SecLevel::tee();
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, import_keys_fn) };
- // Import another 5 keys with below aliases -
- // [ key_test_batch_list_4, key_test_batch_list_5, key_test_batch_list_6,
- // key_test_batch_list_7, key_test_batch_list_8 ]
- let mut imported_key_aliases =
- key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 4..9).unwrap();
+ let import_more_fn = || {
+ let sl = SecLevel::tee();
- // Above context already 3 keys are imported, in this context 5 keys are imported,
- // total 8 keystore entries are expected to be present in Keystore for this user-id.
- assert_eq!(
- sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
- 8,
- "Error while importing keys"
- );
+ // Import another 5 keys with below aliases -
+ // [ key_test_batch_list_4, key_test_batch_list_5, key_test_batch_list_6,
+ // key_test_batch_list_7, key_test_batch_list_8 ]
+ let mut imported_key_aliases =
+ key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 4..9).unwrap();
- // List keystore entries with `start_past_alias` as "key_test_batch_list_3".
- // `listEntriesBatched` should list all the keystore entries with
- // alias > "key_test_batch_list_3".
- let key_descriptors = sl
- .keystore2
- .listEntriesBatched(Domain::APP, -1, Some("key_test_batch_list_3"))
- .unwrap();
- assert_eq!(key_descriptors.len(), 5);
+ // Above context already 3 keys are imported, in this context 5 keys are imported,
+ // total 8 keystore entries are expected to be present in Keystore for this user-id.
+ assert_eq!(
+ sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+ 8,
+ "Error while importing keys"
+ );
- // Make sure above listed aliases are matching with imported keys aliases.
- assert!(key_descriptors
- .iter()
- .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
+ // List keystore entries with `start_past_alias` as "key_test_batch_list_3".
+ // `listEntriesBatched` should list all the keystore entries with
+ // alias > "key_test_batch_list_3".
+ let key_descriptors = sl
+ .keystore2
+ .listEntriesBatched(Domain::APP, -1, Some("key_test_batch_list_3"))
+ .unwrap();
+ assert_eq!(key_descriptors.len(), 5);
- // List all keystore entries with `start_past_alias` as `None`.
- // `listEntriesBatched` should list all the keystore entries.
- let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
- assert_eq!(key_descriptors.len(), 8);
+ // Make sure above listed aliases are matching with imported keys aliases.
+ assert!(key_descriptors
+ .iter()
+ .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
- // Include previously imported keys aliases as well
- imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_1");
- imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_2");
- imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_3");
+ // List all keystore entries with `start_past_alias` as `None`.
+ // `listEntriesBatched` should list all the keystore entries.
+ let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
+ assert_eq!(key_descriptors.len(), 8);
- // Make sure all the above listed aliases are matching with imported keys aliases.
- assert!(key_descriptors
- .iter()
- .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
+ // Include previously imported keys aliases as well
+ imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_1");
+ imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_2");
+ imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_3");
- delete_all_entries(&sl.keystore2);
- assert_eq!(
- sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
- 0,
- "Error while doing cleanup"
- );
- })
+ // Make sure all the above listed aliases are matching with imported keys aliases.
+ assert!(key_descriptors
+ .iter()
+ .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
+
+ delete_all_entries(&sl.keystore2);
+ assert_eq!(
+ sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+ 0,
+ "Error while doing cleanup"
+ );
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, import_more_fn) };
}
#[test]
fn keystore2_list_entries_batched_with_empty_keystore_success() {
- static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
- let keystore2 = get_keystore_service();
+ let list_keys_fn = || {
+ let keystore2 = get_keystore_service();
- // Make sure there are no keystore entries exist before adding new entries.
- delete_all_entries(&keystore2);
+ // Make sure there are no keystore entries exist before adding new entries.
+ delete_all_entries(&keystore2);
- // List all entries in keystore for this user-id, pass startingPastAlias = None
- let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
- assert_eq!(key_descriptors.len(), 0);
+ // List all entries in keystore for this user-id, pass startingPastAlias = None
+ let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
+ assert_eq!(key_descriptors.len(), 0);
- // List all entries in keystore for this user-id, pass startingPastAlias = <random value>
- let key_descriptors =
- keystore2.listEntriesBatched(Domain::APP, -1, Some("startingPastAlias")).unwrap();
- assert_eq!(key_descriptors.len(), 0);
- })
+ // List all entries in keystore for this user-id, pass startingPastAlias = <random value>
+ let key_descriptors =
+ keystore2.listEntriesBatched(Domain::APP, -1, Some("startingPastAlias")).unwrap();
+ assert_eq!(key_descriptors.len(), 0);
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, list_keys_fn) };
}
/// Import a key with SELINUX as domain, list aliases using `listEntriesBatched`.
@@ -502,140 +492,135 @@
#[test]
fn keystore2_list_entries_batched_validate_count_and_order_success() {
- static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
static ALIAS_PREFIX: &str = "key_test_batch_list";
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
- let sl = SecLevel::tee();
+ let list_keys_fn = || {
+ let sl = SecLevel::tee();
- // Make sure there are no keystore entries exist before adding new entries.
- delete_all_entries(&sl.keystore2);
+ // Make sure there are no keystore entries exist before adding new entries.
+ delete_all_entries(&sl.keystore2);
- // Import keys with below mentioned aliases -
- // [
- // key_test_batch_list_1,
- // key_test_batch_list_2,
- // key_test_batch_list_3,
- // key_test_batch_list_4,
- // key_test_batch_list_5,
- // key_test_batch_list_10,
- // key_test_batch_list_11,
- // key_test_batch_list_12,
- // key_test_batch_list_21,
- // key_test_batch_list_22,
- // ]
- let _imported_key_aliases =
- key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..6).unwrap();
- assert_eq!(
- sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
- 5,
- "Error while importing keys"
- );
- let _imported_key_aliases =
- key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 10..13).unwrap();
- assert_eq!(
- sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
- 8,
- "Error while importing keys"
- );
- let _imported_key_aliases =
- key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 21..23).unwrap();
- assert_eq!(
- sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
- 10,
- "Error while importing keys"
- );
+ // Import keys with below mentioned aliases -
+ // [
+ // key_test_batch_list_1,
+ // key_test_batch_list_2,
+ // key_test_batch_list_3,
+ // key_test_batch_list_4,
+ // key_test_batch_list_5,
+ // key_test_batch_list_10,
+ // key_test_batch_list_11,
+ // key_test_batch_list_12,
+ // key_test_batch_list_21,
+ // key_test_batch_list_22,
+ // ]
+ let _imported_key_aliases =
+ key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..6).unwrap();
+ assert_eq!(
+ sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+ 5,
+ "Error while importing keys"
+ );
+ let _imported_key_aliases =
+ key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 10..13).unwrap();
+ assert_eq!(
+ sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+ 8,
+ "Error while importing keys"
+ );
+ let _imported_key_aliases =
+ key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 21..23).unwrap();
+ assert_eq!(
+ sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+ 10,
+ "Error while importing keys"
+ );
- // List the aliases using given `startingPastAlias` and verify the listed
- // aliases with the expected list of aliases.
- verify_aliases(
- &sl.keystore2,
- Some(format!("{}{}", ALIAS_PREFIX, "_5").as_str()),
- vec![],
- );
+ // List the aliases using given `startingPastAlias` and verify the listed
+ // aliases with the expected list of aliases.
+ verify_aliases(&sl.keystore2, Some(format!("{}{}", ALIAS_PREFIX, "_5").as_str()), vec![]);
- verify_aliases(
- &sl.keystore2,
- Some(format!("{}{}", ALIAS_PREFIX, "_4").as_str()),
- vec![ALIAS_PREFIX.to_owned() + "_5"],
- );
+ verify_aliases(
+ &sl.keystore2,
+ Some(format!("{}{}", ALIAS_PREFIX, "_4").as_str()),
+ vec![ALIAS_PREFIX.to_owned() + "_5"],
+ );
- verify_aliases(
- &sl.keystore2,
- Some(format!("{}{}", ALIAS_PREFIX, "_3").as_str()),
- vec![ALIAS_PREFIX.to_owned() + "_4", ALIAS_PREFIX.to_owned() + "_5"],
- );
+ verify_aliases(
+ &sl.keystore2,
+ Some(format!("{}{}", ALIAS_PREFIX, "_3").as_str()),
+ vec![ALIAS_PREFIX.to_owned() + "_4", ALIAS_PREFIX.to_owned() + "_5"],
+ );
- verify_aliases(
- &sl.keystore2,
- Some(format!("{}{}", ALIAS_PREFIX, "_2").as_str()),
- vec![
- ALIAS_PREFIX.to_owned() + "_21",
- ALIAS_PREFIX.to_owned() + "_22",
- ALIAS_PREFIX.to_owned() + "_3",
- ALIAS_PREFIX.to_owned() + "_4",
- ALIAS_PREFIX.to_owned() + "_5",
- ],
- );
+ verify_aliases(
+ &sl.keystore2,
+ Some(format!("{}{}", ALIAS_PREFIX, "_2").as_str()),
+ vec![
+ ALIAS_PREFIX.to_owned() + "_21",
+ ALIAS_PREFIX.to_owned() + "_22",
+ ALIAS_PREFIX.to_owned() + "_3",
+ ALIAS_PREFIX.to_owned() + "_4",
+ ALIAS_PREFIX.to_owned() + "_5",
+ ],
+ );
- verify_aliases(
- &sl.keystore2,
- Some(format!("{}{}", ALIAS_PREFIX, "_1").as_str()),
- vec![
- ALIAS_PREFIX.to_owned() + "_10",
- ALIAS_PREFIX.to_owned() + "_11",
- ALIAS_PREFIX.to_owned() + "_12",
- ALIAS_PREFIX.to_owned() + "_2",
- ALIAS_PREFIX.to_owned() + "_21",
- ALIAS_PREFIX.to_owned() + "_22",
- ALIAS_PREFIX.to_owned() + "_3",
- ALIAS_PREFIX.to_owned() + "_4",
- ALIAS_PREFIX.to_owned() + "_5",
- ],
- );
+ verify_aliases(
+ &sl.keystore2,
+ Some(format!("{}{}", ALIAS_PREFIX, "_1").as_str()),
+ vec![
+ ALIAS_PREFIX.to_owned() + "_10",
+ ALIAS_PREFIX.to_owned() + "_11",
+ ALIAS_PREFIX.to_owned() + "_12",
+ ALIAS_PREFIX.to_owned() + "_2",
+ ALIAS_PREFIX.to_owned() + "_21",
+ ALIAS_PREFIX.to_owned() + "_22",
+ ALIAS_PREFIX.to_owned() + "_3",
+ ALIAS_PREFIX.to_owned() + "_4",
+ ALIAS_PREFIX.to_owned() + "_5",
+ ],
+ );
- verify_aliases(
- &sl.keystore2,
- Some(ALIAS_PREFIX),
- vec![
- ALIAS_PREFIX.to_owned() + "_1",
- ALIAS_PREFIX.to_owned() + "_10",
- ALIAS_PREFIX.to_owned() + "_11",
- ALIAS_PREFIX.to_owned() + "_12",
- ALIAS_PREFIX.to_owned() + "_2",
- ALIAS_PREFIX.to_owned() + "_21",
- ALIAS_PREFIX.to_owned() + "_22",
- ALIAS_PREFIX.to_owned() + "_3",
- ALIAS_PREFIX.to_owned() + "_4",
- ALIAS_PREFIX.to_owned() + "_5",
- ],
- );
+ verify_aliases(
+ &sl.keystore2,
+ Some(ALIAS_PREFIX),
+ vec![
+ ALIAS_PREFIX.to_owned() + "_1",
+ ALIAS_PREFIX.to_owned() + "_10",
+ ALIAS_PREFIX.to_owned() + "_11",
+ ALIAS_PREFIX.to_owned() + "_12",
+ ALIAS_PREFIX.to_owned() + "_2",
+ ALIAS_PREFIX.to_owned() + "_21",
+ ALIAS_PREFIX.to_owned() + "_22",
+ ALIAS_PREFIX.to_owned() + "_3",
+ ALIAS_PREFIX.to_owned() + "_4",
+ ALIAS_PREFIX.to_owned() + "_5",
+ ],
+ );
- verify_aliases(
- &sl.keystore2,
- None,
- vec![
- ALIAS_PREFIX.to_owned() + "_1",
- ALIAS_PREFIX.to_owned() + "_10",
- ALIAS_PREFIX.to_owned() + "_11",
- ALIAS_PREFIX.to_owned() + "_12",
- ALIAS_PREFIX.to_owned() + "_2",
- ALIAS_PREFIX.to_owned() + "_21",
- ALIAS_PREFIX.to_owned() + "_22",
- ALIAS_PREFIX.to_owned() + "_3",
- ALIAS_PREFIX.to_owned() + "_4",
- ALIAS_PREFIX.to_owned() + "_5",
- ],
- );
- })
+ verify_aliases(
+ &sl.keystore2,
+ None,
+ vec![
+ ALIAS_PREFIX.to_owned() + "_1",
+ ALIAS_PREFIX.to_owned() + "_10",
+ ALIAS_PREFIX.to_owned() + "_11",
+ ALIAS_PREFIX.to_owned() + "_12",
+ ALIAS_PREFIX.to_owned() + "_2",
+ ALIAS_PREFIX.to_owned() + "_21",
+ ALIAS_PREFIX.to_owned() + "_22",
+ ALIAS_PREFIX.to_owned() + "_3",
+ ALIAS_PREFIX.to_owned() + "_4",
+ ALIAS_PREFIX.to_owned() + "_5",
+ ],
+ );
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, list_keys_fn) };
}
/// Try to list the key entries with domain SELINUX from user context where user doesn't possesses
@@ -645,22 +630,21 @@
fn keystore2_list_entries_batched_fails_perm_denied() {
let auid = 91 * AID_USER_OFFSET + 10001;
let agid = 91 * AID_USER_OFFSET + 10001;
- static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ let list_keys_fn = move || {
+ let keystore2 = get_keystore_service();
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
- let keystore2 = get_keystore_service();
-
- let result = key_generations::map_ks_error(keystore2.listEntriesBatched(
- Domain::SELINUX,
- key_generations::SELINUX_SHELL_NAMESPACE,
- None,
- ));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
- })
+ let result = key_generations::map_ks_error(keystore2.listEntriesBatched(
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ None,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(auid, agid, list_keys_fn) };
}
/// Try to list key entries with domain BLOB. Test should fail with error response code
@@ -685,21 +669,19 @@
fn keystore2_get_number_of_entries_fails_perm_denied() {
let auid = 91 * AID_USER_OFFSET + 10001;
let agid = 91 * AID_USER_OFFSET + 10001;
- static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ let get_num_fn = move || {
+ let keystore2 = get_keystore_service();
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
- let keystore2 = get_keystore_service();
-
- let result = key_generations::map_ks_error(
- keystore2
- .getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
- );
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
- })
+ let result = key_generations::map_ks_error(
+ keystore2.getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
+ );
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(auid, agid, get_num_fn) };
}
/// Try to get number of key entries with domain BLOB. Test should fail with error response code
diff --git a/keystore2/tests/keystore2_client_operation_tests.rs b/keystore2/tests/keystore2_client_operation_tests.rs
index 95888a6..1f8396e 100644
--- a/keystore2/tests/keystore2_client_operation_tests.rs
+++ b/keystore2/tests/keystore2_client_operation_tests.rs
@@ -28,6 +28,10 @@
};
use nix::unistd::{getuid, Gid, Uid};
use rustutils::users::AID_USER_OFFSET;
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+};
use std::thread;
use std::thread::JoinHandle;
@@ -36,7 +40,8 @@
///
/// # Safety
///
-/// Must be called from a process with no other threads.
+/// Must only be called from a single-threaded process (e.g. as enforced by `AndroidTest.xml`
+/// setting `--test-threads=1`).
pub unsafe fn create_operations(
target_ctx: &'static str,
forced_op: ForcedOp,
@@ -46,7 +51,7 @@
let base_gid = 99 * AID_USER_OFFSET + 10001;
let base_uid = 99 * AID_USER_OFFSET + 10001;
(0..max_ops)
- // SAFETY: The caller guarantees that there are no other threads.
+ // Safety: The caller guarantees that there are no other threads.
.map(|i| unsafe {
execute_op_run_as_child(
target_ctx,
@@ -89,7 +94,8 @@
const MAX_OPS: i32 = 100;
static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
- // SAFETY: The test is run in a separate process with no other threads.
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
let mut child_handles = unsafe { create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS) };
// Wait until all child procs notifies us to continue,
@@ -123,7 +129,8 @@
static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
// Create regular operations.
- // SAFETY: The test is run in a separate process with no other threads.
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
let mut child_handles = unsafe { create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS) };
// Wait until all child procs notifies us to continue, so that there are enough
@@ -135,28 +142,31 @@
// Create a forced operation.
let auid = 99 * AID_USER_OFFSET + 10604;
let agid = 99 * AID_USER_OFFSET + 10604;
- // SAFETY: The test is run in a separate process with no other threads.
+ let force_op_fn = move || {
+ let alias = format!("ks_prune_forced_op_key_{}", getuid());
+
+ // To make room for this forced op, system should be able to prune one of the
+ // above created regular operations and create a slot for this forced operation
+ // successfully.
+ create_signing_operation(
+ ForcedOp(true),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::SELINUX,
+ 100,
+ Some(alias),
+ )
+ .expect("Client failed to create forced operation after BACKEND_BUSY state.");
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
unsafe {
run_as::run_as(
key_generations::TARGET_VOLD_CTX,
Uid::from_raw(auid),
Gid::from_raw(agid),
- move || {
- let alias = format!("ks_prune_forced_op_key_{}", getuid());
-
- // To make room for this forced op, system should be able to prune one of the
- // above created regular operations and create a slot for this forced operation
- // successfully.
- create_signing_operation(
- ForcedOp(true),
- KeyPurpose::SIGN,
- Digest::SHA_2_256,
- Domain::SELINUX,
- 100,
- Some(alias),
- )
- .expect("Client failed to create forced operation after BACKEND_BUSY state.");
- },
+ force_op_fn,
);
};
@@ -208,7 +218,9 @@
// Create initial forced operation in a child process
// and wait for the parent to notify to perform operation.
let alias = format!("ks_forced_op_key_{}", getuid());
- // SAFETY: The test is run in a separate process with no other threads.
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
let mut first_op_handle = unsafe {
execute_op_run_as_child(
key_generations::TARGET_SU_CTX,
@@ -227,7 +239,8 @@
// Create MAX_OPS number of forced operations.
let mut child_handles =
- // SAFETY: The test is run in a separate process with no other threads.
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
unsafe { create_operations(key_generations::TARGET_SU_CTX, ForcedOp(true), MAX_OPS) };
// Wait until all child procs notifies us to continue, so that there are enough operations
@@ -291,7 +304,9 @@
// Create an operation in an untrusted_app context. Wait until the parent notifies to continue.
// Once the parent notifies, this operation is expected to be completed successfully.
let alias = format!("ks_reg_op_key_{}", getuid());
- // SAFETY: The test is run in a separate process with no other threads.
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
let mut child_handle = unsafe {
execute_op_run_as_child(
TARGET_CTX,
@@ -376,6 +391,7 @@
/// - untrusted_app
/// - system_server
/// - priv_app
+///
/// `PERMISSION_DENIED` error response is expected.
#[test]
fn keystore2_forced_op_perm_denied_test() {
@@ -388,21 +404,24 @@
let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
for context in TARGET_CTXS.iter() {
- // SAFETY: The test is run in a separate process with no other threads.
+ let forced_op_fn = move || {
+ let alias = format!("ks_app_forced_op_test_key_{}", getuid());
+ let result = key_generations::map_ks_error(create_signing_operation(
+ ForcedOp(true),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some(alias),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
unsafe {
- run_as::run_as(context, Uid::from_raw(uid), Gid::from_raw(gid), move || {
- let alias = format!("ks_app_forced_op_test_key_{}", getuid());
- let result = key_generations::map_ks_error(create_signing_operation(
- ForcedOp(true),
- KeyPurpose::SIGN,
- Digest::SHA_2_256,
- Domain::APP,
- -1,
- Some(alias),
- ));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
- });
+ run_as::run_as(context, Uid::from_raw(uid), Gid::from_raw(gid), forced_op_fn);
}
}
}
@@ -411,27 +430,29 @@
/// Should be able to create forced operation with `vold` context successfully.
#[test]
fn keystore2_forced_op_success_test() {
- static TARGET_CTX: &str = "u:r:vold:s0";
+ static TARGET_VOLD_CTX: &str = "u:r:vold:s0";
const USER_ID: u32 = 99;
const APPLICATION_ID: u32 = 10601;
let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ let forced_op_fn = move || {
+ let alias = format!("ks_vold_forced_op_key_{}", getuid());
+ create_signing_operation(
+ ForcedOp(true),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::SELINUX,
+ key_generations::SELINUX_VOLD_NAMESPACE,
+ Some(alias),
+ )
+ .expect("Client with vold context failed to create forced operation.");
+ };
- // SAFETY: The test is run in a separate process with no other threads.
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
unsafe {
- run_as::run_as(TARGET_CTX, Uid::from_raw(uid), Gid::from_raw(gid), move || {
- let alias = format!("ks_vold_forced_op_key_{}", getuid());
- create_signing_operation(
- ForcedOp(true),
- KeyPurpose::SIGN,
- Digest::SHA_2_256,
- Domain::SELINUX,
- key_generations::SELINUX_VOLD_NAMESPACE,
- Some(alias),
- )
- .expect("Client with vold context failed to create forced operation.");
- });
+ run_as::run_as(TARGET_VOLD_CTX, Uid::from_raw(uid), Gid::from_raw(gid), forced_op_fn);
}
}
@@ -460,3 +481,120 @@
assert!(result1 || result2);
}
+
+/// Create an operation and use it for performing sign operation. After completing the operation
+/// try to abort the operation. Test should fail to abort already finalized operation with error
+/// code `INVALID_OPERATION_HANDLE`.
+#[test]
+fn keystore2_abort_finalized_op_fail_test() {
+ let op_response = create_signing_operation(
+ ForcedOp(false),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some("ks_op_abort_fail_test_key".to_string()),
+ )
+ .unwrap();
+
+ let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+ perform_sample_sign_operation(&op).unwrap();
+ let result = key_generations::map_ks_error(op.abort());
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE), result.unwrap_err());
+}
+
+/// Create an operation and use it for performing sign operation. Before finishing the operation
+/// try to abort the operation. Test should successfully abort the operation. After aborting try to
+/// use the operation handle, test should fail to use already aborted operation handle with error
+/// code `INVALID_OPERATION_HANDLE`.
+#[test]
+fn keystore2_op_abort_success_test() {
+ let op_response = create_signing_operation(
+ ForcedOp(false),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some("ks_op_abort_success_key".to_string()),
+ )
+ .unwrap();
+
+ let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+ op.update(b"my message").unwrap();
+ let result = key_generations::map_ks_error(op.abort());
+ assert!(result.is_ok());
+
+ // Try to use the op handle after abort.
+ let result = key_generations::map_ks_error(op.finish(None, None));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE), result.unwrap_err());
+}
+
+/// Executes an operation in a thread. Performs an `update` operation repeatedly till the user
+/// interrupts it or encounters any error other than `OPERATION_BUSY`.
+/// Return `false` in case of any error other than `OPERATION_BUSY`, otherwise it returns true.
+fn perform_abort_op_busy_in_thread(
+ op: binder::Strong<dyn IKeystoreOperation>,
+ should_exit_clone: Arc<AtomicBool>,
+) -> JoinHandle<bool> {
+ thread::spawn(move || {
+ loop {
+ if should_exit_clone.load(Ordering::Relaxed) {
+ // Caller requested to exit the thread.
+ return true;
+ }
+
+ match key_generations::map_ks_error(op.update(b"my message")) {
+ Ok(_) => continue,
+ Err(Error::Rc(ResponseCode::OPERATION_BUSY)) => continue,
+ Err(_) => return false,
+ }
+ }
+ })
+}
+
+/// Create an operation and try to use same operation handle in multiple threads to perform
+/// operations. Test tries to abort the operation and expects `abort` call to fail with the error
+/// response `OPERATION_BUSY` as multiple threads try to access the same operation handle
+/// simultaneously. Test tries to simulate `OPERATION_BUSY` error response from `abort` api.
+#[test]
+fn keystore2_op_abort_fails_with_operation_busy_error_test() {
+ loop {
+ let op_response = create_signing_operation(
+ ForcedOp(false),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some("op_abort_busy_alias_test_key".to_string()),
+ )
+ .unwrap();
+ let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+
+ let should_exit = Arc::new(AtomicBool::new(false));
+
+ let update_t_handle1 = perform_abort_op_busy_in_thread(op.clone(), should_exit.clone());
+ let update_t_handle2 = perform_abort_op_busy_in_thread(op.clone(), should_exit.clone());
+
+ // Attempt to abort the operation and anticipate an 'OPERATION_BUSY' error, as multiple
+ // threads are concurrently accessing the same operation handle.
+ let result = match op.abort() {
+ Ok(_) => 0, // Operation successfully aborted.
+ Err(e) => e.service_specific_error(),
+ };
+
+ // Notify threads to stop performing `update` operation.
+ should_exit.store(true, Ordering::Relaxed);
+
+ let _update_op_result = update_t_handle1.join().unwrap();
+ let _update_op_result2 = update_t_handle2.join().unwrap();
+
+ if result == ResponseCode::OPERATION_BUSY.0 {
+ // The abort call failed with an OPERATION_BUSY error, as anticipated, due to multiple
+ // threads competing for access to the same operation handle.
+ return;
+ }
+ assert_eq!(result, 0);
+ }
+}
diff --git a/keystore2/tests/keystore2_client_rsa_key_tests.rs b/keystore2/tests/keystore2_client_rsa_key_tests.rs
index 1559008..cb8729f 100644
--- a/keystore2/tests/keystore2_client_rsa_key_tests.rs
+++ b/keystore2/tests/keystore2_client_rsa_key_tests.rs
@@ -78,9 +78,12 @@
key_params: &key_generations::KeyParams,
op_purpose: KeyPurpose,
forced_op: ForcedOp,
-) -> binder::Result<CreateOperationResponse> {
- let key_metadata =
- key_generations::generate_rsa_key(sl, domain, nspace, alias, key_params, None)?;
+) -> binder::Result<Option<CreateOperationResponse>> {
+ let Some(key_metadata) =
+ key_generations::generate_rsa_key(sl, domain, nspace, alias, key_params, None)?
+ else {
+ return Ok(None);
+ };
let mut op_params = authorizations::AuthSetBuilder::new().purpose(op_purpose);
@@ -97,7 +100,7 @@
op_params = op_params.block_mode(value)
}
- sl.binder.createOperation(&key_metadata.key, &op_params, forced_op.0)
+ sl.binder.createOperation(&key_metadata.key, &op_params, forced_op.0).map(Some)
}
/// Generate RSA signing key with given parameters and perform signing operation.
@@ -109,7 +112,7 @@
) {
let sl = SecLevel::tee();
- let op_response = create_rsa_key_and_operation(
+ let Some(op_response) = create_rsa_key_and_operation(
&sl,
Domain::APP,
-1,
@@ -126,7 +129,9 @@
KeyPurpose::SIGN,
ForcedOp(false),
)
- .expect("Failed to create an operation.");
+ .expect("Failed to create an operation.") else {
+ return;
+ };
assert!(op_response.iOperation.is_some());
assert_eq!(
diff --git a/keystore2/tests/keystore2_client_test_utils.rs b/keystore2/tests/keystore2_client_test_utils.rs
index d4ca2ae..1bbdc91 100644
--- a/keystore2/tests/keystore2_client_test_utils.rs
+++ b/keystore2/tests/keystore2_client_test_utils.rs
@@ -25,7 +25,11 @@
};
use binder::wait_for_interface;
use keystore2_test_utils::{
- authorizations, key_generations, key_generations::Error, run_as, SecLevel,
+ authorizations, key_generations,
+ key_generations::Error,
+ run_as,
+ run_as::{ChannelReader, ChannelWriter},
+ SecLevel,
};
use nix::unistd::{Gid, Uid};
use openssl::bn::BigNum;
@@ -40,7 +44,6 @@
use openssl::x509::X509;
use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
use serde::{Deserialize, Serialize};
-use std::path::PathBuf;
use std::process::{Command, Output};
/// This enum is used to communicate between parent and child processes.
@@ -52,10 +55,21 @@
OtherErr,
}
-/// This is used to notify the child or parent process that the expected state is reched.
+/// This is used to notify the child or parent process that the expected state is reached.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BarrierReached;
+/// This is used to notify the child or parent process that the expected state is reached,
+/// passing a value
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub struct BarrierReachedWithData<T: Send + Sync>(pub T);
+
+impl<T: Send + Sync> BarrierReachedWithData<T> {
+ pub fn new(val: T) -> Self {
+ Self(val)
+ }
+}
+
/// Forced operation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ForcedOp(pub bool);
@@ -100,10 +114,7 @@
// (ro.product.*_for_attestation) reading logic would not be available for such devices
// hence skipping this test for such scenario.
- // This file is only present on GSI builds.
- let gsi_marker = PathBuf::from("/system/system_ext/etc/init/init.gsi.rc");
-
- get_vsr_api_level() < 34 && gsi_marker.as_path().is_file()
+ get_vsr_api_level() < 34 && key_generations::is_gsi()
}
#[macro_export]
@@ -298,7 +309,8 @@
///
/// # Safety
///
-/// Must only be called from a single-threaded process.
+/// Must only be called from a single-threaded process (e.g. as enforced by `AndroidTest.xml`
+/// setting `--test-threads=1`).
pub unsafe fn execute_op_run_as_child(
target_ctx: &'static str,
domain: Domain,
@@ -308,41 +320,44 @@
agid: Gid,
forced_op: ForcedOp,
) -> run_as::ChildHandle<TestOutcome, BarrierReached> {
- // SAFETY: The caller guarantees that there are no other threads.
- unsafe {
- run_as::run_as_child(target_ctx, auid, agid, move |reader, writer| {
- let result = key_generations::map_ks_error(create_signing_operation(
- forced_op,
- KeyPurpose::SIGN,
- Digest::SHA_2_256,
- domain,
- nspace,
- alias,
- ));
+ let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
+ writer: &mut ChannelWriter<BarrierReached>| {
+ let result = key_generations::map_ks_error(create_signing_operation(
+ forced_op,
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ domain,
+ nspace,
+ alias,
+ ));
- // Let the parent know that an operation has been started, then
- // wait until the parent notifies us to continue, so the operation
- // remains open.
- writer.send(&BarrierReached {});
- reader.recv();
+ // Let the parent know that an operation has been started, then
+ // wait until the parent notifies us to continue, so the operation
+ // remains open.
+ writer.send(&BarrierReached {});
+ reader.recv();
- // Continue performing the operation after parent notifies.
- match &result {
- Ok(CreateOperationResponse { iOperation: Some(op), .. }) => {
- match key_generations::map_ks_error(perform_sample_sign_operation(op)) {
- Ok(()) => TestOutcome::Ok,
- Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {
- TestOutcome::InvalidHandle
- }
- Err(e) => panic!("Error in performing op: {:#?}", e),
+ // Continue performing the operation after parent notifies.
+ match &result {
+ Ok(CreateOperationResponse { iOperation: Some(op), .. }) => {
+ match key_generations::map_ks_error(perform_sample_sign_operation(op)) {
+ Ok(()) => TestOutcome::Ok,
+ Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {
+ TestOutcome::InvalidHandle
}
+ Err(e) => panic!("Error in performing op: {:#?}", e),
}
- Ok(_) => TestOutcome::OtherErr,
- Err(Error::Rc(ResponseCode::BACKEND_BUSY)) => TestOutcome::BackendBusy,
- _ => TestOutcome::OtherErr,
}
- })
- .expect("Failed to create an operation.")
+ Ok(_) => TestOutcome::OtherErr,
+ Err(Error::Rc(ResponseCode::BACKEND_BUSY)) => TestOutcome::BackendBusy,
+ _ => TestOutcome::OtherErr,
+ }
+ };
+
+ // Safety: The caller guarantees that there are no other threads.
+ unsafe {
+ run_as::run_as_child(target_ctx, auid, agid, child_fn)
+ .expect("Failed to create an operation.")
}
}
@@ -503,9 +518,7 @@
// then returns an empty byte vector.
pub fn get_system_prop(name: &str) -> Vec<u8> {
match rustutils::system_properties::read(name) {
- Ok(Some(value)) => {
- return value.as_bytes().to_vec();
- }
+ Ok(Some(value)) => value.as_bytes().to_vec(),
_ => {
vec![]
}
diff --git a/keystore2/tests/keystore2_client_update_subcomponent_tests.rs b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs
index e25e52a..0e38298 100644
--- a/keystore2/tests/keystore2_client_update_subcomponent_tests.rs
+++ b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs
@@ -22,7 +22,7 @@
use keystore2_test_utils::{
get_keystore_service, key_generations, key_generations::Error, run_as, SecLevel,
};
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
use rustutils::users::AID_USER_OFFSET;
/// Generate a key and update its public certificate and certificate chain. Test should be able to
@@ -153,9 +153,6 @@
/// permissions, test should be able to update public certificate and cert-chain successfully.
#[test]
fn keystore2_update_subcomponent_fails_permission_denied() {
- static GRANTOR_SU_CTX: &str = "u:r:su:s0";
- static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
const USER_ID_1: u32 = 99;
const APPLICATION_ID: u32 = 10001;
static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID;
@@ -166,117 +163,102 @@
static GRANTEE_2_GID: u32 = GRANTEE_2_UID;
// Generate a key and grant it to multiple users with different access permissions.
- // SAFETY: The test is run in a separate process with no other threads.
- let mut granted_keys = unsafe {
- run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- let sl = SecLevel::tee();
- let alias = format!("ks_update_subcompo_test_1_{}", getuid());
- let mut granted_keys = Vec::new();
+ let grantor_fn = || {
+ let sl = SecLevel::tee();
+ let alias = format!("ks_update_subcompo_test_1_{}", getuid());
+ let mut granted_keys = Vec::new();
- let key_metadata = key_generations::generate_ec_p256_signing_key(
- &sl,
- Domain::APP,
- -1,
- Some(alias),
- None,
- )
+ let key_metadata =
+ key_generations::generate_ec_p256_signing_key(&sl, Domain::APP, -1, Some(alias), None)
+ .unwrap();
+
+ // Grant a key without update permission.
+ let access_vector = KeyPermission::GET_INFO.0;
+ let granted_key = sl
+ .keystore2
+ .grant(&key_metadata.key, GRANTEE_1_UID.try_into().unwrap(), access_vector)
.unwrap();
+ assert_eq!(granted_key.domain, Domain::GRANT);
+ granted_keys.push(granted_key.nspace);
- // Grant a key without update permission.
- let access_vector = KeyPermission::GET_INFO.0;
- let granted_key = sl
- .keystore2
- .grant(&key_metadata.key, GRANTEE_1_UID.try_into().unwrap(), access_vector)
- .unwrap();
- assert_eq!(granted_key.domain, Domain::GRANT);
- granted_keys.push(granted_key.nspace);
+ // Grant a key with update permission.
+ let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::UPDATE.0;
+ let granted_key = sl
+ .keystore2
+ .grant(&key_metadata.key, GRANTEE_2_UID.try_into().unwrap(), access_vector)
+ .unwrap();
+ assert_eq!(granted_key.domain, Domain::GRANT);
+ granted_keys.push(granted_key.nspace);
- // Grant a key with update permission.
- let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::UPDATE.0;
- let granted_key = sl
- .keystore2
- .grant(&key_metadata.key, GRANTEE_2_UID.try_into().unwrap(), access_vector)
- .unwrap();
- assert_eq!(granted_key.domain, Domain::GRANT);
- granted_keys.push(granted_key.nspace);
-
- granted_keys
- })
+ granted_keys
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let mut granted_keys = unsafe { run_as::run_as_root(grantor_fn) };
+
// Grantee context, try to update the key public certs, permission denied error is expected.
let granted_key1_nspace = granted_keys.remove(0);
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_1_UID),
- Gid::from_raw(GRANTEE_1_GID),
- move || {
- let keystore2 = get_keystore_service();
+ let grantee1_fn = move || {
+ let keystore2 = get_keystore_service();
- let other_cert: [u8; 32] = [123; 32];
- let other_cert_chain: [u8; 32] = [12; 32];
+ let other_cert: [u8; 32] = [123; 32];
+ let other_cert_chain: [u8; 32] = [12; 32];
- let result = key_generations::map_ks_error(keystore2.updateSubcomponent(
- &KeyDescriptor {
- domain: Domain::GRANT,
- nspace: granted_key1_nspace,
- alias: None,
- blob: None,
- },
- Some(&other_cert),
- Some(&other_cert_chain),
- ));
- assert!(result.is_err());
- assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ let result = key_generations::map_ks_error(keystore2.updateSubcomponent(
+ &KeyDescriptor {
+ domain: Domain::GRANT,
+ nspace: granted_key1_nspace,
+ alias: None,
+ blob: None,
},
- )
+ Some(&other_cert),
+ Some(&other_cert_chain),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_1_UID, GRANTEE_1_GID, grantee1_fn) };
+
// Grantee context, update granted key public certs. Update should happen successfully.
let granted_key2_nspace = granted_keys.remove(0);
- // SAFETY: The test is run in a separate process with no other threads.
- unsafe {
- run_as::run_as(
- GRANTEE_CTX,
- Uid::from_raw(GRANTEE_2_UID),
- Gid::from_raw(GRANTEE_2_GID),
- move || {
- let keystore2 = get_keystore_service();
+ let grantee2_fn = move || {
+ let keystore2 = get_keystore_service();
- let other_cert: [u8; 32] = [124; 32];
- let other_cert_chain: [u8; 32] = [13; 32];
+ let other_cert: [u8; 32] = [124; 32];
+ let other_cert_chain: [u8; 32] = [13; 32];
- keystore2
- .updateSubcomponent(
- &KeyDescriptor {
- domain: Domain::GRANT,
- nspace: granted_key2_nspace,
- alias: None,
- blob: None,
- },
- Some(&other_cert),
- Some(&other_cert_chain),
- )
- .expect("updateSubcomponent should have succeeded.");
+ keystore2
+ .updateSubcomponent(
+ &KeyDescriptor {
+ domain: Domain::GRANT,
+ nspace: granted_key2_nspace,
+ alias: None,
+ blob: None,
+ },
+ Some(&other_cert),
+ Some(&other_cert_chain),
+ )
+ .expect("updateSubcomponent should have succeeded.");
- let key_entry_response = keystore2
- .getKeyEntry(&KeyDescriptor {
- domain: Domain::GRANT,
- nspace: granted_key2_nspace,
- alias: None,
- blob: None,
- })
- .unwrap();
- assert_eq!(Some(other_cert.to_vec()), key_entry_response.metadata.certificate);
- assert_eq!(
- Some(other_cert_chain.to_vec()),
- key_entry_response.metadata.certificateChain
- );
- },
- )
+ let key_entry_response = keystore2
+ .getKeyEntry(&KeyDescriptor {
+ domain: Domain::GRANT,
+ nspace: granted_key2_nspace,
+ alias: None,
+ blob: None,
+ })
+ .unwrap();
+ assert_eq!(Some(other_cert.to_vec()), key_entry_response.metadata.certificate);
+ assert_eq!(Some(other_cert_chain.to_vec()), key_entry_response.metadata.certificateChain);
};
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(GRANTEE_2_UID, GRANTEE_2_GID, grantee2_fn) };
}
#[test]
diff --git a/keystore2/tests/legacy_blobs/Android.bp b/keystore2/tests/legacy_blobs/Android.bp
index 0f310f5..92d1307 100644
--- a/keystore2/tests/legacy_blobs/Android.bp
+++ b/keystore2/tests/legacy_blobs/Android.bp
@@ -38,7 +38,6 @@
"libkeystore2_crypto_rust",
"libkeystore2_test_utils",
"libkeystore2_with_test_utils",
- "liblazy_static",
"liblibc",
"libnix",
"librustutils",
diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
index 11a4c0b..d71f463 100644
--- a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
+++ b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
@@ -27,7 +27,7 @@
use keystore2::utils::AesGcm;
use keystore2_crypto::{Password, ZVec};
use keystore2_test_utils::{get_keystore_service, key_generations, run_as, SecLevel};
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
use rustutils::users::AID_USER_OFFSET;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
@@ -128,8 +128,6 @@
fn keystore2_encrypted_characteristics() -> anyhow::Result<()> {
let auid = 99 * AID_USER_OFFSET + 10001;
let agid = 99 * AID_USER_OFFSET + 10001;
- static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
- static TARGET_SU_CTX: &str = "u:r:su:s0";
// Cleanup user directory if it exists
let path_buf = PathBuf::from("/data/misc/keystore/user_99");
@@ -137,205 +135,202 @@
std::fs::remove_dir_all(path_buf.as_path()).unwrap();
}
- // Safety: run_as must be called from a single threaded process.
- // This device test is run as a separate single threaded process.
- let mut gen_key_result = unsafe {
- run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- // Remove user if already exist.
- let maint_service = get_maintenance();
- match maint_service.onUserRemoved(99) {
- Ok(_) => {
- println!("User was existed, deleted successfully");
- }
- Err(e) => {
- println!("onUserRemoved error: {:#?}", e);
- }
+ let gen_key_fn = || {
+ // Remove user if already exist.
+ let maint_service = get_maintenance();
+ match maint_service.onUserRemoved(99) {
+ Ok(_) => {
+ println!("User did exist, deleted successfully");
}
- let sl = SecLevel::tee();
+ Err(e) => {
+ println!("onUserRemoved error: {:#?}", e);
+ }
+ }
+ let sl = SecLevel::tee();
- // Generate Key BLOB and prepare legacy keystore blob files.
- let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") };
- let key_metadata = key_generations::generate_ec_p256_signing_key(
- &sl,
- Domain::BLOB,
- SELINUX_SHELL_NAMESPACE,
- None,
- att_challenge,
+ // Generate Key BLOB and prepare legacy keystore blob files.
+ let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") };
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sl,
+ Domain::BLOB,
+ SELINUX_SHELL_NAMESPACE,
+ None,
+ att_challenge,
+ )
+ .expect("Failed to generate key blob");
+
+ // Create keystore file layout for user_99.
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
+ let super_key =
+ TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap());
+
+ let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
+ if !path_buf.as_path().is_dir() {
+ std::fs::create_dir(path_buf.as_path()).unwrap();
+ }
+ path_buf.push(".masterkey");
+ if !path_buf.as_path().is_file() {
+ std::fs::write(path_buf.as_path(), SUPERKEY).unwrap();
+ }
+
+ let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
+ path_buf.push("9910001_USRPKEY_authbound");
+ if !path_buf.as_path().is_file() {
+ make_encrypted_key_file(
+ path_buf.as_path(),
+ &super_key,
+ &key_metadata.key.blob.unwrap(),
)
- .expect("Failed to generate key blob");
+ .unwrap();
+ }
- // Create keystore file layout for user_99.
- let pw: Password = PASSWORD.into();
- let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
- let super_key =
- TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap());
-
- let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
- if !path_buf.as_path().is_dir() {
- std::fs::create_dir(path_buf.as_path()).unwrap();
- }
- path_buf.push(".masterkey");
- if !path_buf.as_path().is_file() {
- std::fs::write(path_buf.as_path(), SUPERKEY).unwrap();
- }
-
- let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
- path_buf.push("9910001_USRPKEY_authbound");
- if !path_buf.as_path().is_file() {
- make_encrypted_key_file(
- path_buf.as_path(),
- &super_key,
- &key_metadata.key.blob.unwrap(),
- )
+ let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
+ path_buf.push(".9910001_chr_USRPKEY_authbound");
+ if !path_buf.as_path().is_file() {
+ make_encrypted_characteristics_file(path_buf.as_path(), &super_key, KEY_PARAMETERS)
.unwrap();
- }
+ }
+ let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
+ path_buf.push("9910001_USRCERT_authbound");
+ if !path_buf.as_path().is_file() {
+ make_cert_blob_file(path_buf.as_path(), key_metadata.certificate.as_ref().unwrap())
+ .unwrap();
+ }
+
+ if let Some(chain) = key_metadata.certificateChain.as_ref() {
let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
- path_buf.push(".9910001_chr_USRPKEY_authbound");
+ path_buf.push("9910001_CACERT_authbound");
if !path_buf.as_path().is_file() {
- make_encrypted_characteristics_file(path_buf.as_path(), &super_key, KEY_PARAMETERS)
- .unwrap();
+ make_cert_blob_file(path_buf.as_path(), chain).unwrap();
}
+ }
- let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
- path_buf.push("9910001_USRCERT_authbound");
- if !path_buf.as_path().is_file() {
- make_cert_blob_file(path_buf.as_path(), key_metadata.certificate.as_ref().unwrap())
- .unwrap();
+ // Keystore2 disables the legacy importer when it finds the legacy database empty.
+ // However, if the device boots with an empty legacy database, the optimization kicks in
+ // and keystore2 never checks the legacy file system layout.
+ // So, restart keystore2 service to detect populated legacy database.
+ keystore2_restart_service();
+
+ let auth_service = get_authorization();
+ match auth_service.onDeviceUnlocked(99, Some(PASSWORD)) {
+ Ok(result) => {
+ println!("Unlock Result: {:?}", result);
}
-
- if let Some(chain) = key_metadata.certificateChain.as_ref() {
- let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
- path_buf.push("9910001_CACERT_authbound");
- if !path_buf.as_path().is_file() {
- make_cert_blob_file(path_buf.as_path(), chain).unwrap();
- }
+ Err(e) => {
+ panic!("Unlock should have succeeded: {:?}", e);
}
+ }
- // Keystore2 disables the legacy importer when it finds the legacy database empty.
- // However, if the device boots with an empty legacy database, the optimization kicks in
- // and keystore2 never checks the legacy file system layout.
- // So, restart keystore2 service to detect populated legacy database.
- keystore2_restart_service();
+ let mut key_params: Vec<KsKeyparameter> = Vec::new();
+ for param in key_metadata.authorizations {
+ let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
+ key_params.push(key_param);
+ }
- let auth_service = get_authorization();
- match auth_service.onDeviceUnlocked(99, Some(PASSWORD)) {
- Ok(result) => {
- println!("Unlock Result: {:?}", result);
- }
- Err(e) => {
- panic!("Unlock should have succeeded: {:?}", e);
- }
- }
-
- let mut key_params: Vec<KsKeyparameter> = Vec::new();
- for param in key_metadata.authorizations {
- let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
- key_params.push(key_param);
- }
-
- KeygenResult {
- cert: key_metadata.certificate.unwrap(),
- cert_chain: key_metadata.certificateChain.unwrap_or_default(),
- key_parameters: key_params,
- }
- })
+ KeygenResult {
+ cert: key_metadata.certificate.unwrap(),
+ cert_chain: key_metadata.certificateChain.unwrap_or_default(),
+ key_parameters: key_params,
+ }
};
- // Safety: run_as must be called from a single threaded process.
- // This device test is run as a separate single threaded process.
- unsafe {
- run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
- println!("UID: {}", getuid());
- println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9910001));
- println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9910001));
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let mut gen_key_result = unsafe { run_as::run_as_root(gen_key_fn) };
- let test_alias = "authbound";
- let keystore2 = get_keystore_service();
+ let use_key_fn = move || {
+ println!("UID: {}", getuid());
+ println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9910001));
+ println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9910001));
- match keystore2.getKeyEntry(&KeyDescriptor {
- domain: Domain::APP,
- nspace: SELINUX_SHELL_NAMESPACE,
- alias: Some(test_alias.to_string()),
- blob: None,
- }) {
- Ok(key_entry_response) => {
- assert_eq!(
- key_entry_response.metadata.certificate.unwrap(),
- gen_key_result.cert
- );
- assert_eq!(
- key_entry_response.metadata.certificateChain.unwrap_or_default(),
- gen_key_result.cert_chain
- );
- assert_eq!(key_entry_response.metadata.key.domain, Domain::KEY_ID);
- assert_ne!(key_entry_response.metadata.key.nspace, 0);
- assert_eq!(
- key_entry_response.metadata.keySecurityLevel,
- SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT
- );
+ let test_alias = "authbound";
+ let keystore2 = get_keystore_service();
- // Preapare KsKeyParameter list from getKeEntry response Authorizations.
- let mut key_params: Vec<KsKeyparameter> = Vec::new();
- for param in key_entry_response.metadata.authorizations {
- let key_param =
- KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
- key_params.push(key_param);
- }
+ match keystore2.getKeyEntry(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: SELINUX_SHELL_NAMESPACE,
+ alias: Some(test_alias.to_string()),
+ blob: None,
+ }) {
+ Ok(key_entry_response) => {
+ assert_eq!(key_entry_response.metadata.certificate.unwrap(), gen_key_result.cert);
+ assert_eq!(
+ key_entry_response.metadata.certificateChain.unwrap_or_default(),
+ gen_key_result.cert_chain
+ );
+ assert_eq!(key_entry_response.metadata.key.domain, Domain::KEY_ID);
+ assert_ne!(key_entry_response.metadata.key.nspace, 0);
+ assert_eq!(
+ key_entry_response.metadata.keySecurityLevel,
+ SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT
+ );
- // Combine keyparameters from gen_key_result and keyparameters
- // from legacy key-char file.
- let mut legacy_file_key_params: Vec<KsKeyparameter> = Vec::new();
- match structured_test_params() {
- LegacyKeyCharacteristics::File(legacy_key_params) => {
- for param in &legacy_key_params {
- let mut present_in_gen_params = false;
- for gen_param in &gen_key_result.key_parameters {
- if param.get_tag() == gen_param.get_tag() {
- present_in_gen_params = true;
- }
- }
- if !present_in_gen_params {
- legacy_file_key_params.push(param.clone());
+ // Preapare KsKeyParameter list from getKeEntry response Authorizations.
+ let mut key_params: Vec<KsKeyparameter> = Vec::new();
+ for param in key_entry_response.metadata.authorizations {
+ let key_param =
+ KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
+ key_params.push(key_param);
+ }
+
+ // Combine keyparameters from gen_key_result and keyparameters
+ // from legacy key-char file.
+ let mut legacy_file_key_params: Vec<KsKeyparameter> = Vec::new();
+ match structured_test_params() {
+ LegacyKeyCharacteristics::File(legacy_key_params) => {
+ for param in &legacy_key_params {
+ let mut present_in_gen_params = false;
+ for gen_param in &gen_key_result.key_parameters {
+ if param.get_tag() == gen_param.get_tag() {
+ present_in_gen_params = true;
}
}
- }
- _ => {
- panic!("Expecting file characteristics");
+ if !present_in_gen_params {
+ legacy_file_key_params.push(param.clone());
+ }
}
}
-
- // Remove Key-Params which have security levels other than TRUSTED_ENVIRONMENT
- gen_key_result.key_parameters.retain(|in_element| {
- *in_element.security_level()
- == SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT
- });
-
- println!("GetKeyEntry response key params: {:#?}", key_params);
- println!("Generated key params: {:#?}", gen_key_result.key_parameters);
-
- gen_key_result.key_parameters.append(&mut legacy_file_key_params);
-
- println!("Combined key params: {:#?}", gen_key_result.key_parameters);
-
- // Validate all keyparameters present in getKeyEntry response.
- for param in &key_params {
- gen_key_result.key_parameters.retain(|in_element| *in_element != *param);
+ _ => {
+ panic!("Expecting file characteristics");
}
+ }
- println!(
- "GetKeyEntry response unmatched key params: {:#?}",
- gen_key_result.key_parameters
- );
- assert_eq!(gen_key_result.key_parameters.len(), 0);
+ // Remove Key-Params which have security levels other than TRUSTED_ENVIRONMENT
+ gen_key_result.key_parameters.retain(|in_element| {
+ *in_element.security_level()
+ == SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT
+ });
+
+ println!("GetKeyEntry response key params: {:#?}", key_params);
+ println!("Generated key params: {:#?}", gen_key_result.key_parameters);
+
+ gen_key_result.key_parameters.append(&mut legacy_file_key_params);
+
+ println!("Combined key params: {:#?}", gen_key_result.key_parameters);
+
+ // Validate all keyparameters present in getKeyEntry response.
+ for param in &key_params {
+ gen_key_result.key_parameters.retain(|in_element| *in_element != *param);
}
- Err(s) => {
- panic!("getKeyEntry should have succeeded. {:?}", s);
- }
- };
- })
+
+ println!(
+ "GetKeyEntry response unmatched key params: {:#?}",
+ gen_key_result.key_parameters
+ );
+ assert_eq!(gen_key_result.key_parameters.len(), 0);
+ }
+ Err(s) => {
+ panic!("getKeyEntry should have succeeded. {:?}", s);
+ }
+ };
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(auid, agid, use_key_fn) };
+
// Make sure keystore2 clean up imported legacy db.
let path_buf = PathBuf::from("/data/misc/keystore/user_99");
if path_buf.as_path().is_dir() {
@@ -367,7 +362,7 @@
/// 6. To load and import the legacy key using its alias.
/// 7. After successful key import validate the user cert and cert-chain with initially
/// generated blobs.
-/// 8. Validate imported key perameters. Imported key parameters list should be the combination
+/// 8. Validate imported key parameters. Imported key parameters list should be the combination
/// of the key-parameters in characteristics file and the characteristics according to
/// the augmentation rules. There might be duplicate entries with different values for the
/// parameters like OS_VERSION, OS_VERSION, BOOT_PATCHLEVEL, VENDOR_PATCHLEVEL etc.
@@ -376,8 +371,6 @@
fn keystore2_encrypted_certificates() -> anyhow::Result<()> {
let auid = 98 * AID_USER_OFFSET + 10001;
let agid = 98 * AID_USER_OFFSET + 10001;
- static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
- static TARGET_SU_CTX: &str = "u:r:su:s0";
// Cleanup user directory if it exists
let path_buf = PathBuf::from("/data/misc/keystore/user_98");
@@ -385,177 +378,171 @@
std::fs::remove_dir_all(path_buf.as_path()).unwrap();
}
- // Safety: run_as must be called from a single threaded process.
- // This device test is run as a separate single threaded process.
- let gen_key_result = unsafe {
- run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
- // Remove user if already exist.
- let maint_service = get_maintenance();
- match maint_service.onUserRemoved(98) {
- Ok(_) => {
- println!("User was existed, deleted successfully");
- }
- Err(e) => {
- println!("onUserRemoved error: {:#?}", e);
- }
+ let gen_key_fn = || {
+ // Remove user if already exist.
+ let maint_service = get_maintenance();
+ match maint_service.onUserRemoved(98) {
+ Ok(_) => {
+ println!("User did exist, deleted successfully");
}
+ Err(e) => {
+ println!("onUserRemoved error: {:#?}", e);
+ }
+ }
- let sl = SecLevel::tee();
- // Generate Key BLOB and prepare legacy keystore blob files.
- let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") };
- let key_metadata = key_generations::generate_ec_p256_signing_key(
- &sl,
- Domain::BLOB,
- SELINUX_SHELL_NAMESPACE,
- None,
- att_challenge,
+ let sl = SecLevel::tee();
+ // Generate Key BLOB and prepare legacy keystore blob files.
+ let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") };
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sl,
+ Domain::BLOB,
+ SELINUX_SHELL_NAMESPACE,
+ None,
+ att_challenge,
+ )
+ .expect("Failed to generate key blob");
+
+ // Create keystore file layout for user_98.
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
+ let super_key =
+ TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap());
+
+ let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
+ if !path_buf.as_path().is_dir() {
+ std::fs::create_dir(path_buf.as_path()).unwrap();
+ }
+ path_buf.push(".masterkey");
+ if !path_buf.as_path().is_file() {
+ std::fs::write(path_buf.as_path(), SUPERKEY).unwrap();
+ }
+
+ let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
+ path_buf.push("9810001_USRPKEY_authboundcertenc");
+ if !path_buf.as_path().is_file() {
+ make_encrypted_key_file(
+ path_buf.as_path(),
+ &super_key,
+ &key_metadata.key.blob.unwrap(),
)
- .expect("Failed to generate key blob");
+ .unwrap();
+ }
- // Create keystore file layout for user_98.
- let pw: Password = PASSWORD.into();
- let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
- let super_key =
- TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap());
+ let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
+ path_buf.push(".9810001_chr_USRPKEY_authboundcertenc");
+ if !path_buf.as_path().is_file() {
+ std::fs::write(path_buf.as_path(), USRPKEY_AUTHBOUND_CHR).unwrap();
+ }
+ let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
+ path_buf.push("9810001_USRCERT_authboundcertenc");
+ if !path_buf.as_path().is_file() {
+ make_encrypted_usr_cert_file(
+ path_buf.as_path(),
+ &super_key,
+ key_metadata.certificate.as_ref().unwrap(),
+ )
+ .unwrap();
+ }
+
+ if let Some(chain) = key_metadata.certificateChain.as_ref() {
let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
- if !path_buf.as_path().is_dir() {
- std::fs::create_dir(path_buf.as_path()).unwrap();
- }
- path_buf.push(".masterkey");
+ path_buf.push("9810001_CACERT_authboundcertenc");
if !path_buf.as_path().is_file() {
- std::fs::write(path_buf.as_path(), SUPERKEY).unwrap();
+ make_encrypted_ca_cert_file(path_buf.as_path(), &super_key, chain).unwrap();
}
+ }
- let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
- path_buf.push("9810001_USRPKEY_authboundcertenc");
- if !path_buf.as_path().is_file() {
- make_encrypted_key_file(
- path_buf.as_path(),
- &super_key,
- &key_metadata.key.blob.unwrap(),
- )
- .unwrap();
+ // Keystore2 disables the legacy importer when it finds the legacy database empty.
+ // However, if the device boots with an empty legacy database, the optimization kicks in
+ // and keystore2 never checks the legacy file system layout.
+ // So, restart keystore2 service to detect populated legacy database.
+ keystore2_restart_service();
+
+ let auth_service = get_authorization();
+ match auth_service.onDeviceUnlocked(98, Some(PASSWORD)) {
+ Ok(result) => {
+ println!("Unlock Result: {:?}", result);
}
-
- let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
- path_buf.push(".9810001_chr_USRPKEY_authboundcertenc");
- if !path_buf.as_path().is_file() {
- std::fs::write(path_buf.as_path(), USRPKEY_AUTHBOUND_CHR).unwrap();
+ Err(e) => {
+ panic!("Unlock should have succeeded: {:?}", e);
}
+ }
- let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
- path_buf.push("9810001_USRCERT_authboundcertenc");
- if !path_buf.as_path().is_file() {
- make_encrypted_usr_cert_file(
- path_buf.as_path(),
- &super_key,
- key_metadata.certificate.as_ref().unwrap(),
- )
- .unwrap();
- }
+ let mut key_params: Vec<KsKeyparameter> = Vec::new();
+ for param in key_metadata.authorizations {
+ let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
+ key_params.push(key_param);
+ }
- if let Some(chain) = key_metadata.certificateChain.as_ref() {
- let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
- path_buf.push("9810001_CACERT_authboundcertenc");
- if !path_buf.as_path().is_file() {
- make_encrypted_ca_cert_file(path_buf.as_path(), &super_key, chain).unwrap();
- }
- }
-
- // Keystore2 disables the legacy importer when it finds the legacy database empty.
- // However, if the device boots with an empty legacy database, the optimization kicks in
- // and keystore2 never checks the legacy file system layout.
- // So, restart keystore2 service to detect populated legacy database.
- keystore2_restart_service();
-
- let auth_service = get_authorization();
- match auth_service.onDeviceUnlocked(98, Some(PASSWORD)) {
- Ok(result) => {
- println!("Unlock Result: {:?}", result);
- }
- Err(e) => {
- panic!("Unlock should have succeeded: {:?}", e);
- }
- }
-
- let mut key_params: Vec<KsKeyparameter> = Vec::new();
- for param in key_metadata.authorizations {
- let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
- key_params.push(key_param);
- }
-
- KeygenResult {
- cert: key_metadata.certificate.unwrap(),
- cert_chain: key_metadata.certificateChain.unwrap_or_default(),
- key_parameters: key_params,
- }
- })
+ KeygenResult {
+ cert: key_metadata.certificate.unwrap(),
+ cert_chain: key_metadata.certificateChain.unwrap_or_default(),
+ key_parameters: key_params,
+ }
};
- // Safety: run_as must be called from a single threaded process.
- // This device test is run as a separate single threaded process.
- unsafe {
- run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
- println!("UID: {}", getuid());
- println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9810001));
- println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9810001));
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let gen_key_result = unsafe { run_as::run_as_root(gen_key_fn) };
- let test_alias = "authboundcertenc";
- let keystore2 = get_keystore_service();
+ let use_key_fn = move || {
+ println!("UID: {}", getuid());
+ println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9810001));
+ println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9810001));
- match keystore2.getKeyEntry(&KeyDescriptor {
- domain: Domain::APP,
- nspace: SELINUX_SHELL_NAMESPACE,
- alias: Some(test_alias.to_string()),
- blob: None,
- }) {
- Ok(key_entry_response) => {
- assert_eq!(
- key_entry_response.metadata.certificate.unwrap(),
- gen_key_result.cert
- );
- assert_eq!(
- key_entry_response.metadata.certificateChain.unwrap_or_default(),
- gen_key_result.cert_chain
- );
+ let test_alias = "authboundcertenc";
+ let keystore2 = get_keystore_service();
- // Preapare KsKeyParameter list from getKeEntry response Authorizations.
- let mut key_params: Vec<KsKeyparameter> = Vec::new();
- for param in key_entry_response.metadata.authorizations {
- let key_param =
- KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
- key_params.push(key_param);
+ match keystore2.getKeyEntry(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: SELINUX_SHELL_NAMESPACE,
+ alias: Some(test_alias.to_string()),
+ blob: None,
+ }) {
+ Ok(key_entry_response) => {
+ assert_eq!(key_entry_response.metadata.certificate.unwrap(), gen_key_result.cert);
+ assert_eq!(
+ key_entry_response.metadata.certificateChain.unwrap_or_default(),
+ gen_key_result.cert_chain
+ );
+
+ // Preapare KsKeyParameter list from getKeEntry response Authorizations.
+ let mut key_params: Vec<KsKeyparameter> = Vec::new();
+ for param in key_entry_response.metadata.authorizations {
+ let key_param =
+ KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
+ key_params.push(key_param);
+ }
+
+ println!("GetKeyEntry response key params: {:#?}", key_params);
+ println!("Generated key params: {:#?}", gen_key_result.key_parameters);
+ match structured_test_params_cache() {
+ LegacyKeyCharacteristics::Cache(legacy_key_params) => {
+ println!("Legacy key-char cache: {:#?}", legacy_key_params);
+ // Validate all keyparameters present in getKeyEntry response.
+ for param in &legacy_key_params {
+ key_params.retain(|in_element| *in_element != *param);
+ }
+
+ println!("GetKeyEntry response unmatched key params: {:#?}", key_params);
+ assert_eq!(key_params.len(), 0);
}
-
- println!("GetKeyEntry response key params: {:#?}", key_params);
- println!("Generated key params: {:#?}", gen_key_result.key_parameters);
- match structured_test_params_cache() {
- LegacyKeyCharacteristics::Cache(legacy_key_params) => {
- println!("Legacy key-char cache: {:#?}", legacy_key_params);
- // Validate all keyparameters present in getKeyEntry response.
- for param in &legacy_key_params {
- key_params.retain(|in_element| *in_element != *param);
- }
-
- println!(
- "GetKeyEntry response unmatched key params: {:#?}",
- key_params
- );
- assert_eq!(key_params.len(), 0);
- }
- _ => {
- panic!("Expecting file characteristics");
- }
+ _ => {
+ panic!("Expecting file characteristics");
}
}
- Err(s) => {
- panic!("getKeyEntry should have succeeded. {:?}", s);
- }
- };
- })
+ }
+ Err(s) => {
+ panic!("getKeyEntry should have succeeded. {:?}", s);
+ }
+ };
};
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ unsafe { run_as::run_as_app(auid, agid, use_key_fn) };
+
// Make sure keystore2 clean up imported legacy db.
let path_buf = PathBuf::from("/data/misc/keystore/user_98");
if path_buf.as_path().is_dir() {
diff --git a/keystore2/tests/user_auth.rs b/keystore2/tests/user_auth.rs
index 263ad56..789f54b 100644
--- a/keystore2/tests/user_auth.rs
+++ b/keystore2/tests/user_auth.rs
@@ -14,7 +14,9 @@
//! Tests for user authentication interactions (via `IKeystoreAuthorization`).
-use crate::keystore2_client_test_utils::BarrierReached;
+use crate::keystore2_client_test_utils::{
+ BarrierReached, BarrierReachedWithData, get_vsr_api_level
+};
use android_security_authorization::aidl::android::security::authorization::{
IKeystoreAuthorization::IKeystoreAuthorization
};
@@ -22,39 +24,50 @@
IKeystoreMaintenance,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, HardwareAuthToken::HardwareAuthToken,
- HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel,
- KeyPurpose::KeyPurpose
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode,
+ HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
+ KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_hardware_gatekeeper::aidl::android::hardware::gatekeeper::{
+ IGatekeeper::IGatekeeper, IGatekeeper::ERROR_RETRY_TIMEOUT,
};
use android_system_keystore2::aidl::android::system::keystore2::{
CreateOperationResponse::CreateOperationResponse, Domain::Domain, KeyDescriptor::KeyDescriptor,
KeyMetadata::KeyMetadata,
};
+use android_system_keystore2::binder::{ExceptionCode, Result as BinderResult};
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
Timestamp::Timestamp,
};
+use anyhow::Context;
use keystore2_test_utils::{
- get_keystore_service, run_as, authorizations::AuthSetBuilder,
+ authorizations::AuthSetBuilder, expect, get_keystore_service, run_as,
+ run_as::{ChannelReader, ChannelWriter}, expect_km_error,
};
use log::{warn, info};
-use nix::unistd::{Gid, Uid};
use rustutils::users::AID_USER_OFFSET;
+use std::{time::Duration, thread::sleep};
/// Test user ID.
const TEST_USER_ID: i32 = 100;
-/// Fake password blob.
-static PASSWORD: &[u8] = &[
+/// Corresponding uid value.
+const UID: u32 = TEST_USER_ID as u32 * AID_USER_OFFSET + 1001;
+/// Fake synthetic password blob.
+static SYNTHETIC_PASSWORD: &[u8] = &[
0x42, 0x39, 0x30, 0x37, 0x44, 0x37, 0x32, 0x37, 0x39, 0x39, 0x43, 0x42, 0x39, 0x41, 0x42, 0x30,
0x34, 0x31, 0x30, 0x38, 0x46, 0x44, 0x33, 0x45, 0x39, 0x42, 0x32, 0x38, 0x36, 0x35, 0x41, 0x36,
0x33, 0x44, 0x42, 0x42, 0x43, 0x36, 0x33, 0x42, 0x34, 0x39, 0x37, 0x33, 0x35, 0x45, 0x41, 0x41,
0x32, 0x45, 0x31, 0x35, 0x43, 0x43, 0x46, 0x32, 0x39, 0x36, 0x33, 0x34, 0x31, 0x32, 0x41, 0x39,
];
-/// Fake SID value corresponding to Gatekeeper.
-static GK_SID: i64 = 123456;
-/// Fake SID value corresponding to a biometric authenticator.
-static BIO_SID1: i64 = 345678;
-/// Fake SID value corresponding to a biometric authenticator.
-static BIO_SID2: i64 = 456789;
+/// Gatekeeper password.
+static GK_PASSWORD: &[u8] = b"correcthorsebatterystaple";
+/// Fake SID base value corresponding to Gatekeeper. Individual tests use different SIDs to reduce
+/// the chances of cross-contamination, calculated statically (because each test is forked into a
+/// separate process).
+static GK_FAKE_SID_BASE: i64 = 123400;
+/// Fake SID base value corresponding to a biometric authenticator. Individual tests use different
+/// SIDs to reduce the chances of cross-contamination.
+static BIO_FAKE_SID_BASE: i64 = 345600;
const WEAK_UNLOCK_ENABLED: bool = true;
const WEAK_UNLOCK_DISABLED: bool = false;
@@ -68,6 +81,18 @@
binder::get_interface("android.security.maintenance").unwrap()
}
+/// Get the default Gatekeeper instance. This may fail on older devices where Gatekeeper is still a
+/// HIDL interface rather than AIDL.
+fn get_gatekeeper() -> Option<binder::Strong<dyn IGatekeeper>> {
+ binder::get_interface("android.hardware.gatekeeper.IGatekeeper/default").ok()
+}
+
+/// Indicate whether a Gatekeeper result indicates a delayed-retry is needed.
+fn is_gk_retry<T: std::fmt::Debug>(result: &BinderResult<T>) -> bool {
+ matches!(result, Err(s) if s.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+ && s.service_specific_error() == ERROR_RETRY_TIMEOUT)
+}
+
fn abort_op(result: binder::Result<CreateOperationResponse>) {
if let Ok(rsp) = result {
if let Some(op) = rsp.iOperation {
@@ -86,156 +111,780 @@
struct TestUser {
id: i32,
maint: binder::Strong<dyn IKeystoreMaintenance>,
+ gk: Option<binder::Strong<dyn IGatekeeper>>,
+ gk_sid: Option<i64>,
+ gk_handle: Vec<u8>,
}
impl TestUser {
fn new() -> Self {
- Self::new_user(TEST_USER_ID, PASSWORD)
+ Self::new_user(TEST_USER_ID, SYNTHETIC_PASSWORD)
}
- fn new_user(user_id: i32, password: &[u8]) -> Self {
+ fn new_user(user_id: i32, sp: &[u8]) -> Self {
let maint = get_maintenance();
maint.onUserAdded(user_id).expect("failed to add test user");
maint
- .initUserSuperKeys(user_id, password, /* allowExisting= */ false)
+ .initUserSuperKeys(user_id, sp, /* allowExisting= */ false)
.expect("failed to init test user");
- Self { id: user_id, maint }
+ let gk = get_gatekeeper();
+ let (gk_sid, gk_handle) = if let Some(gk) = &gk {
+ // AIDL Gatekeeper is available, so enroll a password.
+ loop {
+ let result = gk.enroll(user_id, &[], &[], GK_PASSWORD);
+ if is_gk_retry(&result) {
+ sleep(Duration::from_secs(1));
+ continue;
+ }
+ let rsp = result.expect("gk.enroll() failed");
+ info!("registered test user {user_id} as sid {} with GK", rsp.secureUserId);
+ break (Some(rsp.secureUserId), rsp.data);
+ }
+ } else {
+ (None, vec![])
+ };
+ Self { id: user_id, maint, gk, gk_sid, gk_handle }
+ }
+
+ /// Perform Gatekeeper verification, which will return a HAT on success.
+ fn gk_verify(&self, challenge: i64) -> Option<HardwareAuthToken> {
+ let Some(gk) = &self.gk else { return None };
+ loop {
+ let result = gk.verify(self.id, challenge, &self.gk_handle, GK_PASSWORD);
+ if is_gk_retry(&result) {
+ sleep(Duration::from_secs(1));
+ continue;
+ }
+ let rsp = result.expect("gk.verify failed");
+ break Some(rsp.hardwareAuthToken);
+ }
}
}
impl Drop for TestUser {
fn drop(&mut self) {
let _ = self.maint.onUserRemoved(self.id);
+ if let Some(gk) = &self.gk {
+ info!("deregister test user {} with GK", self.id);
+ if let Err(e) = gk.deleteUser(self.id) {
+ warn!("failed to deregister test user {}: {e:?}", self.id);
+ }
+ }
}
}
#[test]
-fn keystore2_test_unlocked_device_required() {
+fn test_auth_bound_timeout_with_gk() {
+ let bio_fake_sid1 = BIO_FAKE_SID_BASE + 1;
+ let bio_fake_sid2 = BIO_FAKE_SID_BASE + 2;
+ type Barrier = BarrierReachedWithData<Option<i64>>;
android_logger::init_once(
android_logger::Config::default()
.with_tag("keystore2_client_tests")
.with_max_level(log::LevelFilter::Debug),
);
- static CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
- const UID: u32 = TEST_USER_ID as u32 * AID_USER_OFFSET + 1001;
- // Safety: only one thread at this point, and nothing yet done with binder.
+ let child_fn = move |reader: &mut ChannelReader<Barrier>,
+ writer: &mut ChannelWriter<Barrier>|
+ -> Result<(), run_as::Error> {
+ // Now we're in a new process, wait to be notified before starting.
+ let gk_sid: i64 = match reader.recv().0 {
+ Some(sid) => sid,
+ None => {
+ // There is no AIDL Gatekeeper available, so abandon the test. It would be nice to
+ // know this before starting the child process, but finding it out requires Binder,
+ // which can't be used until after the child has forked.
+ return Ok(());
+ }
+ };
+
+ // Action A: create a new auth-bound key which requires auth in the last 3 seconds,
+ // and fail to start an operation using it.
+ let ks2 = get_keystore_service();
+ let sec_level =
+ ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+ let params = AuthSetBuilder::new()
+ .user_secure_id(gk_sid)
+ .user_secure_id(bio_fake_sid1)
+ .user_secure_id(bio_fake_sid2)
+ .user_auth_type(HardwareAuthenticatorType::ANY)
+ .auth_timeout(3)
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256);
+
+ let KeyMetadata { key, .. } = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some("auth-bound-timeout-1".to_string()),
+ blob: None,
+ },
+ None,
+ ¶ms,
+ 0,
+ b"entropy",
+ )
+ .context("key generation failed")?;
+ info!("A: created auth-timeout key {key:?} bound to sids {gk_sid}, {bio_fake_sid1}, {bio_fake_sid2}");
+
+ // No HATs so cannot create an operation using the key.
+ let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+ info!("A: failed auth-bound operation (no HAT) as expected {result:?}");
+
+ writer.send(&Barrier::new(None)); // A done.
+
+ // Action B: succeed when a valid HAT is available.
+ reader.recv();
+
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ expect!(result.is_ok());
+ let op = result.unwrap().iOperation.context("no operation in result")?;
+ let result = op.finish(Some(b"data"), None);
+ expect!(result.is_ok());
+ info!("B: performed auth-bound operation (with valid GK HAT) as expected");
+
+ writer.send(&Barrier::new(None)); // B done.
+
+ // Action C: fail again when the HAT is old enough to not even be checked.
+ reader.recv();
+ info!("C: wait so that any HAT times out");
+ sleep(Duration::from_secs(4));
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ info!("C: failed auth-bound operation (HAT is too old) as expected {result:?}");
+ writer.send(&Barrier::new(None)); // C done.
+
+ Ok(())
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
let mut child_handle = unsafe {
// Perform keystore actions while running as the test user.
- run_as::run_as_child(
- CTX,
- Uid::from_raw(UID),
- Gid::from_raw(UID),
- move |reader, writer| -> Result<(), String> {
- // Now we're in a new process, wait to be notified before starting.
- reader.recv();
-
- // Action A: create a new unlocked-device-required key (which thus requires
- // super-encryption), while the device is unlocked.
- let ks2 = get_keystore_service();
- let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
- let params = AuthSetBuilder::new()
- .unlocked_device_required()
- .algorithm(Algorithm::EC)
- .purpose(KeyPurpose::SIGN)
- .purpose(KeyPurpose::VERIFY)
- .digest(Digest::SHA_2_256)
- .ec_curve(EcCurve::P_256);
-
- let KeyMetadata { key, .. } = sec_level
- .generateKey(
- &KeyDescriptor {
- domain: Domain::APP,
- nspace: -1,
- alias: Some("unlocked-device-required".to_string()),
- blob: None,
- },
- None,
- ¶ms,
- 0,
- b"entropy",
- )
- .expect("key generation failed");
- info!("A: created unlocked-device-required key while unlocked {key:?}");
- writer.send(&BarrierReached {}); // A done.
-
- // Action B: fail to use the unlocked-device-required key while locked.
- reader.recv();
- let params =
- AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
- let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
- info!("B: use unlocked-device-required key while locked => {result:?}");
- assert!(result.is_err());
- writer.send(&BarrierReached {}); // B done.
-
- // Action C: try to use the unlocked-device-required key while unlocked with a
- // password.
- reader.recv();
- let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
- info!("C: use unlocked-device-required key while lskf-unlocked => {result:?}");
- assert!(result.is_ok(), "failed with {result:?}");
- abort_op(result);
- writer.send(&BarrierReached {}); // C done.
-
- // Action D: try to use the unlocked-device-required key while unlocked with a weak
- // biometric.
- reader.recv();
- let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
- info!("D: use unlocked-device-required key while weak-locked => {result:?}");
- assert!(result.is_ok(), "createOperation failed: {result:?}");
- abort_op(result);
- writer.send(&BarrierReached {}); // D done.
-
- let _ = sec_level.deleteKey(&key);
- Ok(())
- },
- )
+ run_as::run_as_child_app(UID, UID, child_fn)
}
.unwrap();
+ // Now that the separate process has been forked off, it's safe to use binder to setup a test
+ // user.
+ let _ks2 = get_keystore_service();
+ let user = TestUser::new();
+ if user.gk.is_none() {
+ // Can't run this test if there's no AIDL Gatekeeper.
+ child_handle.send(&Barrier::new(None));
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+ return;
+ }
+ let user_id = user.id;
+ let auth_service = get_authorization();
+
+ // Lock and unlock to ensure super keys are already created.
+ auth_service
+ .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+ .unwrap();
+ auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+
+ info!("trigger child process action A and wait for completion");
+ child_handle.send(&Barrier::new(Some(user.gk_sid.unwrap())));
+ child_handle.recv_or_die();
+
+ // Unlock with GK password to get a genuine auth token.
+ let real_hat = user.gk_verify(0).expect("failed to perform GK verify");
+ auth_service.addAuthToken(&real_hat).unwrap();
+
+ info!("trigger child process action B and wait for completion");
+ child_handle.send(&Barrier::new(None));
+ child_handle.recv_or_die();
+
+ info!("trigger child process action C and wait for completion");
+ child_handle.send(&Barrier::new(None));
+ child_handle.recv_or_die();
+
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+#[test]
+fn test_auth_bound_timeout_failure() {
+ let gk_fake_sid = GK_FAKE_SID_BASE + 1;
+ let bio_fake_sid1 = BIO_FAKE_SID_BASE + 3;
+ let bio_fake_sid2 = BIO_FAKE_SID_BASE + 4;
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_client_tests")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+
+ let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
+ writer: &mut ChannelWriter<BarrierReached>|
+ -> Result<(), run_as::Error> {
+ // Now we're in a new process, wait to be notified before starting.
+ reader.recv();
+
+ // Action A: create a new auth-bound key which requires auth in the last 3 seconds,
+ // and fail to start an operation using it.
+ let ks2 = get_keystore_service();
+
+ let sec_level =
+ ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+ let params = AuthSetBuilder::new()
+ .user_secure_id(bio_fake_sid1)
+ .user_secure_id(bio_fake_sid2)
+ .user_auth_type(HardwareAuthenticatorType::ANY)
+ .auth_timeout(3)
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256);
+
+ let KeyMetadata { key, .. } = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some("auth-bound-timeout-2".to_string()),
+ blob: None,
+ },
+ None,
+ ¶ms,
+ 0,
+ b"entropy",
+ )
+ .context("key generation failed")?;
+ info!("A: created auth-timeout key {key:?} bound to sids {bio_fake_sid1}, {bio_fake_sid2}");
+
+ // No HATs so cannot create an operation using the key.
+ let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+ info!("A: failed auth-bound operation (no HAT) as expected {result:?}");
+
+ writer.send(&BarrierReached {}); // A done.
+
+ // Action B: fail again when an invalid HAT is available.
+ reader.recv();
+
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ expect!(result.is_err());
+ if get_vsr_api_level() >= 35 {
+ // Older devices may report an incorrect error code when presented with an invalid auth
+ // token.
+ expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+ }
+ info!("B: failed auth-bound operation (HAT is invalid) as expected {result:?}");
+
+ writer.send(&BarrierReached {}); // B done.
+
+ // Action C: fail again when the HAT is old enough to not even be checked.
+ reader.recv();
+ info!("C: wait so that any HAT times out");
+ sleep(Duration::from_secs(4));
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+ info!("C: failed auth-bound operation (HAT is too old) as expected {result:?}");
+ writer.send(&BarrierReached {}); // C done.
+
+ Ok(())
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let mut child_handle = unsafe {
+ // Perform keystore actions while running as the test user.
+ run_as::run_as_child_app(UID, UID, child_fn)
+ }
+ .unwrap();
+
+ // Now that the separate process has been forked off, it's safe to use binder to setup a test
+ // user.
+ let _ks2 = get_keystore_service();
+ let user = TestUser::new();
+ let user_id = user.id;
+ let auth_service = get_authorization();
+
+ // Lock and unlock to ensure super keys are already created.
+ auth_service
+ .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+ .unwrap();
+ auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+ auth_service.addAuthToken(&fake_lskf_token(gk_fake_sid)).unwrap();
+
+ info!("trigger child process action A and wait for completion");
+ child_handle.send(&BarrierReached {});
+ child_handle.recv_or_die();
+
+ // Unlock with password and a fake auth token that matches the key
+ auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+ auth_service.addAuthToken(&fake_bio_lskf_token(gk_fake_sid, bio_fake_sid1)).unwrap();
+
+ info!("trigger child process action B and wait for completion");
+ child_handle.send(&BarrierReached {});
+ child_handle.recv_or_die();
+
+ info!("trigger child process action C and wait for completion");
+ child_handle.send(&BarrierReached {});
+ child_handle.recv_or_die();
+
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+#[test]
+fn test_auth_bound_per_op_with_gk() {
+ let bio_fake_sid1 = BIO_FAKE_SID_BASE + 5;
+ let bio_fake_sid2 = BIO_FAKE_SID_BASE + 6;
+ type Barrier = BarrierReachedWithData<Option<i64>>;
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_client_tests")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+
+ let child_fn = move |reader: &mut ChannelReader<Barrier>,
+ writer: &mut ChannelWriter<Barrier>|
+ -> Result<(), run_as::Error> {
+ // Now we're in a new process, wait to be notified before starting.
+ let gk_sid: i64 = match reader.recv().0 {
+ Some(sid) => sid,
+ None => {
+ // There is no AIDL Gatekeeper available, so abandon the test. It would be nice to
+ // know this before starting the child process, but finding it out requires Binder,
+ // which can't be used until after the child has forked.
+ return Ok(());
+ }
+ };
+
+ // Action A: create a new auth-bound key which requires auth-per-operation (because
+ // AUTH_TIMEOUT is not specified), and fail to finish an operation using it.
+ let ks2 = get_keystore_service();
+ let sec_level =
+ ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+ let params = AuthSetBuilder::new()
+ .user_secure_id(gk_sid)
+ .user_secure_id(bio_fake_sid1)
+ .user_auth_type(HardwareAuthenticatorType::ANY)
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256);
+
+ let KeyMetadata { key, .. } = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some("auth-per-op-1".to_string()),
+ blob: None,
+ },
+ None,
+ ¶ms,
+ 0,
+ b"entropy",
+ )
+ .context("key generation failed")?;
+ info!("A: created auth-per-op key {key:?} bound to sids {gk_sid}, {bio_fake_sid1}");
+
+ // We can create an operation using the key...
+ let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+ let result = sec_level
+ .createOperation(&key, ¶ms, UNFORCED)
+ .expect("failed to create auth-per-op operation");
+ let op = result.iOperation.context("no operation in result")?;
+ info!("A: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+
+ // .. but attempting to finish the operation fails because Keystore can't find a HAT.
+ let result = op.finish(Some(b"data"), None);
+ expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+ info!("A: failed auth-per-op op (no HAT) as expected {result:?}");
+
+ writer.send(&Barrier::new(None)); // A done.
+
+ // Action B: start an operation and pass out the challenge
+ reader.recv();
+ let result = sec_level
+ .createOperation(&key, ¶ms, UNFORCED)
+ .expect("failed to create auth-per-op operation");
+ let op = result.iOperation.context("no operation in result")?;
+ info!("B: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+ writer.send(&Barrier::new(Some(result.operationChallenge.unwrap().challenge))); // B done.
+
+ // Action C: finishing the operation succeeds now there's a per-op HAT.
+ reader.recv();
+ let result = op.finish(Some(b"data"), None);
+ expect!(result.is_ok());
+ info!("C: performed auth-per-op op expected");
+ writer.send(&Barrier::new(None)); // D done.
+
+ Ok(())
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let mut child_handle = unsafe {
+ // Perform keystore actions while running as the test user.
+ run_as::run_as_child_app(UID, UID, child_fn)
+ }
+ .unwrap();
+
+ // Now that the separate process has been forked off, it's safe to use binder to setup a test
+ // user.
+ let _ks2 = get_keystore_service();
+ let user = TestUser::new();
+ if user.gk.is_none() {
+ // Can't run this test if there's no AIDL Gatekeeper.
+ child_handle.send(&Barrier::new(None));
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+ return;
+ }
+ let user_id = user.id;
+ let auth_service = get_authorization();
+
+ // Lock and unlock to ensure super keys are already created.
+ auth_service
+ .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+ .unwrap();
+ auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+
+ info!("trigger child process action A and wait for completion");
+ child_handle.send(&Barrier::new(Some(user.gk_sid.unwrap())));
+ child_handle.recv_or_die();
+
+ info!("trigger child process action B and wait for completion");
+ child_handle.send(&Barrier::new(None));
+ let challenge = child_handle.recv_or_die().0.expect("no challenge");
+
+ // Unlock with GK and the challenge to get a genuine per-op auth token
+ let real_hat = user.gk_verify(challenge).expect("failed to perform GK verify");
+ auth_service.addAuthToken(&real_hat).unwrap();
+
+ info!("trigger child process action C and wait for completion");
+ child_handle.send(&Barrier::new(None));
+ child_handle.recv_or_die();
+
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+#[test]
+fn test_auth_bound_per_op_with_gk_failure() {
+ let bio_fake_sid1 = BIO_FAKE_SID_BASE + 7;
+ let bio_fake_sid2 = BIO_FAKE_SID_BASE + 8;
+ type Barrier = BarrierReachedWithData<Option<i64>>;
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_client_tests")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+
+ let child_fn = move |reader: &mut ChannelReader<Barrier>,
+ writer: &mut ChannelWriter<Barrier>|
+ -> Result<(), run_as::Error> {
+ // Now we're in a new process, wait to be notified before starting.
+ let gk_sid: i64 = match reader.recv().0 {
+ Some(sid) => sid,
+ None => {
+ // There is no AIDL Gatekeeper available, so abandon the test. It would be nice to
+ // know this before starting the child process, but finding it out requires Binder,
+ // which can't be used until after the child has forked.
+ return Ok(());
+ }
+ };
+
+ // Action A: create a new auth-bound key which requires auth-per-operation (because
+ // AUTH_TIMEOUT is not specified), and fail to finish an operation using it.
+ let ks2 = get_keystore_service();
+ let sec_level =
+ ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+ let params = AuthSetBuilder::new()
+ .user_secure_id(gk_sid)
+ .user_secure_id(bio_fake_sid1)
+ .user_auth_type(HardwareAuthenticatorType::ANY)
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256);
+
+ let KeyMetadata { key, .. } = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some("auth-per-op-2".to_string()),
+ blob: None,
+ },
+ None,
+ ¶ms,
+ 0,
+ b"entropy",
+ )
+ .context("key generation failed")?;
+ info!("A: created auth-per-op key {key:?} bound to sids {gk_sid}, {bio_fake_sid1}");
+
+ // We can create an operation using the key...
+ let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+ let result = sec_level
+ .createOperation(&key, ¶ms, UNFORCED)
+ .expect("failed to create auth-per-op operation");
+ let op = result.iOperation.context("no operation in result")?;
+ info!("A: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+
+ // .. but attempting to finish the operation fails because Keystore can't find a HAT.
+ let result = op.finish(Some(b"data"), None);
+ expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+ info!("A: failed auth-per-op op (no HAT) as expected {result:?}");
+
+ writer.send(&Barrier::new(None)); // A done.
+
+ // Action B: fail again when an irrelevant HAT is available.
+ reader.recv();
+
+ let result = sec_level
+ .createOperation(&key, ¶ms, UNFORCED)
+ .expect("failed to create auth-per-op operation");
+ let op = result.iOperation.context("no operation in result")?;
+ info!("B: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+ // The operation fails because the HAT that Keystore received is not related to the
+ // challenge.
+ let result = op.finish(Some(b"data"), None);
+ expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+ info!("B: failed auth-per-op op (HAT is not per-op) as expected {result:?}");
+
+ writer.send(&Barrier::new(None)); // B done.
+
+ // Action C: start an operation and pass out the challenge
+ reader.recv();
+ let result = sec_level
+ .createOperation(&key, ¶ms, UNFORCED)
+ .expect("failed to create auth-per-op operation");
+ let op = result.iOperation.context("no operation in result")?;
+ info!("C: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+ writer.send(&Barrier::new(Some(result.operationChallenge.unwrap().challenge))); // C done.
+
+ // Action D: finishing the operation still fails because the per-op HAT
+ // is invalid (the HMAC signature is faked and so the secure world
+ // rejects the HAT).
+ reader.recv();
+ let result = op.finish(Some(b"data"), None);
+ expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+ info!("D: failed auth-per-op op (HAT is per-op but invalid) as expected {result:?}");
+ writer.send(&Barrier::new(None)); // D done.
+
+ Ok(())
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let mut child_handle = unsafe {
+ // Perform keystore actions while running as the test user.
+ run_as::run_as_child_app(UID, UID, child_fn)
+ }
+ .unwrap();
+
+ // Now that the separate process has been forked off, it's safe to use binder to setup a test
+ // user.
+ let _ks2 = get_keystore_service();
+ let user = TestUser::new();
+ if user.gk.is_none() {
+ // Can't run this test if there's no AIDL Gatekeeper.
+ child_handle.send(&Barrier::new(None));
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+ return;
+ }
+ let user_id = user.id;
+ let auth_service = get_authorization();
+
+ // Lock and unlock to ensure super keys are already created.
+ auth_service
+ .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+ .unwrap();
+ auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+
+ info!("trigger child process action A and wait for completion");
+ let gk_sid = user.gk_sid.unwrap();
+ child_handle.send(&Barrier::new(Some(gk_sid)));
+ child_handle.recv_or_die();
+
+ // Unlock with password and a fake auth token.
+ auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+ auth_service.addAuthToken(&fake_lskf_token(gk_sid)).unwrap();
+
+ info!("trigger child process action B and wait for completion");
+ child_handle.send(&Barrier::new(None));
+ child_handle.recv_or_die();
+
+ info!("trigger child process action C and wait for completion");
+ child_handle.send(&Barrier::new(None));
+ let challenge = child_handle.recv_or_die().0.expect("no challenge");
+
+ // Add a fake auth token with the challenge value.
+ auth_service.addAuthToken(&fake_lskf_token_with_challenge(gk_sid, challenge)).unwrap();
+
+ info!("trigger child process action D and wait for completion");
+ child_handle.send(&Barrier::new(None));
+ child_handle.recv_or_die();
+
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+#[test]
+fn test_unlocked_device_required() {
+ let gk_fake_sid = GK_FAKE_SID_BASE + 3;
+ let bio_fake_sid1 = BIO_FAKE_SID_BASE + 9;
+ let bio_fake_sid2 = BIO_FAKE_SID_BASE + 10;
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_client_tests")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+
+ let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
+ writer: &mut ChannelWriter<BarrierReached>|
+ -> Result<(), run_as::Error> {
+ let ks2 = get_keystore_service();
+ if ks2.getInterfaceVersion().unwrap() < 4 {
+ // Assuming `IKeystoreAuthorization::onDeviceLocked` and
+ // `IKeystoreAuthorization::onDeviceUnlocked` APIs will be supported on devices
+ // with `IKeystoreService` >= 4.
+ return Ok(());
+ }
+
+ // Now we're in a new process, wait to be notified before starting.
+ reader.recv();
+
+ // Action A: create a new unlocked-device-required key (which thus requires
+ // super-encryption), while the device is unlocked.
+ let sec_level =
+ ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+ let params = AuthSetBuilder::new()
+ .no_auth_required()
+ .unlocked_device_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256);
+
+ let KeyMetadata { key, .. } = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some("unlocked-device-required".to_string()),
+ blob: None,
+ },
+ None,
+ ¶ms,
+ 0,
+ b"entropy",
+ )
+ .context("key generation failed")?;
+ info!("A: created unlocked-device-required key while unlocked {key:?}");
+ writer.send(&BarrierReached {}); // A done.
+
+ // Action B: fail to use the unlocked-device-required key while locked.
+ reader.recv();
+ let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ info!("B: use unlocked-device-required key while locked => {result:?}");
+ expect_km_error!(&result, ErrorCode::DEVICE_LOCKED);
+ writer.send(&BarrierReached {}); // B done.
+
+ // Action C: try to use the unlocked-device-required key while unlocked with a
+ // password.
+ reader.recv();
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ info!("C: use unlocked-device-required key while lskf-unlocked => {result:?}");
+ expect!(result.is_ok(), "failed with {result:?}");
+ abort_op(result);
+ writer.send(&BarrierReached {}); // C done.
+
+ // Action D: try to use the unlocked-device-required key while unlocked with a weak
+ // biometric.
+ reader.recv();
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ info!("D: use unlocked-device-required key while weak-locked => {result:?}");
+ expect!(result.is_ok(), "createOperation failed: {result:?}");
+ abort_op(result);
+ writer.send(&BarrierReached {}); // D done.
+
+ Ok(())
+ };
+
+ // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+ // `--test-threads=1`), and nothing yet done with binder.
+ let mut child_handle = unsafe {
+ // Perform keystore actions while running as the test user.
+ run_as::run_as_child_app(UID, UID, child_fn)
+ }
+ .unwrap();
+
+ let ks2 = get_keystore_service();
+ if ks2.getInterfaceVersion().unwrap() < 4 {
+ // Assuming `IKeystoreAuthorization::onDeviceLocked` and
+ // `IKeystoreAuthorization::onDeviceUnlocked` APIs will be supported on devices
+ // with `IKeystoreService` >= 4.
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+ return;
+ }
// Now that the separate process has been forked off, it's safe to use binder.
let user = TestUser::new();
let user_id = user.id;
let auth_service = get_authorization();
// Lock and unlock to ensure super keys are already created.
- auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_DISABLED).unwrap();
- auth_service.onDeviceUnlocked(user_id, Some(PASSWORD)).unwrap();
- auth_service.addAuthToken(&fake_lskf_token(GK_SID)).unwrap();
+ auth_service
+ .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+ .unwrap();
+ auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+ auth_service.addAuthToken(&fake_lskf_token(gk_fake_sid)).unwrap();
info!("trigger child process action A while unlocked and wait for completion");
child_handle.send(&BarrierReached {});
- child_handle.recv();
+ child_handle.recv_or_die();
// Move to locked and don't allow weak unlock, so super keys are wiped.
- auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_DISABLED).unwrap();
+ auth_service
+ .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+ .unwrap();
info!("trigger child process action B while locked and wait for completion");
child_handle.send(&BarrierReached {});
- child_handle.recv();
+ child_handle.recv_or_die();
// Unlock with password => loads super key from database.
- auth_service.onDeviceUnlocked(user_id, Some(PASSWORD)).unwrap();
- auth_service.addAuthToken(&fake_lskf_token(GK_SID)).unwrap();
+ auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+ auth_service.addAuthToken(&fake_lskf_token(gk_fake_sid)).unwrap();
info!("trigger child process action C while lskf-unlocked and wait for completion");
child_handle.send(&BarrierReached {});
- child_handle.recv();
+ child_handle.recv_or_die();
// Move to locked and allow weak unlock, then do a weak unlock.
- auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_ENABLED).unwrap();
+ auth_service
+ .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_ENABLED)
+ .unwrap();
auth_service.onDeviceUnlocked(user_id, None).unwrap();
info!("trigger child process action D while weak-unlocked and wait for completion");
child_handle.send(&BarrierReached {});
- child_handle.recv();
+ child_handle.recv_or_die();
assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
}
/// Generate a fake [`HardwareAuthToken`] for the given sid.
fn fake_lskf_token(gk_sid: i64) -> HardwareAuthToken {
+ fake_lskf_token_with_challenge(gk_sid, 0)
+}
+
+/// Generate a fake [`HardwareAuthToken`] for the given sid and challenge.
+fn fake_lskf_token_with_challenge(gk_sid: i64, challenge: i64) -> HardwareAuthToken {
HardwareAuthToken {
- challenge: 0,
+ challenge,
userId: gk_sid,
authenticatorId: 0,
authenticatorType: HardwareAuthenticatorType::PASSWORD,
@@ -243,3 +892,15 @@
mac: vec![1, 2, 3],
}
}
+
+/// Generate a fake [`HardwareAuthToken`] for the given sids
+fn fake_bio_lskf_token(gk_sid: i64, bio_sid: i64) -> HardwareAuthToken {
+ HardwareAuthToken {
+ challenge: 0,
+ userId: gk_sid,
+ authenticatorId: bio_sid,
+ authenticatorType: HardwareAuthenticatorType::FINGERPRINT,
+ timestamp: Timestamp { milliSeconds: 123 },
+ mac: vec![1, 2, 3],
+ }
+}
diff --git a/keystore2/watchdog/Android.bp b/keystore2/watchdog/Android.bp
index 5074388..9a99f10 100644
--- a/keystore2/watchdog/Android.bp
+++ b/keystore2/watchdog/Android.bp
@@ -26,6 +26,7 @@
crate_name: "watchdog_rs",
srcs: ["src/lib.rs"],
rustlibs: [
+ "libchrono",
"liblog_rust",
],
}
diff --git a/keystore2/watchdog/src/lib.rs b/keystore2/watchdog/src/lib.rs
index baed5ca..f6a1291 100644
--- a/keystore2/watchdog/src/lib.rs
+++ b/keystore2/watchdog/src/lib.rs
@@ -29,6 +29,9 @@
time::{Duration, Instant},
};
+#[cfg(test)]
+mod tests;
+
/// Represents a Watchdog record. It can be created with `Watchdog::watch` or
/// `Watchdog::watch_with`. It disarms the record when dropped.
pub struct WatchPoint {
@@ -61,6 +64,18 @@
context: Option<Box<dyn std::fmt::Debug + Send + 'static>>,
}
+impl Record {
+ // Return a string representation of the start time of the record.
+ //
+ // Times are hard. This may not be accurate (e.g. if the system clock has been modified since
+ // the watchdog started), but it's _really_ useful to get a starting wall time for overrunning
+ // watchdogs.
+ fn started_utc(&self) -> String {
+ let started_utc = chrono::Utc::now() - self.started.elapsed();
+ format!("{}", started_utc.format("%m-%d %H:%M:%S%.3f UTC"))
+ }
+}
+
struct WatchdogState {
state: State,
thread: Option<thread::JoinHandle<()>>,
@@ -134,8 +149,13 @@
.filter(|(_, r)| r.deadline.saturating_duration_since(now) == Duration::new(0, 0))
.collect();
- log::warn!("When extracting from a bug report, please include this header");
- log::warn!("and all {} records below.", overdue_records.len());
+ log::warn!(
+ concat!(
+ "When extracting from a bug report, please include this header ",
+ "and all {} records below (to footer)"
+ ),
+ overdue_records.len()
+ );
// Watch points can be nested, i.e., a single thread may have multiple armed
// watch points. And the most recent on each thread (thread recent) is closest to the point
@@ -166,9 +186,10 @@
match &r.context {
Some(ctx) => {
log::warn!(
- "{:?} {} Pending: {:?} Overdue {:?} for {:?}",
+ "{:?} {} Started: {} Pending: {:?} Overdue {:?} for {:?}",
i.tid,
i.id,
+ r.started_utc(),
r.started.elapsed(),
r.deadline.elapsed(),
ctx
@@ -176,9 +197,10 @@
}
None => {
log::warn!(
- "{:?} {} Pending: {:?} Overdue {:?}",
+ "{:?} {} Started: {} Pending: {:?} Overdue {:?}",
i.tid,
i.id,
+ r.started_utc(),
r.started.elapsed(),
r.deadline.elapsed()
);
@@ -190,7 +212,32 @@
}
fn disarm(&mut self, index: Index) {
- self.records.remove(&index);
+ let result = self.records.remove(&index);
+ if let Some(record) = result {
+ let now = Instant::now();
+ let timeout_left = record.deadline.saturating_duration_since(now);
+ if timeout_left == Duration::new(0, 0) {
+ match &record.context {
+ Some(ctx) => log::info!(
+ "Watchdog complete for: {:?} {} Started: {} Pending: {:?} Overdue {:?} for {:?}",
+ index.tid,
+ index.id,
+ record.started_utc(),
+ record.started.elapsed(),
+ record.deadline.elapsed(),
+ ctx
+ ),
+ None => log::info!(
+ "Watchdog complete for: {:?} {} Started: {} Pending: {:?} Overdue {:?}",
+ index.tid,
+ index.id,
+ record.started_utc(),
+ record.started.elapsed(),
+ record.deadline.elapsed()
+ ),
+ }
+ }
+ }
}
fn arm(&mut self, index: Index, record: Record) {
@@ -332,86 +379,3 @@
state.state = State::Running;
}
}
-
-#[cfg(test)]
-mod tests {
-
- use super::*;
- use std::sync::atomic;
- use std::thread;
- use std::time::Duration;
-
- /// Count the number of times `Debug::fmt` is invoked.
- #[derive(Default, Clone)]
- struct DebugCounter(Arc<atomic::AtomicU8>);
- impl DebugCounter {
- fn value(&self) -> u8 {
- self.0.load(atomic::Ordering::Relaxed)
- }
- }
- impl std::fmt::Debug for DebugCounter {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
- let count = self.0.fetch_add(1, atomic::Ordering::Relaxed);
- write!(f, "hit_count: {count}")
- }
- }
-
- #[test]
- fn test_watchdog() {
- android_logger::init_once(
- android_logger::Config::default()
- .with_tag("keystore2_watchdog_tests")
- .with_max_level(log::LevelFilter::Debug),
- );
-
- let wd = Watchdog::new(Duration::from_secs(3));
- let hit_counter = DebugCounter::default();
- let wp = Watchdog::watch_with(
- &wd,
- "test_watchdog",
- Duration::from_millis(100),
- hit_counter.clone(),
- );
- assert_eq!(0, hit_counter.value());
- thread::sleep(Duration::from_millis(500));
- assert_eq!(1, hit_counter.value());
- thread::sleep(Duration::from_secs(1));
- assert_eq!(1, hit_counter.value());
-
- drop(wp);
- thread::sleep(Duration::from_secs(10));
- assert_eq!(1, hit_counter.value());
- let (_, ref state) = *wd.state;
- let state = state.lock().unwrap();
- assert_eq!(state.state, State::NotRunning);
- }
-
- #[test]
- fn test_watchdog_backoff() {
- android_logger::init_once(
- android_logger::Config::default()
- .with_tag("keystore2_watchdog_tests")
- .with_max_level(log::LevelFilter::Debug),
- );
-
- let wd = Watchdog::new(Duration::from_secs(3));
- let hit_counter = DebugCounter::default();
- let wp = Watchdog::watch_with(
- &wd,
- "test_watchdog",
- Duration::from_millis(100),
- hit_counter.clone(),
- );
- assert_eq!(0, hit_counter.value());
- thread::sleep(Duration::from_millis(500));
- assert_eq!(1, hit_counter.value());
- thread::sleep(Duration::from_secs(6));
- assert_eq!(2, hit_counter.value());
- thread::sleep(Duration::from_secs(11));
- assert_eq!(3, hit_counter.value());
-
- drop(wp);
- thread::sleep(Duration::from_secs(4));
- assert_eq!(3, hit_counter.value());
- }
-}
diff --git a/keystore2/watchdog/src/tests.rs b/keystore2/watchdog/src/tests.rs
new file mode 100644
index 0000000..d35c0dd
--- /dev/null
+++ b/keystore2/watchdog/src/tests.rs
@@ -0,0 +1,86 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Watchdog tests.
+
+use super::*;
+use std::sync::atomic;
+use std::thread;
+use std::time::Duration;
+
+/// Count the number of times `Debug::fmt` is invoked.
+#[derive(Default, Clone)]
+struct DebugCounter(Arc<atomic::AtomicU8>);
+impl DebugCounter {
+ fn value(&self) -> u8 {
+ self.0.load(atomic::Ordering::Relaxed)
+ }
+}
+impl std::fmt::Debug for DebugCounter {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ let count = self.0.fetch_add(1, atomic::Ordering::Relaxed);
+ write!(f, "hit_count: {count}")
+ }
+}
+
+#[test]
+fn test_watchdog() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_watchdog_tests")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+
+ let wd = Watchdog::new(Duration::from_secs(3));
+ let hit_counter = DebugCounter::default();
+ let wp =
+ Watchdog::watch_with(&wd, "test_watchdog", Duration::from_millis(100), hit_counter.clone());
+ assert_eq!(0, hit_counter.value());
+ thread::sleep(Duration::from_millis(500));
+ assert_eq!(1, hit_counter.value());
+ thread::sleep(Duration::from_secs(1));
+ assert_eq!(1, hit_counter.value());
+
+ drop(wp);
+ thread::sleep(Duration::from_secs(10));
+ assert_eq!(1, hit_counter.value());
+ let (_, ref state) = *wd.state;
+ let state = state.lock().unwrap();
+ assert_eq!(state.state, State::NotRunning);
+}
+
+#[test]
+fn test_watchdog_backoff() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_watchdog_tests")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+
+ let wd = Watchdog::new(Duration::from_secs(3));
+ let hit_counter = DebugCounter::default();
+ let wp =
+ Watchdog::watch_with(&wd, "test_watchdog", Duration::from_millis(100), hit_counter.clone());
+ assert_eq!(0, hit_counter.value());
+ thread::sleep(Duration::from_millis(500));
+ assert_eq!(1, hit_counter.value());
+ thread::sleep(Duration::from_secs(6));
+ assert_eq!(2, hit_counter.value());
+ thread::sleep(Duration::from_secs(11));
+ assert_eq!(3, hit_counter.value());
+
+ drop(wp);
+ thread::sleep(Duration::from_secs(4));
+ assert_eq!(3, hit_counter.value());
+}
diff --git a/prng_seeder/Android.bp b/prng_seeder/Android.bp
index 4f9b7e1..b56a405 100644
--- a/prng_seeder/Android.bp
+++ b/prng_seeder/Android.bp
@@ -19,19 +19,6 @@
default_applicable_licenses: ["system_security_license"],
}
-rust_bindgen {
- name: "libcutils_socket_bindgen",
- crate_name: "cutils_socket_bindgen",
- wrapper_src: "cutils_wrapper.h",
- source_stem: "bindings",
- bindgen_flags: [
- "--allowlist-function=android_get_control_socket",
- ],
- shared_libs: [
- "libcutils",
- ],
-}
-
rust_defaults {
name: "prng_seeder_defaults",
edition: "2021",
@@ -39,10 +26,10 @@
"libanyhow",
"libbssl_sys",
"libclap",
- "libcutils_socket_bindgen",
"liblogger",
"liblog_rust",
"libnix",
+ "librustutils",
"libtokio",
],
@@ -73,10 +60,10 @@
"libanyhow",
"libbssl_sys",
"libclap",
- "libcutils_socket_bindgen",
"liblogger",
"liblog_rust",
"libnix",
+ "librustutils",
"libtokio",
],
test_suites: ["general-tests"],
diff --git a/prng_seeder/cutils_wrapper.h b/prng_seeder/cutils_wrapper.h
deleted file mode 100644
index 9c1fe56..0000000
--- a/prng_seeder/cutils_wrapper.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (C) 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cutils/sockets.h>
diff --git a/prng_seeder/src/cutils_socket.rs b/prng_seeder/src/cutils_socket.rs
deleted file mode 100644
index b408be6..0000000
--- a/prng_seeder/src/cutils_socket.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use std::ffi::CString;
-use std::os::unix::{net::UnixListener, prelude::FromRawFd};
-
-use anyhow::{ensure, Result};
-
-pub fn android_get_control_socket(name: &str) -> Result<UnixListener> {
- let name = CString::new(name)?;
- // SAFETY: name is a valid C string, and android_get_control_socket doesn't retain it after it
- // returns.
- let fd = unsafe { cutils_socket_bindgen::android_get_control_socket(name.as_ptr()) };
- ensure!(fd >= 0, "android_get_control_socket failed");
- // SAFETY: android_get_control_socket either returns a valid and open FD or -1, and we checked
- // that it's not -1.
- Ok(unsafe { UnixListener::from_raw_fd(fd) })
-}
diff --git a/prng_seeder/src/main.rs b/prng_seeder/src/main.rs
index cb7f38d..d112d61 100644
--- a/prng_seeder/src/main.rs
+++ b/prng_seeder/src/main.rs
@@ -18,7 +18,6 @@
//! by init.
mod conditioner;
-mod cutils_socket;
mod drbg;
use std::{
@@ -70,6 +69,9 @@
}
fn setup() -> Result<(ConditionerBuilder, UnixListener)> {
+ // SAFETY: nobody has taken ownership of the inherited FDs yet.
+ unsafe { rustutils::inherited_fd::init_once() }
+ .context("In setup, failed to own inherited FDs")?;
configure_logging()?;
let cli = Cli::try_parse()?;
// SAFETY: Nothing else sets the signal handler, so either it was set here or it is the default.
@@ -78,8 +80,9 @@
let listener = match cli.socket {
Some(path) => get_socket(path.as_path())?,
- None => cutils_socket::android_get_control_socket("prng_seeder")
- .context("In setup, calling android_get_control_socket")?,
+ None => rustutils::sockets::android_get_control_socket("prng_seeder")
+ .context("In setup, calling android_get_control_socket")?
+ .into(),
};
let hwrng = std::fs::File::open(&cli.source)
.with_context(|| format!("Unable to open hwrng {}", cli.source.display()))?;
diff --git a/provisioner/Android.bp b/provisioner/Android.bp
index ede1ae6..6a4dc24 100644
--- a/provisioner/Android.bp
+++ b/provisioner/Android.bp
@@ -36,6 +36,7 @@
],
static_libs: [
"android.hardware.common-V2-ndk",
+ "android.hardware.drm.common-V1-ndk",
"android.hardware.drm-V1-ndk",
"android.hardware.security.rkp-V3-ndk",
"libbase",
diff --git a/provisioner/rkp_factory_extraction_lib.cpp b/provisioner/rkp_factory_extraction_lib.cpp
index ec70d08..9b04626 100644
--- a/provisioner/rkp_factory_extraction_lib.cpp
+++ b/provisioner/rkp_factory_extraction_lib.cpp
@@ -25,7 +25,6 @@
#include <cstring>
#include <iterator>
#include <keymaster/cppcose/cppcose.h>
-#include <openssl/base64.h>
#include <remote_prov/remote_prov_utils.h>
#include <sys/random.h>
@@ -33,6 +32,7 @@
#include <optional>
#include <string>
#include <string_view>
+#include <unordered_set>
#include <vector>
#include "cppbor_parse.h"
@@ -42,6 +42,7 @@
using aidl::android::hardware::security::keymint::MacedPublicKey;
using aidl::android::hardware::security::keymint::ProtectedData;
using aidl::android::hardware::security::keymint::RpcHardwareInfo;
+using aidl::android::hardware::security::keymint::remote_prov::BccEntryData;
using aidl::android::hardware::security::keymint::remote_prov::EekChain;
using aidl::android::hardware::security::keymint::remote_prov::generateEekChain;
using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain;
@@ -50,35 +51,13 @@
using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryCsr;
using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryProtectedData;
-using namespace cppbor;
-using namespace cppcose;
+using cppbor::Array;
+using cppbor::Map;
+using cppbor::Null;
+template <class T> using ErrMsgOr = cppcose::ErrMsgOr<T>;
constexpr size_t kVersionWithoutSuperencryption = 3;
-std::string toBase64(const std::vector<uint8_t>& buffer) {
- size_t base64Length;
- int rc = EVP_EncodedLength(&base64Length, buffer.size());
- if (!rc) {
- std::cerr << "Error getting base64 length. Size overflow?" << std::endl;
- exit(-1);
- }
-
- std::string base64(base64Length, ' ');
- rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size());
- ++rc; // Account for NUL, which BoringSSL does not for some reason.
- if (rc != base64Length) {
- std::cerr << "Error writing base64. Expected " << base64Length
- << " bytes to be written, but " << rc << " bytes were actually written."
- << std::endl;
- exit(-1);
- }
-
- // BoringSSL automatically adds a NUL -- remove it from the string data
- base64.pop_back();
-
- return base64;
-}
-
std::vector<uint8_t> generateChallenge() {
std::vector<uint8_t> challenge(kChallengeSize);
@@ -90,7 +69,8 @@
if (errno == EINTR) {
continue;
} else {
- std::cerr << errno << ": " << strerror(errno) << std::endl;
+ std::cerr << "generateChallenge: getrandom returned an error with errno " << errno
+ << ": " << strerror(errno) << ". Exiting..." << std::endl;
exit(-1);
}
}
@@ -105,7 +85,7 @@
const DeviceInfo& verifiedDeviceInfo,
const std::vector<uint8_t>& challenge,
const std::vector<uint8_t>& keysToSignMac,
- IRemotelyProvisionedComponent* provisionable) {
+ const RpcHardwareInfo& rpcHardwareInfo) {
Array macedKeysToSign = Array()
.add(Map().add(1, 5).encode()) // alg: hmac-sha256
.add(Map()) // empty unprotected headers
@@ -113,12 +93,12 @@
.add(keysToSignMac); // MAC as returned from the HAL
ErrMsgOr<std::unique_ptr<Map>> parsedVerifiedDeviceInfo =
- parseAndValidateFactoryDeviceInfo(verifiedDeviceInfo.deviceInfo, provisionable);
+ parseAndValidateFactoryDeviceInfo(verifiedDeviceInfo.deviceInfo, rpcHardwareInfo);
if (!parsedVerifiedDeviceInfo) {
return {nullptr, parsedVerifiedDeviceInfo.moveMessage()};
}
- auto [parsedProtectedData, ignore2, errMsg] = parse(protectedData.protectedData);
+ auto [parsedProtectedData, ignore2, errMsg] = cppbor::parse(protectedData.protectedData);
if (!parsedProtectedData) {
std::cerr << "Error parsing protected data: '" << errMsg << "'" << std::endl;
return {nullptr, errMsg};
@@ -145,7 +125,7 @@
if (!status.isOk()) {
std::cerr << "Failed to get hardware info for '" << componentName
<< "'. Description: " << status.getDescription() << "." << std::endl;
- exit(-1);
+ return {nullptr, status.getDescription()};
}
const std::vector<uint8_t> eek = getProdEekChain(hwInfo.supportedEekCurve);
@@ -156,13 +136,14 @@
if (!status.isOk()) {
std::cerr << "Bundle extraction failed for '" << componentName
<< "'. Description: " << status.getDescription() << "." << std::endl;
- exit(-1);
+ return {nullptr, status.getDescription()};
}
return composeCertificateRequestV1(protectedData, verifiedDeviceInfo, challenge, keysToSignMac,
- irpc);
+ hwInfo);
}
-void selfTestGetCsrV1(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+std::optional<std::string> selfTestGetCsrV1(std::string_view componentName,
+ IRemotelyProvisionedComponent* irpc) {
std::vector<uint8_t> keysToSignMac;
std::vector<MacedPublicKey> emptyKeys;
DeviceInfo verifiedDeviceInfo;
@@ -172,14 +153,14 @@
if (!status.isOk()) {
std::cerr << "Failed to get hardware info for '" << componentName
<< "'. Description: " << status.getDescription() << "." << std::endl;
- exit(-1);
+ return status.getDescription();
}
const std::vector<uint8_t> eekId = {0, 1, 2, 3, 4, 5, 6, 7};
ErrMsgOr<EekChain> eekChain = generateEekChain(hwInfo.supportedEekCurve, /*length=*/3, eekId);
if (!eekChain) {
std::cerr << "Error generating test EEK certificate chain: " << eekChain.message();
- exit(-1);
+ return eekChain.message();
}
const std::vector<uint8_t> challenge = generateChallenge();
status = irpc->generateCertificateRequest(
@@ -188,18 +169,19 @@
if (!status.isOk()) {
std::cerr << "Error generating test cert chain for '" << componentName
<< "'. Description: " << status.getDescription() << "." << std::endl;
- exit(-1);
+ return status.getDescription();
}
auto result = verifyFactoryProtectedData(verifiedDeviceInfo, /*keysToSign=*/{}, keysToSignMac,
- protectedData, *eekChain, eekId,
- hwInfo.supportedEekCurve, irpc, challenge);
+ protectedData, *eekChain, eekId, hwInfo,
+ std::string(componentName), challenge);
if (!result) {
std::cerr << "Self test failed for IRemotelyProvisionedComponent '" << componentName
<< "'. Error message: '" << result.message() << "'." << std::endl;
- exit(-1);
+ return result.message();
}
+ return std::nullopt;
}
CborResult<Array> composeCertificateRequestV3(const std::vector<uint8_t>& csr) {
@@ -223,25 +205,35 @@
return {std::unique_ptr<Array>(parsedCsr.release()->asArray()), ""};
}
-CborResult<cppbor::Array> getCsrV3(std::string_view componentName,
- IRemotelyProvisionedComponent* irpc, bool selfTest) {
+CborResult<Array> getCsrV3(std::string_view componentName, IRemotelyProvisionedComponent* irpc,
+ bool selfTest, bool allowDegenerate, bool requireUdsCerts) {
std::vector<uint8_t> csr;
std::vector<MacedPublicKey> emptyKeys;
const std::vector<uint8_t> challenge = generateChallenge();
- auto status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr);
+ RpcHardwareInfo hwInfo;
+ auto status = irpc->getHardwareInfo(&hwInfo);
+ if (!status.isOk()) {
+ std::cerr << "Failed to get hardware info for '" << componentName
+ << "'. Description: " << status.getDescription() << "." << std::endl;
+ return {nullptr, status.getDescription()};
+ }
+
+ status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr);
if (!status.isOk()) {
std::cerr << "Bundle extraction failed for '" << componentName
<< "'. Description: " << status.getDescription() << "." << std::endl;
- exit(-1);
+ return {nullptr, status.getDescription()};
}
if (selfTest) {
- auto result = verifyFactoryCsr(/*keysToSign=*/cppbor::Array(), csr, irpc, challenge);
+ auto result = verifyFactoryCsr(/*keysToSign=*/cppbor::Array(), csr, hwInfo,
+ std::string(componentName), challenge, allowDegenerate,
+ requireUdsCerts);
if (!result) {
std::cerr << "Self test failed for IRemotelyProvisionedComponent '" << componentName
<< "'. Error message: '" << result.message() << "'." << std::endl;
- exit(-1);
+ return {nullptr, result.message()};
}
}
@@ -249,35 +241,37 @@
}
CborResult<Array> getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc,
- bool selfTest) {
+ bool selfTest, bool allowDegenerate, bool requireUdsCerts) {
RpcHardwareInfo hwInfo;
auto status = irpc->getHardwareInfo(&hwInfo);
if (!status.isOk()) {
std::cerr << "Failed to get hardware info for '" << componentName
<< "'. Description: " << status.getDescription() << "." << std::endl;
- exit(-1);
+ return {nullptr, status.getDescription()};
}
if (hwInfo.versionNumber < kVersionWithoutSuperencryption) {
if (selfTest) {
- selfTestGetCsrV1(componentName, irpc);
+ auto errMsg = selfTestGetCsrV1(componentName, irpc);
+ if (errMsg) {
+ return {nullptr, *errMsg};
+ }
}
return getCsrV1(componentName, irpc);
} else {
- return getCsrV3(componentName, irpc, selfTest);
+ return getCsrV3(componentName, irpc, selfTest, allowDegenerate, requireUdsCerts);
}
}
-bool isRemoteProvisioningSupported(IRemotelyProvisionedComponent* irpc) {
- RpcHardwareInfo hwInfo;
- auto status = irpc->getHardwareInfo(&hwInfo);
- if (status.isOk()) {
- return true;
+std::unordered_set<std::string> parseCommaDelimited(const std::string& input) {
+ std::stringstream ss(input);
+ std::unordered_set<std::string> result;
+ while (ss.good()) {
+ std::string name;
+ std::getline(ss, name, ',');
+ if (!name.empty()) {
+ result.insert(name);
+ }
}
- if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
- return false;
- }
- std::cerr << "Unexpected error when getting hardware info. Description: "
- << status.getDescription() << "." << std::endl;
- exit(-1);
-}
+ return result;
+}
\ No newline at end of file
diff --git a/provisioner/rkp_factory_extraction_lib.h b/provisioner/rkp_factory_extraction_lib.h
index 93c498a..3515f48 100644
--- a/provisioner/rkp_factory_extraction_lib.h
+++ b/provisioner/rkp_factory_extraction_lib.h
@@ -23,8 +23,13 @@
#include <memory>
#include <string>
#include <string_view>
+#include <unordered_set>
#include <vector>
+// Parse a comma-delimited string.
+// Ignores any empty strings.
+std::unordered_set<std::string> parseCommaDelimited(const std::string& input);
+
// Challenge size must be between 32 and 64 bytes inclusive.
constexpr size_t kChallengeSize = 64;
@@ -35,9 +40,6 @@
std::string errMsg;
};
-// Return `buffer` encoded as a base64 string.
-std::string toBase64(const std::vector<uint8_t>& buffer);
-
// Generate a random challenge containing `kChallengeSize` bytes.
std::vector<uint8_t> generateChallenge();
@@ -47,13 +49,4 @@
CborResult<cppbor::Array>
getCsr(std::string_view componentName,
aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc,
- bool selfTest);
-
-// Generates a test certificate chain and validates it, exiting the process on error.
-void selfTestGetCsr(
- std::string_view componentName,
- aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
-
-// Returns true if the given IRemotelyProvisionedComponent supports remote provisioning.
-bool isRemoteProvisioningSupported(
- aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
+ bool selfTest, bool allowDegenerate, bool requireUdsCerts);
\ No newline at end of file
diff --git a/provisioner/rkp_factory_extraction_lib_test.cpp b/provisioner/rkp_factory_extraction_lib_test.cpp
index 3fe88da..e21ef93 100644
--- a/provisioner/rkp_factory_extraction_lib_test.cpp
+++ b/provisioner/rkp_factory_extraction_lib_test.cpp
@@ -25,6 +25,7 @@
#include <android-base/properties.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <openssl/base64.h>
#include <cstdint>
#include <memory>
@@ -60,6 +61,101 @@
} // namespace cppbor
+inline const std::vector<uint8_t> kCsrWithoutUdsCerts{
+ 0x85, 0x01, 0xa0, 0x82, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xb8, 0x36,
+ 0xbb, 0x1e, 0x07, 0x85, 0x02, 0xde, 0xdb, 0x91, 0x38, 0x5d, 0xc7, 0xf8, 0x59, 0xa9, 0x4f, 0x50,
+ 0xee, 0x2a, 0x3f, 0xa5, 0x5f, 0xaa, 0xa1, 0x8e, 0x46, 0x84, 0xb8, 0x3b, 0x4b, 0x6d, 0x22, 0x58,
+ 0x20, 0xa1, 0xc1, 0xd8, 0xa5, 0x9d, 0x1b, 0xce, 0x8c, 0x65, 0x10, 0x8d, 0xcf, 0xa1, 0xf4, 0x91,
+ 0x10, 0x09, 0xfb, 0xb0, 0xc5, 0xb4, 0x01, 0x75, 0x72, 0xb4, 0x44, 0xaa, 0x23, 0x13, 0xe1, 0xe9,
+ 0xe5, 0x84, 0x43, 0xa1, 0x01, 0x26, 0xa0, 0x59, 0x01, 0x04, 0xa9, 0x01, 0x66, 0x69, 0x73, 0x73,
+ 0x75, 0x65, 0x72, 0x02, 0x67, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x00, 0x47, 0x44,
+ 0x50, 0x58, 0x20, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x20, 0xb8, 0x96, 0x54, 0xe2, 0x2c, 0xa4,
+ 0xd2, 0x4a, 0x9c, 0x0e, 0x45, 0x11, 0xc8, 0xf2, 0x63, 0xf0, 0x66, 0x0d, 0x2e, 0x20, 0x48, 0x96,
+ 0x90, 0x14, 0xf4, 0x54, 0x63, 0xc4, 0xf4, 0x39, 0x30, 0x38, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x55,
+ 0xa1, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74,
+ 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x20, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x3a, 0x00, 0x47, 0x44,
+ 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x4d, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20,
+ 0x01, 0x21, 0x58, 0x20, 0x91, 0xdc, 0x49, 0x60, 0x0d, 0x22, 0xf6, 0x28, 0x14, 0xaf, 0xab, 0xa5,
+ 0x9d, 0x4f, 0x26, 0xac, 0xf9, 0x99, 0xe7, 0xe1, 0xc9, 0xb7, 0x5d, 0x36, 0x21, 0x9d, 0x00, 0x47,
+ 0x63, 0x28, 0x79, 0xa7, 0x22, 0x58, 0x20, 0x13, 0x77, 0x51, 0x7f, 0x6a, 0xca, 0xa0, 0x50, 0x79,
+ 0x52, 0xb4, 0x6b, 0xd9, 0xb1, 0x3a, 0x1c, 0x9f, 0x91, 0x97, 0x60, 0xc1, 0x4b, 0x43, 0x5e, 0x45,
+ 0xd3, 0x0b, 0xa4, 0xbb, 0xc7, 0x27, 0x39, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40,
+ 0x88, 0xbd, 0xf9, 0x82, 0x04, 0xfe, 0xa6, 0xfe, 0x82, 0x94, 0xa3, 0xe9, 0x10, 0x91, 0xb5, 0x2e,
+ 0xa1, 0x62, 0x68, 0xa5, 0x3d, 0xab, 0xdb, 0xa5, 0x87, 0x2a, 0x97, 0x26, 0xb8, 0xd4, 0x60, 0x1a,
+ 0xf1, 0x3a, 0x45, 0x72, 0x77, 0xd4, 0xeb, 0x2b, 0xa4, 0x48, 0x93, 0xba, 0xae, 0x79, 0x35, 0x57,
+ 0x66, 0x54, 0x9d, 0x8e, 0xbd, 0xb0, 0x87, 0x5f, 0x8c, 0xf9, 0x04, 0xa3, 0xa7, 0x00, 0xf1, 0x21,
+ 0x84, 0x43, 0xa1, 0x01, 0x26, 0xa0, 0x59, 0x02, 0x0f, 0x82, 0x58, 0x20, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
+ 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x59, 0x01, 0xe9, 0x84,
+ 0x03, 0x67, 0x6b, 0x65, 0x79, 0x6d, 0x69, 0x6e, 0x74, 0xae, 0x65, 0x62, 0x72, 0x61, 0x6e, 0x64,
+ 0x66, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x01, 0x65, 0x6d,
+ 0x6f, 0x64, 0x65, 0x6c, 0x65, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x66, 0x64, 0x65, 0x76, 0x69, 0x63,
+ 0x65, 0x66, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x67, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,
+ 0x65, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x68, 0x76, 0x62, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x65,
+ 0x67, 0x72, 0x65, 0x65, 0x6e, 0x6a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+ 0x62, 0x31, 0x32, 0x6c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72,
+ 0x66, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x6d, 0x76, 0x62, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x64,
+ 0x69, 0x67, 0x65, 0x73, 0x74, 0x4f, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa,
+ 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x6c,
+ 0x65, 0x76, 0x65, 0x6c, 0x63, 0x74, 0x65, 0x65, 0x70, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x70, 0x61,
+ 0x74, 0x63, 0x68, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x1a, 0x01, 0x34, 0x8c, 0x62, 0x70, 0x62,
+ 0x6f, 0x6f, 0x74, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x66,
+ 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x72, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x70, 0x61,
+ 0x74, 0x63, 0x68, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x1a, 0x01, 0x34, 0x8c, 0x61, 0x72, 0x76,
+ 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6c, 0x65, 0x76, 0x65,
+ 0x6c, 0x1a, 0x01, 0x34, 0x8c, 0x63, 0x82, 0xa6, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58,
+ 0x20, 0x85, 0xcd, 0xd8, 0x8c, 0x35, 0x50, 0x11, 0x9c, 0x44, 0x24, 0xa7, 0xf1, 0xbf, 0x75, 0x6e,
+ 0x7c, 0xab, 0x8c, 0x86, 0xfa, 0x23, 0x95, 0x2c, 0x11, 0xaf, 0xf9, 0x52, 0x80, 0x8f, 0x45, 0x43,
+ 0x40, 0x22, 0x58, 0x20, 0xec, 0x4e, 0x0d, 0x5a, 0x81, 0xe8, 0x06, 0x12, 0x18, 0xa8, 0x10, 0x74,
+ 0x6e, 0x56, 0x33, 0x11, 0x7d, 0x74, 0xff, 0x49, 0xf7, 0x38, 0x32, 0xda, 0xf4, 0x60, 0xaa, 0x19,
+ 0x64, 0x29, 0x58, 0xbe, 0x23, 0x58, 0x21, 0x00, 0xa6, 0xd1, 0x85, 0xdb, 0x8b, 0x15, 0x84, 0xde,
+ 0x34, 0xf2, 0xe3, 0xee, 0x73, 0x8b, 0x85, 0x57, 0xc1, 0xa3, 0x5d, 0x3f, 0x95, 0x14, 0xd3, 0x74,
+ 0xfc, 0x73, 0x51, 0x7f, 0xe7, 0x1b, 0x30, 0xbb, 0xa6, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21,
+ 0x58, 0x20, 0x96, 0x6c, 0x16, 0x6c, 0x4c, 0xa7, 0x73, 0x64, 0x9a, 0x34, 0x88, 0x75, 0xf4, 0xdc,
+ 0xf3, 0x93, 0xb2, 0xf1, 0xd7, 0xfd, 0xe3, 0x11, 0xcf, 0x6b, 0xee, 0x26, 0xa4, 0xc5, 0xeb, 0xa5,
+ 0x33, 0x24, 0x22, 0x58, 0x20, 0xe0, 0x33, 0xe8, 0x53, 0xb2, 0x65, 0x1e, 0x33, 0x2a, 0x61, 0x9a,
+ 0x7a, 0xf4, 0x5f, 0x40, 0x0f, 0x80, 0x4a, 0x38, 0xff, 0x5d, 0x3c, 0xa3, 0x82, 0x36, 0x1e, 0x9d,
+ 0x93, 0xd9, 0x48, 0xaa, 0x0a, 0x23, 0x58, 0x20, 0x5e, 0xe5, 0x8f, 0x9a, 0x8c, 0xd3, 0xf4, 0xc0,
+ 0xf7, 0x08, 0x27, 0x5f, 0x8f, 0x77, 0x12, 0x36, 0x7b, 0x6d, 0xf7, 0x65, 0xd4, 0xcc, 0x63, 0xdc,
+ 0x28, 0x35, 0x33, 0x27, 0x5d, 0x28, 0xc9, 0x9d, 0x58, 0x40, 0x6c, 0xfa, 0xc9, 0xc0, 0xdf, 0x0e,
+ 0xe4, 0x17, 0x58, 0x06, 0xea, 0xf9, 0x88, 0x9e, 0x27, 0xa0, 0x89, 0x17, 0xa8, 0x1a, 0xe6, 0x0c,
+ 0x5e, 0x85, 0xa1, 0x13, 0x20, 0x86, 0x14, 0x2e, 0xd6, 0xae, 0xfb, 0xc1, 0xb6, 0x59, 0x66, 0x83,
+ 0xd2, 0xf4, 0xc8, 0x7a, 0x30, 0x0c, 0x6b, 0x53, 0x8b, 0x76, 0x06, 0xcb, 0x1b, 0x0f, 0xc3, 0x51,
+ 0x71, 0x52, 0xd1, 0xe3, 0x2a, 0xbc, 0x53, 0x16, 0x46, 0x49, 0xa1, 0x6b, 0x66, 0x69, 0x6e, 0x67,
+ 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x78, 0x3b, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x31, 0x2f,
+ 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x31, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x31,
+ 0x3a, 0x31, 0x31, 0x2f, 0x69, 0x64, 0x2f, 0x32, 0x30, 0x32, 0x31, 0x30, 0x38, 0x30, 0x35, 0x2e,
+ 0x34, 0x32, 0x3a, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2d,
+ 0x6b, 0x65, 0x79, 0x73};
+
+std::string toBase64(const std::vector<uint8_t>& buffer) {
+ size_t base64Length;
+ int rc = EVP_EncodedLength(&base64Length, buffer.size());
+ if (!rc) {
+ std::cerr << "Error getting base64 length. Size overflow?" << std::endl;
+ exit(-1);
+ }
+
+ std::string base64(base64Length, ' ');
+ rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size());
+ ++rc; // Account for NUL, which BoringSSL does not for some reason.
+ if (rc != base64Length) {
+ std::cerr << "Error writing base64. Expected " << base64Length
+ << " bytes to be written, but " << rc << " bytes were actually written."
+ << std::endl;
+ exit(-1);
+ }
+
+ // BoringSSL automatically adds a NUL -- remove it from the string data
+ base64.pop_back();
+
+ return base64;
+}
+
class MockIRemotelyProvisionedComponent : public IRemotelyProvisionedComponentDefault {
public:
MOCK_METHOD(ScopedAStatus, getHardwareInfo, (RpcHardwareInfo * _aidl_return), (override));
@@ -77,7 +173,7 @@
(const std::vector<MacedPublicKey>& in_keysToSign,
const std::vector<uint8_t>& in_challenge, std::vector<uint8_t>* _aidl_return),
(override));
- MOCK_METHOD(ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override));
+ MOCK_METHOD(ScopedAStatus, getInterfaceVersion, (int32_t* _aidl_return), (override));
MOCK_METHOD(ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override));
};
@@ -87,7 +183,7 @@
input[i] = i;
}
- // Test three lengths so we get all the different paddding options
+ // Test three lengths so we get all the different padding options
EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4"
"vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV"
"5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj"
@@ -159,7 +255,7 @@
std::vector<uint8_t> eekChain;
std::vector<uint8_t> challenge;
- // Set up mock, then call getSCsr
+ // Set up mock, then call getCsr
auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
hwInfo->versionNumber = 2;
@@ -180,8 +276,9 @@
SetArgPointee<6>(kFakeMac), //
Return(ByMove(ScopedAStatus::ok())))); //
- auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get(),
- /*selfTest=*/false);
+ auto [csr, csrErrMsg] =
+ getCsr("mock component name", mockRpc.get(),
+ /*selfTest=*/false, /*allowDegenerate=*/true, /*requireUdsCerts=*/false);
ASSERT_THAT(csr, NotNull()) << csrErrMsg;
ASSERT_THAT(csr->asArray(), Pointee(Property(&Array::size, Eq(4))));
@@ -230,7 +327,7 @@
TEST(LibRkpFactoryExtractionTests, GetCsrWithV3Hal) {
const std::vector<uint8_t> kCsr = Array()
- .add(3 /* version */)
+ .add(1 /* version */)
.add(Map() /* UdsCerts */)
.add(Array() /* DiceCertChain */)
.add(Array() /* SignedData */)
@@ -250,12 +347,13 @@
.WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(kCsr),
Return(ByMove(ScopedAStatus::ok()))));
- auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get(),
- /*selfTest=*/false);
+ auto [csr, csrErrMsg] =
+ getCsr("mock component name", mockRpc.get(),
+ /*selfTest=*/false, /*allowDegenerate=*/true, /*requireUdsCerts=*/false);
ASSERT_THAT(csr, NotNull()) << csrErrMsg;
ASSERT_THAT(csr, Pointee(Property(&Array::size, Eq(5))));
- EXPECT_THAT(csr->get(0 /* version */), Pointee(Eq(Uint(3))));
+ EXPECT_THAT(csr->get(0 /* version */), Pointee(Eq(Uint(1))));
EXPECT_THAT(csr->get(1)->asMap(), NotNull());
EXPECT_THAT(csr->get(2)->asArray(), NotNull());
EXPECT_THAT(csr->get(3)->asArray(), NotNull());
@@ -266,3 +364,63 @@
const Tstr fingerprint(android::base::GetProperty("ro.build.fingerprint", ""));
EXPECT_THAT(*unverifedDeviceInfo->get("fingerprint")->asTstr(), Eq(fingerprint));
}
+
+TEST(LibRkpFactoryExtractionTests, requireUdsCerts) {
+ const std::vector<uint8_t> csrEncoded = kCsrWithoutUdsCerts;
+ std::vector<uint8_t> challenge;
+
+ // Set up mock, then call getCsr
+ auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
+ EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
+ hwInfo->versionNumber = 3;
+ return ScopedAStatus::ok();
+ });
+ EXPECT_CALL(*mockRpc,
+ generateCertificateRequestV2(IsEmpty(), // keysToSign
+ _, // challenge
+ NotNull())) // _aidl_return
+ .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(csrEncoded),
+ Return(ByMove(ScopedAStatus::ok()))));
+
+ auto [csr, csrErrMsg] =
+ getCsr("default", mockRpc.get(),
+ /*selfTest=*/true, /*allowDegenerate=*/false, /*requireUdsCerts=*/true);
+ ASSERT_EQ(csr, nullptr);
+ ASSERT_THAT(csrErrMsg, testing::HasSubstr("UdsCerts are required"));
+}
+
+TEST(LibRkpFactoryExtractionTests, dontRequireUdsCerts) {
+ const std::vector<uint8_t> csrEncoded = kCsrWithoutUdsCerts;
+ std::vector<uint8_t> challenge;
+
+ // Set up mock, then call getCsr
+ auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
+ EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
+ hwInfo->versionNumber = 3;
+ return ScopedAStatus::ok();
+ });
+ EXPECT_CALL(*mockRpc,
+ generateCertificateRequestV2(IsEmpty(), // keysToSign
+ _, // challenge
+ NotNull())) // _aidl_return
+ .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(csrEncoded),
+ Return(ByMove(ScopedAStatus::ok()))));
+
+ auto [csr, csrErrMsg] =
+ getCsr("default", mockRpc.get(),
+ /*selfTest=*/true, /*allowDegenerate=*/false, /*requireUdsCerts=*/false);
+ ASSERT_EQ(csr, nullptr);
+ ASSERT_THAT(csrErrMsg, testing::HasSubstr("challenges do not match"));
+}
+
+TEST(LibRkpFactoryExtractionTests, parseCommaDelimitedString) {
+ const auto& rpcNames = "default,avf,,default,Strongbox,strongbox,,";
+ const auto& rpcSet = parseCommaDelimited(rpcNames);
+
+ ASSERT_EQ(rpcSet.size(), 4);
+ ASSERT_TRUE(rpcSet.count("") == 0);
+ ASSERT_TRUE(rpcSet.count("default") == 1);
+ ASSERT_TRUE(rpcSet.count("avf") == 1);
+ ASSERT_TRUE(rpcSet.count("strongbox") == 1);
+ ASSERT_TRUE(rpcSet.count("Strongbox") == 1);
+}
diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp
index 1cb1144..599b52a 100644
--- a/provisioner/rkp_factory_extraction_tool.cpp
+++ b/provisioner/rkp_factory_extraction_tool.cpp
@@ -26,6 +26,7 @@
#include <future>
#include <string>
+#include <unordered_set>
#include <vector>
#include "DrmRkpAdapter.h"
@@ -33,18 +34,23 @@
using aidl::android::hardware::drm::IDrmFactory;
using aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent;
+using aidl::android::hardware::security::keymint::RpcHardwareInfo;
using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild;
-
-using namespace cppbor;
-using namespace cppcose;
+using aidl::android::hardware::security::keymint::remote_prov::RKPVM_INSTANCE_NAME;
DEFINE_string(output_format, "build+csr", "How to format the output. Defaults to 'build+csr'.");
DEFINE_bool(self_test, true,
"If true, this tool performs a self-test, validating the payload for correctness. "
"This checks that the device on the factory line is producing valid output "
"before attempting to upload the output to the device info service.");
+DEFINE_bool(allow_degenerate, true,
+ "If true, self_test validation will allow degenerate DICE chains in the CSR.");
DEFINE_string(serialno_prop, "ro.serialno",
"The property of getting serial number. Defaults to 'ro.serialno'.");
+DEFINE_string(require_uds_certs, "",
+ "The comma-delimited names of remotely provisioned "
+ "components whose UDS certificate chains are required to be present in the CSR. "
+ "Example: avf,default,strongbox");
namespace {
@@ -57,15 +63,15 @@
return std::string(descriptor) + "/" + name;
}
-void writeOutput(const std::string instance_name, const Array& csr) {
+void writeOutput(const std::string instance_name, const cppbor::Array& csr) {
if (FLAGS_output_format == kBinaryCsrOutput) {
auto bytes = csr.encode();
std::copy(bytes.begin(), bytes.end(), std::ostream_iterator<char>(std::cout));
} else if (FLAGS_output_format == kBuildPlusCsr) {
auto [json, error] = jsonEncodeCsrWithBuild(instance_name, csr, FLAGS_serialno_prop);
if (!error.empty()) {
- std::cerr << "Error JSON encoding the output: " << error;
- exit(1);
+ std::cerr << "Error JSON encoding the output: " << error << std::endl;
+ exit(-1);
}
std::cout << json << std::endl;
} else {
@@ -73,20 +79,28 @@
std::cerr << "Valid formats:" << std::endl;
std::cerr << " " << kBinaryCsrOutput << std::endl;
std::cerr << " " << kBuildPlusCsr << std::endl;
- exit(1);
+ exit(-1);
}
}
-void getCsrForIRpc(const char* descriptor, const char* name, IRemotelyProvisionedComponent* irpc) {
+void getCsrForIRpc(const char* descriptor, const char* name, IRemotelyProvisionedComponent* irpc,
+ bool requireUdsCerts) {
+ auto fullName = getFullServiceName(descriptor, name);
// AVF RKP HAL is not always supported, so we need to check if it is supported before
// generating the CSR.
- if (std::string(name) == "avf" && !isRemoteProvisioningSupported(irpc)) {
- return;
+ if (fullName == RKPVM_INSTANCE_NAME) {
+ RpcHardwareInfo hwInfo;
+ auto status = irpc->getHardwareInfo(&hwInfo);
+ if (!status.isOk()) {
+ return;
+ }
}
- auto [request, errMsg] = getCsr(name, irpc, FLAGS_self_test);
- auto fullName = getFullServiceName(descriptor, name);
+
+ auto [request, errMsg] =
+ getCsr(name, irpc, FLAGS_self_test, FLAGS_allow_degenerate, requireUdsCerts);
if (!request) {
- std::cerr << "Unable to build CSR for '" << fullName << ": " << errMsg << std::endl;
+ std::cerr << "Unable to build CSR for '" << fullName << "': " << errMsg << ", exiting."
+ << std::endl;
exit(-1);
}
@@ -95,23 +109,33 @@
// Callback for AServiceManager_forEachDeclaredInstance that writes out a CSR
// for every IRemotelyProvisionedComponent.
-void getCsrForInstance(const char* name, void* /*context*/) {
+void getCsrForInstance(const char* name, void* context) {
auto fullName = getFullServiceName(IRemotelyProvisionedComponent::descriptor, name);
- std::future<AIBinder*> wait_for_service_func =
+ std::future<AIBinder*> waitForServiceFunc =
std::async(std::launch::async, AServiceManager_waitForService, fullName.c_str());
- if (wait_for_service_func.wait_for(std::chrono::seconds(10)) == std::future_status::timeout) {
- std::cerr << "Wait for service timed out after 10 seconds: " << fullName;
+ if (waitForServiceFunc.wait_for(std::chrono::seconds(10)) == std::future_status::timeout) {
+ std::cerr << "Wait for service timed out after 10 seconds: '" << fullName << "', exiting."
+ << std::endl;
exit(-1);
}
- AIBinder* rkpAiBinder = wait_for_service_func.get();
+ AIBinder* rkpAiBinder = waitForServiceFunc.get();
::ndk::SpAIBinder rkp_binder(rkpAiBinder);
- auto rkp_service = IRemotelyProvisionedComponent::fromBinder(rkp_binder);
- if (!rkp_service) {
- std::cerr << "Unable to get binder object for '" << fullName << "', skipping.";
+ auto rkpService = IRemotelyProvisionedComponent::fromBinder(rkp_binder);
+ if (!rkpService) {
+ std::cerr << "Unable to get binder object for '" << fullName << "', exiting." << std::endl;
exit(-1);
}
- getCsrForIRpc(IRemotelyProvisionedComponent::descriptor, name, rkp_service.get());
+ if (context == nullptr) {
+ std::cerr << "Unable to get context for '" << fullName << "', exiting." << std::endl;
+ exit(-1);
+ }
+
+ auto requireUdsCertsRpcNames = static_cast<std::unordered_set<std::string>*>(context);
+ auto requireUdsCerts = requireUdsCertsRpcNames->count(name) != 0;
+ requireUdsCertsRpcNames->erase(name);
+ getCsrForIRpc(IRemotelyProvisionedComponent::descriptor, name, rkpService.get(),
+ requireUdsCerts);
}
} // namespace
@@ -119,12 +143,21 @@
int main(int argc, char** argv) {
gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags=*/true);
- AServiceManager_forEachDeclaredInstance(IRemotelyProvisionedComponent::descriptor,
- /*context=*/nullptr, getCsrForInstance);
+ auto requireUdsCertsRpcNames = parseCommaDelimited(FLAGS_require_uds_certs);
- // Append drm csr's
- for (auto const& e : android::mediadrm::getDrmRemotelyProvisionedComponents()) {
- getCsrForIRpc(IDrmFactory::descriptor, e.first.c_str(), e.second.get());
+ AServiceManager_forEachDeclaredInstance(IRemotelyProvisionedComponent::descriptor,
+ &requireUdsCertsRpcNames, getCsrForInstance);
+
+ // Append drm CSRs
+ for (auto const& [name, irpc] : android::mediadrm::getDrmRemotelyProvisionedComponents()) {
+ auto requireUdsCerts = requireUdsCertsRpcNames.count(name) != 0;
+ requireUdsCertsRpcNames.erase(name);
+ getCsrForIRpc(IDrmFactory::descriptor, name.c_str(), irpc.get(), requireUdsCerts);
+ }
+
+ for (auto const& rpcName : requireUdsCertsRpcNames) {
+ std::cerr << "WARNING: You requested to enforce the presence of UDS Certs for '" << rpcName
+ << "', but no Remotely Provisioned Component had that name." << std::endl;
}
return 0;