diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-28 20:09:15 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-28 20:09:15 +0000 |
commit | 6791d0e8d9dbd4d57dad070918a3f85f76f8239a (patch) | |
tree | 564b27298c1ad70c065d6e0f5d1c4501ddb0c061 | |
parent | 39e615410bc0bfe4dad0c66cd29fbe533eca663c (diff) | |
parent | 8c6abf187fd1b233a2774b9776640262d0cf54b5 (diff) | |
download | security-6791d0e8d9dbd4d57dad070918a3f85f76f8239a.tar.gz |
Snap for 8505378 from 8c6abf187fd1b233a2774b9776640262d0cf54b5 to mainline-go-scheduling-release
Change-Id: Iecf55a1aae163f44dc3d902beda625dc3a6d2b66
-rw-r--r-- | identity/Android.bp | 2 | ||||
-rw-r--r-- | keystore/keystore_cli_v2.cpp | 2 | ||||
-rw-r--r-- | keystore/tests/confirmationui_invocation_test.cpp | 2 | ||||
-rw-r--r-- | keystore/tests/fuzzer/Android.bp | 4 | ||||
-rw-r--r-- | keystore2/Android.bp | 7 | ||||
-rw-r--r-- | keystore2/TEST_MAPPING | 3 | ||||
-rw-r--r-- | keystore2/src/km_compat/km_compat.cpp | 2 | ||||
-rw-r--r-- | keystore2/src/remote_provisioning.rs | 24 | ||||
-rw-r--r-- | keystore2/src/security_level.rs | 15 | ||||
-rw-r--r-- | keystore2/src/service.rs | 25 | ||||
-rw-r--r-- | keystore2/src/utils.rs | 17 | ||||
-rw-r--r-- | keystore2/test_utils/authorizations.rs | 88 | ||||
-rw-r--r-- | keystore2/test_utils/key_generations.rs | 68 | ||||
-rw-r--r-- | keystore2/test_utils/lib.rs | 11 | ||||
-rw-r--r-- | keystore2/tests/legacy_blobs/Android.bp | 51 | ||||
-rw-r--r-- | keystore2/tests/legacy_blobs/AndroidTest.xml | 34 | ||||
-rw-r--r-- | keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs | 579 | ||||
-rw-r--r-- | ondevice-signing/Android.bp | 1 | ||||
-rw-r--r-- | ondevice-signing/StatsReporter.cpp | 67 | ||||
-rw-r--r-- | ondevice-signing/StatsReporter.h | 44 | ||||
-rw-r--r-- | ondevice-signing/odsign_main.cpp | 25 |
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. |