summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-28 20:09:15 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-28 20:09:15 +0000
commit6791d0e8d9dbd4d57dad070918a3f85f76f8239a (patch)
tree564b27298c1ad70c065d6e0f5d1c4501ddb0c061
parent39e615410bc0bfe4dad0c66cd29fbe533eca663c (diff)
parent8c6abf187fd1b233a2774b9776640262d0cf54b5 (diff)
downloadsecurity-6791d0e8d9dbd4d57dad070918a3f85f76f8239a.tar.gz
Snap for 8505378 from 8c6abf187fd1b233a2774b9776640262d0cf54b5 to mainline-go-scheduling-release
Change-Id: Iecf55a1aae163f44dc3d902beda625dc3a6d2b66
-rw-r--r--identity/Android.bp2
-rw-r--r--keystore/keystore_cli_v2.cpp2
-rw-r--r--keystore/tests/confirmationui_invocation_test.cpp2
-rw-r--r--keystore/tests/fuzzer/Android.bp4
-rw-r--r--keystore2/Android.bp7
-rw-r--r--keystore2/TEST_MAPPING3
-rw-r--r--keystore2/src/km_compat/km_compat.cpp2
-rw-r--r--keystore2/src/remote_provisioning.rs24
-rw-r--r--keystore2/src/security_level.rs15
-rw-r--r--keystore2/src/service.rs25
-rw-r--r--keystore2/src/utils.rs17
-rw-r--r--keystore2/test_utils/authorizations.rs88
-rw-r--r--keystore2/test_utils/key_generations.rs68
-rw-r--r--keystore2/test_utils/lib.rs11
-rw-r--r--keystore2/tests/legacy_blobs/Android.bp51
-rw-r--r--keystore2/tests/legacy_blobs/AndroidTest.xml34
-rw-r--r--keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs579
-rw-r--r--ondevice-signing/Android.bp1
-rw-r--r--ondevice-signing/StatsReporter.cpp67
-rw-r--r--ondevice-signing/StatsReporter.h44
-rw-r--r--ondevice-signing/odsign_main.cpp25
21 files changed, 1036 insertions, 35 deletions
diff --git a/identity/Android.bp b/identity/Android.bp
index c69ead11..b3b704fb 100644
--- a/identity/Android.bp
+++ b/identity/Android.bp
@@ -59,7 +59,7 @@ cc_binary {
],
static_libs: [
"android.hardware.identity-V4-cpp",
- "android.hardware.keymaster-V4-cpp",
+ "android.hardware.keymaster-V3-cpp",
"libcppbor_external",
],
}
diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp
index 1e9126d0..d01c67d4 100644
--- a/keystore/keystore_cli_v2.cpp
+++ b/keystore/keystore_cli_v2.cpp
@@ -1025,7 +1025,7 @@ int Confirmation(const std::string& promptText, const std::string& extraDataHex,
return 1;
}
- auto listener = std::make_shared<ConfirmationListener>();
+ auto listener = ndk::SharedRefBase::make<ConfirmationListener>();
auto future = listener->get_future();
auto rc = apcService->presentPrompt(listener, promptText, extraData, locale, uiOptionsAsFlags);
diff --git a/keystore/tests/confirmationui_invocation_test.cpp b/keystore/tests/confirmationui_invocation_test.cpp
index 7f8a3738..822e6a4c 100644
--- a/keystore/tests/confirmationui_invocation_test.cpp
+++ b/keystore/tests/confirmationui_invocation_test.cpp
@@ -55,7 +55,7 @@ TEST(ConfirmationInvocationTest, InvokeAndCancel) {
std::string locale("en");
std::vector<uint8_t> extraData{0xaa, 0xff, 0x00, 0x55};
- auto listener = std::make_shared<ConfirmationListener>();
+ auto listener = ndk::SharedRefBase::make<ConfirmationListener>();
auto future = listener->get_future();
diff --git a/keystore/tests/fuzzer/Android.bp b/keystore/tests/fuzzer/Android.bp
index 589cef7a..4116ae14 100644
--- a/keystore/tests/fuzzer/Android.bp
+++ b/keystore/tests/fuzzer/Android.bp
@@ -31,12 +31,12 @@ cc_fuzz {
],
static_libs: [
"libkeystore-wifi-hidl",
- "libutils",
],
shared_libs: [
"android.system.wifi.keystore@1.0",
"libhidlbase",
"liblog",
+ "libutils",
],
fuzz_config: {
cc: [
@@ -51,13 +51,13 @@ cc_defaults {
static_libs: [
"libkeystore-attestation-application-id",
"liblog",
- "libutils",
"libbase",
"libhidlbase",
],
shared_libs: [
"libbinder",
"libcrypto",
+ "libutils",
],
fuzz_config: {
cc: [
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 74aa4bde..e6cb4fb5 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -79,7 +79,10 @@ rust_library {
name: "libkeystore2_test_utils",
crate_name: "keystore2_test_utils",
srcs: ["test_utils/lib.rs"],
+ defaults: ["keymint_use_latest_hal_aidl_rust"],
rustlibs: [
+ "android.system.keystore2-V2-rust",
+ "libbinder_rs",
"libkeystore2_selinux",
"liblog_rust",
"libnix",
@@ -105,11 +108,14 @@ rust_library {
rust_test {
name: "keystore2_test_utils_test",
srcs: ["test_utils/lib.rs"],
+ defaults: ["keymint_use_latest_hal_aidl_rust"],
test_suites: ["general-tests"],
require_root: true,
auto_gen_config: true,
compile_multilib: "first",
rustlibs: [
+ "android.system.keystore2-V2-rust",
+ "libbinder_rs",
"libkeystore2_selinux",
"liblog_rust",
"libnix",
@@ -172,4 +178,5 @@ rust_binary {
"liblegacykeystore-rust",
"librusqlite",
],
+ afdo: true,
}
diff --git a/keystore2/TEST_MAPPING b/keystore2/TEST_MAPPING
index 049adc74..5d0a7dd3 100644
--- a/keystore2/TEST_MAPPING
+++ b/keystore2/TEST_MAPPING
@@ -13,6 +13,9 @@
"name": "keystore2_test_utils_test"
},
{
+ "name": "keystore2_legacy_blobs_test"
+ },
+ {
"name": "CtsIdentityTestCases"
},
{
diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp
index 192f4455..6d0630b4 100644
--- a/keystore2/src/km_compat/km_compat.cpp
+++ b/keystore2/src/km_compat/km_compat.cpp
@@ -126,7 +126,7 @@ bool isKeyCreationParameter(const KMV1::KeyParameter& param) {
case Tag::TRUSTED_CONFIRMATION_REQUIRED:
case Tag::UNLOCKED_DEVICE_REQUIRED:
case Tag::CREATION_DATETIME:
- case Tag::UNIQUE_ID:
+ case Tag::INCLUDE_UNIQUE_ID:
case Tag::IDENTITY_CREDENTIAL_KEY:
case Tag::STORAGE_KEY:
case Tag::MAC_LENGTH:
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index be23ae5b..b47b3731 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -78,11 +78,27 @@ impl RemProvState {
self.km_uuid
}
+ fn is_rkp_only(&self) -> bool {
+ let default_value = false;
+
+ let property_name = match self.security_level {
+ SecurityLevel::STRONGBOX => "remote_provisioning.strongbox.rkp_only",
+ SecurityLevel::TRUSTED_ENVIRONMENT => "remote_provisioning.tee.rkp_only",
+ _ => return default_value,
+ };
+
+ rustutils::system_properties::read_bool(property_name, default_value)
+ .unwrap_or(default_value)
+ }
+
/// Checks if remote provisioning is enabled and partially caches the result. On a hybrid system
/// remote provisioning can flip from being disabled to enabled depending on responses from the
/// server, so unfortunately caching the presence or absence of the HAL is not enough to fully
/// make decisions about the state of remote provisioning during runtime.
fn check_rem_prov_enabled(&self, db: &mut KeystoreDB) -> Result<bool> {
+ if self.is_rkp_only() {
+ return Ok(true);
+ }
if !self.is_hal_present.load(Ordering::Relaxed)
|| get_remotely_provisioned_component(&self.security_level).is_err()
{
@@ -137,12 +153,12 @@ impl RemProvState {
match get_rem_prov_attest_key(key.domain, caller_uid, db, &self.km_uuid) {
Err(e) => {
log::error!(
- concat!(
- "In get_remote_provisioning_key_and_certs: Failed to get ",
- "attestation key. {:?}"
- ),
+ "In get_remote_provisioning_key_and_certs: Error occurred: {:?}",
e
);
+ if self.is_rkp_only() {
+ return Err(e);
+ }
log_rkp_error_stats(MetricsRkpError::FALL_BACK_DURING_HYBRID);
Ok(None)
}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 1f6be32b..28de1ec8 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -27,7 +27,8 @@ use crate::metrics_store::log_key_creation_event_stats;
use crate::remote_provisioning::RemProvState;
use crate::super_key::{KeyBlob, SuperKeyManager};
use crate::utils::{
- check_device_attestation_permissions, check_key_permission, is_device_id_attestation_tag,
+ check_device_attestation_permissions, check_key_permission,
+ check_unique_id_attestation_permissions, is_device_id_attestation_tag,
key_characteristics_to_internal, uid_to_android_user, watchdog as wd,
};
use crate::{
@@ -452,10 +453,14 @@ impl KeystoreSecurityLevel {
}
if params.iter().any(|kp| kp.tag == Tag::INCLUDE_UNIQUE_ID) {
- check_key_permission(KeyPerm::GenUniqueId, key, &None).context(concat!(
- "In add_required_parameters: ",
- "Caller does not have the permission to generate a unique ID"
- ))?;
+ if check_key_permission(KeyPerm::GenUniqueId, key, &None).is_err()
+ && check_unique_id_attestation_permissions().is_err()
+ {
+ return Err(Error::perm()).context(
+ "In add_required_parameters: \
+ Caller does not have the permission to generate a unique ID",
+ );
+ }
if self.id_rotation_state.had_factory_reset_since_id_rotation().context(
"In add_required_parameters: Call to had_factory_reset_since_id_rotation failed.",
)? {
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index 79e76923..d634e0c0 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -276,22 +276,19 @@ impl KeystoreService {
// If the first check fails we check if the caller has the list permission allowing to list
// any namespace. In that case we also adjust the queried namespace if a specific uid was
// selected.
- match check_key_permission(KeyPerm::GetInfo, &k, &None) {
- Err(e) => {
- if let Some(selinux::Error::PermissionDenied) =
- e.root_cause().downcast_ref::<selinux::Error>()
- {
- check_keystore_permission(KeystorePerm::List)
- .context("In list_entries: While checking keystore permission.")?;
- if namespace != -1 {
- k.nspace = namespace;
- }
- } else {
- return Err(e).context("In list_entries: While checking key permission.")?;
+ if let Err(e) = check_key_permission(KeyPerm::GetInfo, &k, &None) {
+ if let Some(selinux::Error::PermissionDenied) =
+ e.root_cause().downcast_ref::<selinux::Error>() {
+
+ check_keystore_permission(KeystorePerm::List)
+ .context("In list_entries: While checking keystore permission.")?;
+ if namespace != -1 {
+ k.nspace = namespace;
}
+ } else {
+ return Err(e).context("In list_entries: While checking key permission.")?;
}
- Ok(()) => {}
- };
+ }
DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace))
}
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index a312c4b8..9db2eb9d 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -107,9 +107,20 @@ pub fn is_device_id_attestation_tag(tag: Tag) -> bool {
}
/// This function checks whether the calling app has the Android permissions needed to attest device
-/// identifiers. It throws an error if the permissions cannot be verified, or if the caller doesn't
-/// have the right permissions, and returns silently otherwise.
+/// 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")
+}
+
+/// 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")
+}
+
+fn check_android_permission(permission: &str) -> anyhow::Result<()> {
let permission_controller: Strong<dyn IPermissionController::IPermissionController> =
binder::get_interface("permission")?;
@@ -119,7 +130,7 @@ pub fn check_device_attestation_permissions() -> anyhow::Result<()> {
500,
);
permission_controller.checkPermission(
- "android.permission.READ_PRIVILEGED_PHONE_STATE",
+ permission,
ThreadState::get_calling_pid(),
ThreadState::get_calling_uid() as i32,
)
diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs
new file mode 100644
index 00000000..4fbe1241
--- /dev/null
+++ b/keystore2/test_utils/authorizations.rs
@@ -0,0 +1,88 @@
+// 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.
+
+//! This module implements test utils to create Autherizations.
+
+use std::ops::Deref;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyParameter::KeyParameter,
+ KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, Tag::Tag,
+};
+
+/// Helper struct to create set of Authorizations.
+pub struct AuthSetBuilder(Vec<KeyParameter>);
+
+impl Default for AuthSetBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl AuthSetBuilder {
+ /// Creates new Authorizations list.
+ pub fn new() -> Self {
+ Self(Vec::new())
+ }
+
+ /// Add Purpose.
+ pub fn purpose(mut self, p: KeyPurpose) -> Self {
+ self.0.push(KeyParameter { tag: Tag::PURPOSE, value: KeyParameterValue::KeyPurpose(p) });
+ self
+ }
+
+ /// Add Digest.
+ pub fn digest(mut self, d: Digest) -> Self {
+ self.0.push(KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(d) });
+ self
+ }
+
+ /// Add Algorithm.
+ pub fn algorithm(mut self, a: Algorithm) -> Self {
+ self.0.push(KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(a) });
+ self
+ }
+
+ /// Add EC-Curve.
+ pub fn ec_curve(mut self, e: EcCurve) -> Self {
+ self.0.push(KeyParameter { tag: Tag::EC_CURVE, value: KeyParameterValue::EcCurve(e) });
+ self
+ }
+
+ /// Add Attestation-Challenge.
+ pub fn attestation_challenge(mut self, b: Vec<u8>) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::ATTESTATION_CHALLENGE,
+ value: KeyParameterValue::Blob(b),
+ });
+ self
+ }
+
+ /// Add Attestation-ID.
+ pub fn attestation_app_id(mut self, b: Vec<u8>) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::ATTESTATION_APPLICATION_ID,
+ value: KeyParameterValue::Blob(b),
+ });
+ self
+ }
+}
+
+impl Deref for AuthSetBuilder {
+ type Target = Vec<KeyParameter>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs
new file mode 100644
index 00000000..f49aa9ff
--- /dev/null
+++ b/keystore2/test_utils/key_generations.rs
@@ -0,0 +1,68 @@
+// 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.
+
+//! This module implements test utils to generate various types of keys.
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
+ KeyMetadata::KeyMetadata,
+};
+
+use crate::authorizations::AuthSetBuilder;
+
+const SELINUX_SHELL_NAMESPACE: i64 = 1;
+
+/// Generate attested EC Key blob using given security level with below key parameters -
+/// Purposes: SIGN and VERIFY
+/// Digest: SHA_2_256
+/// Curve: P_256
+pub fn generate_ec_p256_signing_key_with_attestation(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+) -> binder::Result<KeyMetadata> {
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+ let gen_params = AuthSetBuilder::new()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256)
+ .attestation_challenge(att_challenge.to_vec())
+ .attestation_app_id(att_app_id.to_vec());
+
+ match sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::BLOB,
+ nspace: SELINUX_SHELL_NAMESPACE,
+ alias: None,
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ ) {
+ Ok(key_metadata) => {
+ assert!(key_metadata.certificate.is_some());
+ assert!(key_metadata.certificateChain.is_some());
+ assert!(key_metadata.key.blob.is_some());
+
+ Ok(key_metadata)
+ }
+ Err(e) => Err(e),
+ }
+}
diff --git a/keystore2/test_utils/lib.rs b/keystore2/test_utils/lib.rs
index a355544b..c63bfacc 100644
--- a/keystore2/test_utils/lib.rs
+++ b/keystore2/test_utils/lib.rs
@@ -19,8 +19,14 @@ use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::{env::temp_dir, ops::Deref};
+use android_system_keystore2::aidl::android::system::keystore2::IKeystoreService::IKeystoreService;
+
+pub mod authorizations;
+pub mod key_generations;
pub mod run_as;
+static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default";
+
/// Represents the lifecycle of a temporary directory for testing.
#[derive(Debug)]
pub struct TempDir {
@@ -104,3 +110,8 @@ impl Deref for PathBuilder {
&self.0
}
}
+
+/// Get Keystore2 service.
+pub fn get_keystore_service() -> binder::Strong<dyn IKeystoreService> {
+ binder::get_interface(KS2_SERVICE_NAME).unwrap()
+}
diff --git a/keystore2/tests/legacy_blobs/Android.bp b/keystore2/tests/legacy_blobs/Android.bp
new file mode 100644
index 00000000..9322a411
--- /dev/null
+++ b/keystore2/tests/legacy_blobs/Android.bp
@@ -0,0 +1,51 @@
+// 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.
+
+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_test {
+ name: "keystore2_legacy_blobs_test",
+ srcs: ["keystore2_legacy_blob_tests.rs"],
+ test_suites: [
+ "general-tests",
+ ],
+ // auto_gen_config: true,
+ test_config: "AndroidTest.xml",
+
+ rustlibs: [
+ "libkeystore2_with_test_utils",
+ "libkeystore2_crypto_rust",
+ "android.system.keystore2-V2-rust",
+ "android.hardware.security.keymint-V2-rust",
+ "android.security.maintenance-rust",
+ "android.security.authorization-rust",
+ "librustutils",
+ "libkeystore2_test_utils",
+ "libnix",
+ "libanyhow",
+ "libbinder_rs",
+ "liblazy_static",
+ "liblibc",
+ "libserde",
+ "libthiserror",
+ ],
+ require_root: true,
+}
diff --git a/keystore2/tests/legacy_blobs/AndroidTest.xml b/keystore2/tests/legacy_blobs/AndroidTest.xml
new file mode 100644
index 00000000..ea83fbf7
--- /dev/null
+++ b/keystore2/tests/legacy_blobs/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config to run keystore2_legacy_blobs_test device tests.">
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option
+ name="push"
+ value="keystore2_legacy_blobs_test->/data/local/tmp/keystore2_legacy_blobs_test"
+ />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+ <option name="test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="keystore2_legacy_blobs_test" />
+ <option name="native-test-flag" value="--test-threads=1" />
+ </test>
+</configuration>
diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
new file mode 100644
index 00000000..6def39e2
--- /dev/null
+++ b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
@@ -0,0 +1,579 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use nix::unistd::{getuid, Gid, Uid};
+use rustutils::users::AID_USER_OFFSET;
+use serde::{Deserialize, Serialize};
+
+use std::ops::Deref;
+use std::path::PathBuf;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel;
+
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor,
+};
+
+use android_security_maintenance::aidl::android::security::maintenance::{
+ IKeystoreMaintenance::IKeystoreMaintenance, UserState::UserState,
+};
+
+use android_security_authorization::aidl::android::security::authorization::{
+ IKeystoreAuthorization::IKeystoreAuthorization, LockScreenEvent::LockScreenEvent,
+};
+
+use keystore2::key_parameter::KeyParameter as KsKeyparameter;
+use keystore2::legacy_blob::test_utils::legacy_blob_test_vectors::*;
+use keystore2::legacy_blob::test_utils::*;
+use keystore2::legacy_blob::LegacyKeyCharacteristics;
+use keystore2::utils::AesGcm;
+use keystore2_crypto::{Password, ZVec};
+
+use keystore2_test_utils::get_keystore_service;
+use keystore2_test_utils::key_generations;
+use keystore2_test_utils::run_as;
+
+static USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance";
+static AUTH_SERVICE_NAME: &str = "android.security.authorization";
+const SELINUX_SHELL_NAMESPACE: i64 = 1;
+
+fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> {
+ binder::get_interface(USER_MANAGER_SERVICE_NAME).unwrap()
+}
+
+fn get_authorization() -> binder::Strong<dyn IKeystoreAuthorization> {
+ binder::get_interface(AUTH_SERVICE_NAME).unwrap()
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+struct KeygenResult {
+ cert: Vec<u8>,
+ cert_chain: Vec<u8>,
+ key_parameters: Vec<KsKeyparameter>,
+}
+
+struct TestKey(ZVec);
+
+impl keystore2::utils::AesGcmKey for TestKey {
+ fn key(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+impl Deref for TestKey {
+ type Target = [u8];
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn keystore2_restart_service() {
+ let output = std::process::Command::new("pidof")
+ .arg("keystore2")
+ .output()
+ .expect("failed to execute pidof keystore2");
+
+ let id = String::from_utf8(output.stdout).unwrap();
+ let id: String = id.chars().filter(|c| c.is_digit(10)).collect();
+
+ let _status = std::process::Command::new("kill").arg("-9").arg(id).status().unwrap();
+
+ // Loop till we find keystore2 service up and running.
+ loop {
+ let output = std::process::Command::new("pidof")
+ .arg("keystore2")
+ .output()
+ .expect("failed to execute pidof keystore2");
+
+ if output.status.code() == Some(0) {
+ break;
+ }
+ }
+}
+
+/// Create legacy blobs file layout for a user with user-id 99 and app-id 10001 with
+/// user-cert, ca-certs and encrypted key-characteristics files and tries to import
+/// these legacy blobs under user context.
+///
+/// Expected File layout for user with user-id "98" and app-id "10001" and key-alias
+/// "authbound":
+/// /data/misc/keystore/user_99/.masterkey
+/// /data/misc/keystore/user_99/9910001_USRPKEY_authbound
+/// /data/misc/keystore/user_99/.9910001_chr_USRPKEY_authbound
+/// /data/misc/keystore/user_99/9910001_USRCERT_authbound
+/// /data/misc/keystore/user_99/9910001_CACERT_authbound
+///
+/// Test performs below tasks -
+/// With su context it performs following tasks -
+/// 1. Remove this user if already exist.
+/// 2. Generate a key-blob, user cert-blob and ca-cert-blob to store it in legacy blobs file
+/// layout.
+/// 3. Prepare file layout using generated key-blob, user cert and ca certs.
+/// 4. Restart the keystore2 service to make it detect the populated legacy blobs.
+/// 5. Inform the keystore2 service about the user and unlock the user.
+/// With user-99 context it performs following tasks -
+/// 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
+/// 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.
+/// 9. Confirm keystore2 service cleanup the legacy blobs after successful import.
+#[test]
+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");
+ if path_buf.as_path().is_dir() {
+ 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 keystore2 = get_keystore_service();
+ let sec_level = keystore2
+ .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT)
+ .unwrap();
+ // Generate Key BLOB and prepare legacy keystore blob files.
+ let key_metadata =
+ key_generations::generate_ec_p256_signing_key_with_attestation(&sec_level)
+ .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(Some(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(),
+ )
+ .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();
+ }
+
+ 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(),
+ key_metadata.certificateChain.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.onLockScreenEvent(LockScreenEvent::UNLOCK, 99, Some(PASSWORD), None)
+ {
+ Ok(result) => {
+ println!("Unlock Result: {:?}", result);
+ }
+ Err(e) => {
+ panic!("Unlock should have succeeded: {:?}", e);
+ }
+ }
+
+ let maint_service = get_maintenance();
+ assert_eq!(Ok(UserState(1)), maint_service.getState(99));
+
+ 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(),
+ 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));
+
+ let test_alias = "authbound";
+ let keystore2 = get_keystore_service();
+
+ 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(),
+ 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
+ );
+
+ // 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;
+ }
+ }
+ if !present_in_gen_params {
+ legacy_file_key_params.push(param.clone());
+ }
+ }
+ }
+ _ => {
+ panic!("Expecting file characteristics");
+ }
+ }
+
+ // 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);
+ }
+
+ 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);
+ }
+ };
+ })
+ };
+
+ // 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() {
+ panic!("Keystore service should have deleted this dir {:?}", path_buf);
+ }
+ Ok(())
+}
+
+/// Create legacy blobs file layout for a user with user-id 98 and app-id 10001 with encrypted
+/// user-cert and ca-certs files and tries to import these legacy blobs under user context.
+///
+/// Expected File layout for user with user-id "98" and app-id "10001" and key-alias
+/// "authboundcertenc":
+/// /data/misc/keystore/user_98/.masterkey
+/// /data/misc/keystore/user_98/9810001_USRPKEY_authboundcertenc
+/// /data/misc/keystore/user_98/.9810001_chr_USRPKEY_authboundcertenc
+/// /data/misc/keystore/user_98/9810001_USRCERT_authboundcertenc
+/// /data/misc/keystore/user_98/9810001_CACERT_authboundcertenc
+///
+/// Test performs below tasks -
+/// With su context it performs following tasks -
+/// 1. Remove this user if already exist.
+/// 2. Generate a key-blob, user cert-blob and ca-cert-blob to store it in legacy blobs file
+/// layout.
+/// 3. Prepare file layout using generated key-blob, user cert and ca certs.
+/// 4. Restart the keystore2 service to make it detect the populated legacy blobs.
+/// 5. Inform the keystore2 service about the user and unlock the user.
+/// With user-98 context it performs following tasks -
+/// 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
+/// 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.
+/// 9. Confirm keystore2 service cleanup the legacy blobs after successful import.
+#[test]
+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");
+ if path_buf.as_path().is_dir() {
+ 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 keystore2 = get_keystore_service();
+ let sec_level = keystore2
+ .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT)
+ .unwrap();
+ // Generate Key BLOB and prepare legacy keystore blob files.
+ let key_metadata =
+ key_generations::generate_ec_p256_signing_key_with_attestation(&sec_level)
+ .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(Some(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(),
+ )
+ .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();
+ }
+
+ 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,
+ key_metadata.certificateChain.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.onLockScreenEvent(LockScreenEvent::UNLOCK, 98, Some(PASSWORD), None)
+ {
+ Ok(result) => {
+ println!("Unlock Result: {:?}", result);
+ }
+ Err(e) => {
+ panic!("Unlock should have succeeded: {:?}", e);
+ }
+ }
+
+ let maint_service = get_maintenance();
+ assert_eq!(Ok(UserState(1)), maint_service.getState(98));
+
+ 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(),
+ 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));
+
+ let test_alias = "authboundcertenc";
+ let keystore2 = get_keystore_service();
+
+ 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(),
+ 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);
+ }
+ _ => {
+ panic!("Expecting file characteristics");
+ }
+ }
+ }
+ Err(s) => {
+ panic!("getKeyEntry should have succeeded. {:?}", s);
+ }
+ };
+ })
+ };
+
+ // 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() {
+ panic!("Keystore service should have deleted this dir {:?}", path_buf);
+ }
+ Ok(())
+}
diff --git a/ondevice-signing/Android.bp b/ondevice-signing/Android.bp
index bdc94b7e..d73f8fe5 100644
--- a/ondevice-signing/Android.bp
+++ b/ondevice-signing/Android.bp
@@ -112,6 +112,7 @@ cc_binary {
"KeystoreKey.cpp",
"KeystoreHmacKey.cpp",
"odsign_main.cpp",
+ "StatsReporter.cpp",
],
header_libs: ["odrefresh_headers"],
diff --git a/ondevice-signing/StatsReporter.cpp b/ondevice-signing/StatsReporter.cpp
new file mode 100644
index 00000000..65e645a3
--- /dev/null
+++ b/ondevice-signing/StatsReporter.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "StatsReporter.h"
+#include <android-base/logging.h>
+#include <stdlib.h>
+#include <string>
+#include <sys/stat.h>
+
+// Keep these constant in sync with COMPOS_METRIC_NAME & METRICS_FILE in OdsignStatsLogger.java.
+constexpr const char* kOdsignMetricsFile = "/data/misc/odsign/metrics/odsign-metrics.txt";
+constexpr const char* kComposMetricName = "comp_os_artifacts_check_record";
+
+StatsReporter::~StatsReporter() {
+ if (comp_os_artifacts_check_record_ == nullptr) {
+ LOG(INFO) << "Metrics report is empty";
+
+ // Remove the metrics file if any old version of the file already exists
+ if (std::filesystem::remove(kOdsignMetricsFile) != 0 &&
+ !((errno = ENOENT) || errno == ENOTDIR)) {
+ PLOG(ERROR) << "Could not remove already present file";
+ }
+ return;
+ }
+
+ std::ofstream odsign_metrics_file_;
+ odsign_metrics_file_.open(kOdsignMetricsFile, std::ios::trunc);
+ if (!odsign_metrics_file_) {
+ PLOG(ERROR) << "Could not open file: " << kOdsignMetricsFile;
+ return;
+ }
+
+ odsign_metrics_file_ << kComposMetricName << ' ';
+ odsign_metrics_file_ << comp_os_artifacts_check_record_->current_artifacts_ok << ' ';
+ odsign_metrics_file_ << comp_os_artifacts_check_record_->comp_os_pending_artifacts_exists
+ << ' ';
+ odsign_metrics_file_ << comp_os_artifacts_check_record_->use_comp_os_generated_artifacts
+ << '\n';
+ if (chmod(kOdsignMetricsFile, 0644) != 0) {
+ PLOG(ERROR) << "Could not set correct file permissions for " << kOdsignMetricsFile;
+ return;
+ }
+ odsign_metrics_file_.close();
+ if (!odsign_metrics_file_) {
+ PLOG(ERROR) << "Failed to close the file";
+ }
+}
+
+StatsReporter::CompOsArtifactsCheckRecord* StatsReporter::GetComposArtifactsCheckRecord() {
+ if (comp_os_artifacts_check_record_ == nullptr) {
+ comp_os_artifacts_check_record_ = std::make_unique<CompOsArtifactsCheckRecord>();
+ }
+ return comp_os_artifacts_check_record_.get();
+}
diff --git a/ondevice-signing/StatsReporter.h b/ondevice-signing/StatsReporter.h
new file mode 100644
index 00000000..2682b963
--- /dev/null
+++ b/ondevice-signing/StatsReporter.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <fstream>
+
+// Class to store CompOsArtifactsCheck related metrics.
+// These are flushed to a file kOdsignMetricsFile and consumed by
+// System Server (in class OdsignStatsLogger) & sent to statsd.
+class StatsReporter {
+ public:
+ // Keep sync with EarlyBootCompOsArtifactsCheckReported
+ // definition in proto_logging/stats/atoms.proto.
+ struct CompOsArtifactsCheckRecord {
+ bool current_artifacts_ok = false;
+ bool comp_os_pending_artifacts_exists = false;
+ bool use_comp_os_generated_artifacts = false;
+ };
+
+ // The report is flushed (from buffer) into a file by the destructor.
+ ~StatsReporter();
+
+ // Get pointer to comp_os_artifacts_check_record, caller can then modify it.
+ // Note: pointer remains valid for the lifetime of this StatsReporter.
+ CompOsArtifactsCheckRecord* GetComposArtifactsCheckRecord();
+
+ private:
+ // Temporary buffer which stores the metrics.
+ std::unique_ptr<CompOsArtifactsCheckRecord> comp_os_artifacts_check_record_;
+};
diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp
index fc558462..04679a59 100644
--- a/ondevice-signing/odsign_main.cpp
+++ b/ondevice-signing/odsign_main.cpp
@@ -33,6 +33,7 @@
#include "CertUtils.h"
#include "KeystoreKey.h"
+#include "StatsReporter.h"
#include "VerityUtils.h"
#include "odsign_info.pb.h"
@@ -365,19 +366,26 @@ Result<OdsignInfo> getComposInfo() {
return compos_info;
}
-art::odrefresh::ExitCode checkCompOsPendingArtifacts(const SigningKey& signing_key,
- bool* digests_verified) {
+art::odrefresh::ExitCode CheckCompOsPendingArtifacts(const SigningKey& signing_key,
+ bool* digests_verified,
+ StatsReporter* stats_reporter) {
+ StatsReporter::CompOsArtifactsCheckRecord* compos_check_record =
+ stats_reporter->GetComposArtifactsCheckRecord();
+
if (!directoryHasContent(kCompOsPendingArtifactsDir)) {
// No pending CompOS artifacts, all that matters is the current ones.
return checkArtifacts();
}
+ compos_check_record->comp_os_pending_artifacts_exists = true;
+
// CompOS has generated some artifacts that may, or may not, match the
// current state. But if there are already valid artifacts present the
// CompOS ones are redundant.
art::odrefresh::ExitCode odrefresh_status = checkArtifacts();
if (odrefresh_status != art::odrefresh::ExitCode::kCompilationRequired) {
if (odrefresh_status == art::odrefresh::ExitCode::kOkay) {
+ compos_check_record->current_artifacts_ok = true;
LOG(INFO) << "Current artifacts are OK, deleting pending artifacts";
removeDirectory(kCompOsPendingArtifactsDir);
}
@@ -432,6 +440,7 @@ art::odrefresh::ExitCode checkCompOsPendingArtifacts(const SigningKey& signing_k
// are pretty bad.
return art::odrefresh::ExitCode::kCleanupFailed;
}
+ compos_check_record->use_comp_os_generated_artifacts = true;
LOG(INFO) << "Persisted CompOS digests.";
*digests_verified = true;
return odrefresh_status;
@@ -455,6 +464,9 @@ art::odrefresh::ExitCode checkCompOsPendingArtifacts(const SigningKey& signing_k
} // namespace
int main(int /* argc */, char** argv) {
+ // stats_reporter is a pointer so that we can explicitly delete it
+ // instead of waiting for the program to die & its destrcutor be called
+ auto stats_reporter = std::make_unique<StatsReporter>();
android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
auto errorScopeGuard = []() {
@@ -516,7 +528,14 @@ int main(int /* argc */, char** argv) {
bool digests_verified = false;
art::odrefresh::ExitCode odrefresh_status =
- useCompOs ? checkCompOsPendingArtifacts(*key, &digests_verified) : checkArtifacts();
+ useCompOs ? CheckCompOsPendingArtifacts(*key, &digests_verified, stats_reporter.get())
+ : checkArtifacts();
+
+ // Explicitly reset the pointer - We rely on stats_reporter's
+ // destructor for actually writing the buffered metrics. This will otherwise not be called
+ // if the program doesn't exit normally (for ex, killed by init, which actually happens
+ // because odsign (after it finishes) sets kStopServiceProp instructing init to kill it).
+ stats_reporter.reset();
// The artifacts dir doesn't necessarily need to exist; if the existing
// artifacts on the system partition are valid, those can be used.