From a42dee61cefd6083648face2d7fb56ffba0f60dd Mon Sep 17 00:00:00 2001 From: Rajesh Nyamagoud Date: Fri, 22 Apr 2022 21:15:55 +0000 Subject: [PATCH] Added tests to attest keys with attestation id. - Generate an RSA/EC attested keys with attestation of the device's identifiers. Test should succeed in generatating a attested key with attestation of device identifier. Test might fail on devices which doesn't support device id attestation with error response code `CANNOT_ATTEST_IDS or INVALID_TAG`. - Try to generate an attested key with attestation of invalid device's identifiers. Test should fail with error response `CANNOT_ATTEST_IDS` - Test to make sure `CANNOT_ATTEST_IDS` error code is returned while trying to generate a key on a device which doesn't support `FEATURE_DEVICE_ID_ATTESTATION`. Bug: 194359114 Test: atest keystore2_client_test Change-Id: Ib57c58d3ea89279eb69db342c3343b8d99ddc639 --- keystore2/test_utils/authorizations.rs | 81 ++++++++ keystore2/test_utils/key_generations.rs | 80 ++++++++ keystore2/tests/Android.bp | 1 + keystore2/tests/ffi_test_utils.cpp | 93 +++++++++ keystore2/tests/ffi_test_utils.hpp | 1 + keystore2/tests/ffi_test_utils.rs | 10 + .../keystore2_client_attest_key_tests.rs | 190 +++++++++++++++++- .../tests/keystore2_client_test_utils.rs | 101 ++++++++++ 8 files changed, 552 insertions(+), 5 deletions(-) diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs index 4608bc5f..514cbd3b 100644 --- a/keystore2/test_utils/authorizations.rs +++ b/keystore2/test_utils/authorizations.rs @@ -161,6 +161,87 @@ impl AuthSetBuilder { .push(KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KeyParameterValue::Integer(l) }); self } + + /// Add Attestation-Device-Brand. + pub fn attestation_device_brand(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_BRAND, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Add Attestation-Device-name. + pub fn attestation_device_name(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_DEVICE, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Add Attestation-Device-Product-Name. + pub fn attestation_device_product_name(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_PRODUCT, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Add Attestation-Device-Serial. + pub fn attestation_device_serial(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_SERIAL, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Add Attestation-Device-IMEI. + pub fn attestation_device_imei(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_IMEI, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Add Attestation-Device-IMEI. + pub fn attestation_device_second_imei(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_SECOND_IMEI, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Add Attestation-Device-MEID. + pub fn attestation_device_meid(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_MEID, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Add Attestation-Device-Manufacturer. + pub fn attestation_device_manufacturer(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_MANUFACTURER, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Add Attestation-Device-Model. + pub fn attestation_device_model(mut self, b: Vec) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_MODEL, + value: KeyParameterValue::Blob(b), + }); + self + } } impl Deref for AuthSetBuilder { diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs index ff40aa17..0a1ffb1f 100644 --- a/keystore2/test_utils/key_generations.rs +++ b/keystore2/test_utils/key_generations.rs @@ -306,6 +306,12 @@ pub enum Error { /// Error code to indicate error while using keystore-engine API. #[error("Failed to perform crypto op using keystore-engine APIs.")] Keystore2EngineOpFailed, + /// Error code to indicate error in attestation-id validation. + #[error("Failed to validate attestation-id.")] + ValidateAttestIdFailed, + /// Error code to indicate error in getting value from attest record. + #[error("Failed to get value from attest record.")] + AttestRecordGetValueFailed, } /// Keystore2 error mapping. @@ -1109,3 +1115,77 @@ pub fn import_aes_keys( Ok(imported_key_aliases) } + +/// Generate attested EC-P_256 key with device id attestation. +pub fn generate_key_with_attest_id( + sec_level: &binder::Strong, + algorithm: Algorithm, + alias: Option, + att_challenge: &[u8], + attest_key: &KeyDescriptor, + attest_id: Tag, + value: Vec, +) -> binder::Result { + assert!(algorithm == Algorithm::RSA || algorithm == Algorithm::EC); + + let mut ec_gen_params; + if algorithm == Algorithm::EC { + ec_gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(att_challenge.to_vec()); + } else { + ec_gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .rsa_public_exponent(65537) + .key_size(2048) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .padding_mode(PaddingMode::RSA_PKCS1_1_5_SIGN) + .attestation_challenge(att_challenge.to_vec()); + } + + match attest_id { + Tag::ATTESTATION_ID_BRAND => { + ec_gen_params = ec_gen_params.attestation_device_brand(value); + } + Tag::ATTESTATION_ID_DEVICE => { + ec_gen_params = ec_gen_params.attestation_device_name(value); + } + Tag::ATTESTATION_ID_PRODUCT => { + ec_gen_params = ec_gen_params.attestation_device_product_name(value); + } + Tag::ATTESTATION_ID_SERIAL => { + ec_gen_params = ec_gen_params.attestation_device_serial(value); + } + Tag::ATTESTATION_ID_MANUFACTURER => { + ec_gen_params = ec_gen_params.attestation_device_manufacturer(value); + } + Tag::ATTESTATION_ID_MODEL => { + ec_gen_params = ec_gen_params.attestation_device_model(value); + } + Tag::ATTESTATION_ID_IMEI => { + ec_gen_params = ec_gen_params.attestation_device_imei(value); + } + Tag::ATTESTATION_ID_SECOND_IMEI => { + ec_gen_params = ec_gen_params.attestation_device_second_imei(value); + } + _ => { + panic!("Unknown attestation id"); + } + } + + sec_level.generateKey( + &KeyDescriptor { domain: Domain::APP, nspace: -1, alias, blob: None }, + Some(attest_key), + &ec_gen_params, + 0, + b"entropy", + ) +} diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp index 0c8b0c80..32c39dc1 100644 --- a/keystore2/tests/Android.bp +++ b/keystore2/tests/Android.bp @@ -58,6 +58,7 @@ rust_test { "libkeymaster_messages", "libcppbor_external", "libkeystore-engine", + "libkeymint_support", ], require_root: true, } diff --git a/keystore2/tests/ffi_test_utils.cpp b/keystore2/tests/ffi_test_utils.cpp index 47dd7a4a..7fbfb8b2 100644 --- a/keystore2/tests/ffi_test_utils.cpp +++ b/keystore2/tests/ffi_test_utils.cpp @@ -18,6 +18,8 @@ #include #include +#include + using aidl::android::hardware::security::keymint::ErrorCode; #define TAG_SEQUENCE 0x30 @@ -503,3 +505,94 @@ bool performCryptoOpUsingKeystoreEngine(int64_t grant_id) { #endif return result; } + +CxxResult getValueFromAttestRecord(rust::Vec cert_buf, int32_t tag) { + CxxResult cxx_result{}; + cxx_result.error = KM_ERROR_OK; + + uint8_t* cert_data = cert_buf.data(); + int cert_data_size = cert_buf.size(); + + std::vector cert_bytes; + cert_bytes.insert(cert_bytes.end(), cert_data, (cert_data + cert_data_size)); + + aidl::android::hardware::security::keymint::X509_Ptr cert( + aidl::android::hardware::security::keymint::test::parse_cert_blob(cert_bytes)); + if (!cert.get()) { + cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; + return cxx_result; + } + + ASN1_OCTET_STRING* attest_rec = + aidl::android::hardware::security::keymint::test::get_attestation_record(cert.get()); + if (!attest_rec) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + aidl::android::hardware::security::keymint::AuthorizationSet att_sw_enforced; + aidl::android::hardware::security::keymint::AuthorizationSet att_hw_enforced; + uint32_t att_attestation_version; + uint32_t att_keymint_version; + aidl::android::hardware::security::keymint::SecurityLevel att_attestation_security_level; + aidl::android::hardware::security::keymint::SecurityLevel att_keymint_security_level; + std::vector att_challenge; + std::vector att_unique_id; + std::vector att_app_id; + + auto error = aidl::android::hardware::security::keymint::parse_attestation_record( + attest_rec->data, attest_rec->length, &att_attestation_version, + &att_attestation_security_level, &att_keymint_version, &att_keymint_security_level, + &att_challenge, &att_sw_enforced, &att_hw_enforced, &att_unique_id); + EXPECT_EQ(ErrorCode::OK, error); + if (error != ErrorCode::OK) { + cxx_result.error = static_cast(error); + return cxx_result; + } + + aidl::android::hardware::security::keymint::Tag auth_tag = + static_cast(tag); + + if (auth_tag == aidl::android::hardware::security::keymint::Tag::ATTESTATION_APPLICATION_ID) { + int pos = att_sw_enforced.find( + aidl::android::hardware::security::keymint::Tag::ATTESTATION_APPLICATION_ID); + if (pos == -1) { + cxx_result.error = KM_ERROR_ATTESTATION_APPLICATION_ID_MISSING; + return cxx_result; + } + aidl::android::hardware::security::keymint::KeyParameter param = att_sw_enforced[pos]; + std::vector val = + param.value.get(); + std::move(val.begin(), val.end(), std::back_inserter(cxx_result.data)); + return cxx_result; + } + + if (auth_tag == aidl::android::hardware::security::keymint::Tag::ATTESTATION_CHALLENGE) { + if (att_challenge.size() == 0) { + cxx_result.error = KM_ERROR_ATTESTATION_CHALLENGE_MISSING; + return cxx_result; + } + std::move(att_challenge.begin(), att_challenge.end(), std::back_inserter(cxx_result.data)); + return cxx_result; + } + + if (auth_tag == aidl::android::hardware::security::keymint::Tag::UNIQUE_ID) { + if (att_unique_id.size() == 0) { + cxx_result.error = KM_ERROR_UNSUPPORTED_TAG; + return cxx_result; + } + std::move(att_unique_id.begin(), att_unique_id.end(), std::back_inserter(cxx_result.data)); + return cxx_result; + } + + int pos = att_hw_enforced.find(auth_tag); + if (pos == -1) { + cxx_result.error = KM_ERROR_UNSUPPORTED_TAG; + return cxx_result; + } + aidl::android::hardware::security::keymint::KeyParameter param = att_hw_enforced[pos]; + std::vector val = + param.value.get(); + std::move(val.begin(), val.end(), std::back_inserter(cxx_result.data)); + return cxx_result; +} diff --git a/keystore2/tests/ffi_test_utils.hpp b/keystore2/tests/ffi_test_utils.hpp index 32f29f43..3ed7edc1 100644 --- a/keystore2/tests/ffi_test_utils.hpp +++ b/keystore2/tests/ffi_test_utils.hpp @@ -10,3 +10,4 @@ CxxResult createWrappedKey(rust::Vec encrypted_secure_key, rust::Vec tag); CxxResult buildAsn1DerEncodedWrappedKeyDescription(); bool performCryptoOpUsingKeystoreEngine(int64_t grant_id); +CxxResult getValueFromAttestRecord(rust::Vec cert_buf, int32_t tag); diff --git a/keystore2/tests/ffi_test_utils.rs b/keystore2/tests/ffi_test_utils.rs index 689713ae..c6521741 100644 --- a/keystore2/tests/ffi_test_utils.rs +++ b/keystore2/tests/ffi_test_utils.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::Tag::Tag; use keystore2_test_utils::key_generations::Error; #[cxx::bridge] @@ -32,6 +33,7 @@ mod ffi { ) -> CxxResult; fn buildAsn1DerEncodedWrappedKeyDescription() -> CxxResult; fn performCryptoOpUsingKeystoreEngine(grant_id: i64) -> bool; + fn getValueFromAttestRecord(cert_buf: Vec, tag: i32) -> CxxResult; } } @@ -87,3 +89,11 @@ pub fn perform_crypto_op_using_keystore_engine(grant_id: i64) -> Result Result, Error> { + let result = ffi::getValueFromAttestRecord(cert_buf.to_vec(), tag.0); + if result.error == 0 && !result.data.is_empty() { + return Ok(result.data); + } + Err(Error::AttestRecordGetValueFailed) +} diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs index 4febd9b5..b8ad90d9 100644 --- a/keystore2/tests/keystore2_client_attest_key_tests.rs +++ b/keystore2/tests/keystore2_client_attest_key_tests.rs @@ -17,21 +17,26 @@ use nix::unistd::getuid; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, - SecurityLevel::SecurityLevel, + SecurityLevel::SecurityLevel, Tag::Tag, }; use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, + Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, + ResponseCode::ResponseCode, }; use keystore2_test_utils::{ authorizations, get_keystore_service, key_generations, key_generations::Error, }; -use crate::ffi_test_utils::validate_certchain; +use crate::ffi_test_utils::{get_value_from_attest_record, validate_certchain}; use crate::{ - keystore2_client_test_utils::app_attest_key_feature_exists, - skip_test_if_no_app_attest_key_feature, + skip_test_if_no_app_attest_key_feature, skip_test_if_no_device_id_attestation_feature, +}; + +use crate::keystore2_client_test_utils::{ + app_attest_key_feature_exists, device_id_attestation_feature_exists, get_attest_id_value, + is_second_imei_id_attestation_required, }; /// Generate RSA and EC attestation keys and use them for signing RSA-signing keys. @@ -480,3 +485,178 @@ fn keystore2_attest_symmetric_key_fail_sys_error() { // Should not have an attestation record. assert!(aes_key_metadata.certificateChain.is_none()); } + +fn get_attestation_ids(keystore2: &binder::Strong) -> Vec<(Tag, Vec)> { + let attest_ids = vec![ + (Tag::ATTESTATION_ID_BRAND, "ro.product.brand_for_attestation"), + (Tag::ATTESTATION_ID_DEVICE, "ro.product.device"), + (Tag::ATTESTATION_ID_PRODUCT, "ro.product.name_for_attestation"), + (Tag::ATTESTATION_ID_SERIAL, "ro.serialno"), + (Tag::ATTESTATION_ID_MANUFACTURER, "ro.product.manufacturer"), + (Tag::ATTESTATION_ID_MODEL, "ro.product.model_for_attestation"), + (Tag::ATTESTATION_ID_IMEI, ""), //Get this value from Telephony service. + (Tag::ATTESTATION_ID_SECOND_IMEI, ""), //Get this value from Telephony service. + ]; + + let mut attest_id_params: Vec<(Tag, Vec)> = vec![]; + for (attest_id, prop_name) in attest_ids { + if attest_id == Tag::ATTESTATION_ID_SECOND_IMEI + && !is_second_imei_id_attestation_required(keystore2) + { + continue; + } + + if let Some(value) = get_attest_id_value(attest_id, prop_name) { + if !value.is_empty() { + attest_id_params.push((attest_id, value)); + } + } + } + + attest_id_params +} + +/// Generate an attested key with attestation of the device's identifiers. Test should succeed in +/// generating a attested key with attestation of device identifiers. Test might fail on devices +/// which don't support device id attestation with error response code `CANNOT_ATTEST_IDS or +/// INVALID_TAG` +fn generate_attested_key_with_device_attest_ids(algorithm: Algorithm) { + skip_test_if_no_device_id_attestation_feature!(); + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let att_challenge: &[u8] = b"foo"; + + let attest_key_metadata = + key_generations::generate_attestation_key(&sec_level, algorithm, att_challenge).unwrap(); + + let attest_id_params = get_attestation_ids(&keystore2); + + for (attest_id, value) in attest_id_params { + // Create RSA/EC key and use attestation key to sign it. + let key_alias = format!("ks_attested_test_key_{}", getuid()); + let key_metadata = + key_generations::map_ks_error(key_generations::generate_key_with_attest_id( + &sec_level, + algorithm, + Some(key_alias), + att_challenge, + &attest_key_metadata.key, + attest_id, + value.clone(), + )) + .unwrap(); + + assert!(key_metadata.certificate.is_some()); + assert!(key_metadata.certificateChain.is_none()); + + let mut cert_chain: Vec = Vec::new(); + cert_chain.extend(key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attest_key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(attest_key_metadata.certificateChain.as_ref().unwrap()); + + validate_certchain(&cert_chain).expect("Error while validating cert chain"); + let attest_id_value = + get_value_from_attest_record(key_metadata.certificate.as_ref().unwrap(), attest_id) + .expect("Attest id verification failed."); + assert_eq!(attest_id_value, value); + } +} + +#[test] +fn keystore2_attest_ecdsa_attestation_id() { + generate_attested_key_with_device_attest_ids(Algorithm::EC); +} + +#[test] +fn keystore2_attest_rsa_attestation_id() { + generate_attested_key_with_device_attest_ids(Algorithm::RSA); +} + +/// Try to generate an attested key with attestation of invalid device's identifiers. Test should +/// fail with error response code `CANNOT_ATTEST_IDS`. +#[test] +fn keystore2_attest_key_fails_with_invalid_attestation_id() { + skip_test_if_no_device_id_attestation_feature!(); + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let digest = Digest::SHA_2_256; + let att_challenge: &[u8] = b"foo"; + + // Create EC-Attestation key. + let attest_key_metadata = key_generations::generate_ec_attestation_key( + &sec_level, + att_challenge, + digest, + EcCurve::P_256, + ) + .unwrap(); + + let attest_id_params = vec![ + (Tag::ATTESTATION_ID_BRAND, b"invalid-brand".to_vec()), + (Tag::ATTESTATION_ID_DEVICE, b"invalid-device-name".to_vec()), + (Tag::ATTESTATION_ID_PRODUCT, b"invalid-product-name".to_vec()), + (Tag::ATTESTATION_ID_SERIAL, b"invalid-ro-serial".to_vec()), + (Tag::ATTESTATION_ID_MANUFACTURER, b"invalid-ro-product-manufacturer".to_vec()), + (Tag::ATTESTATION_ID_MODEL, b"invalid-ro-product-model".to_vec()), + (Tag::ATTESTATION_ID_IMEI, b"invalid-imei".to_vec()), + ]; + + for (attest_id, value) in attest_id_params { + // Create EC key and use attestation key to sign it. + let ec_key_alias = format!("ks_ec_attested_test_key_fail_{}{}", getuid(), digest.0); + let result = key_generations::map_ks_error(key_generations::generate_key_with_attest_id( + &sec_level, + Algorithm::EC, + Some(ec_key_alias), + att_challenge, + &attest_key_metadata.key, + attest_id, + value, + )); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); + } +} + +/// If `DEVICE_ID_ATTESTATION_FEATURE` is not supported then test tries to generate an attested +/// key with attestation of valid device's identifiers. Test should fail to generate key with +/// error code `CANNOT_ATTEST_IDS`. +#[test] +fn keystore2_attest_key_without_attestation_id_support_fails_with_cannot_attest_id() { + if device_id_attestation_feature_exists() { + // Skip this test on device supporting `DEVICE_ID_ATTESTATION_FEATURE`. + return; + } + + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let att_challenge: &[u8] = b"foo"; + let attest_key_metadata = + key_generations::generate_attestation_key(&sec_level, Algorithm::RSA, att_challenge) + .unwrap(); + + let attest_id_params = get_attestation_ids(&keystore2); + for (attest_id, value) in attest_id_params { + // Create RSA/EC key and use attestation key to sign it. + let key_alias = format!("ks_attested_test_key_{}", getuid()); + let result = key_generations::map_ks_error(key_generations::generate_key_with_attest_id( + &sec_level, + Algorithm::RSA, + Some(key_alias), + att_challenge, + &attest_key_metadata.key, + attest_id, + value.clone(), + )); + assert!( + result.is_err(), + "Expected to fail as FEATURE_DEVICE_ID_ATTESTATION is not supported." + ); + assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); + } +} diff --git a/keystore2/tests/keystore2_client_test_utils.rs b/keystore2/tests/keystore2_client_test_utils.rs index 07c21839..3354798e 100644 --- a/keystore2/tests/keystore2_client_test_utils.rs +++ b/keystore2/tests/keystore2_client_test_utils.rs @@ -15,6 +15,8 @@ use nix::unistd::{Gid, Uid}; use serde::{Deserialize, Serialize}; +use std::process::{Command, Output}; + use openssl::encrypt::Encrypter; use openssl::error::ErrorStack; use openssl::hash::MessageDigest; @@ -66,6 +68,7 @@ pub const SAMPLE_PLAIN_TEXT: &[u8] = b"my message 11111"; pub const PACKAGE_MANAGER_NATIVE_SERVICE: &str = "package_native"; pub const APP_ATTEST_KEY_FEATURE: &str = "android.hardware.keystore.app_attest_key"; +pub const DEVICE_ID_ATTESTATION_FEATURE: &str = "android.software.device_id_attestation"; /// Determines whether app_attest_key_feature is supported or not. pub fn app_attest_key_feature_exists() -> bool { @@ -75,6 +78,14 @@ pub fn app_attest_key_feature_exists() -> bool { pm.hasSystemFeature(APP_ATTEST_KEY_FEATURE, 0).expect("hasSystemFeature failed.") } +/// Determines whether device_id_attestation is supported or not. +pub fn device_id_attestation_feature_exists() -> bool { + let pm = wait_for_interface::(PACKAGE_MANAGER_NATIVE_SERVICE) + .expect("Failed to get package manager native service."); + + pm.hasSystemFeature(DEVICE_ID_ATTESTATION_FEATURE, 0).expect("hasSystemFeature failed.") +} + #[macro_export] macro_rules! skip_test_if_no_app_attest_key_feature { () => { @@ -84,6 +95,15 @@ macro_rules! skip_test_if_no_app_attest_key_feature { }; } +#[macro_export] +macro_rules! skip_test_if_no_device_id_attestation_feature { + () => { + if !device_id_attestation_feature_exists() { + return; + } + }; +} + /// Indicate whether the default device is KeyMint (rather than Keymaster). pub fn has_default_keymint() -> bool { binder::is_declared("android.hardware.security.keymint.IKeyMintDevice/default") @@ -444,3 +464,84 @@ pub fn verify_aliases( .iter() .all(|key| expected_aliases.contains(key.alias.as_ref().unwrap()))); } + +// Get the value of the given system property, if the given system property doesn't exist +// then returns an empty byte vector. +pub fn get_system_prop(name: &str) -> Vec { + match rustutils::system_properties::read(name) { + Ok(Some(value)) => { + return value.as_bytes().to_vec(); + } + _ => { + vec![] + } + } +} + +/// Determines whether the SECOND-IMEI can be used as device attest-id. +pub fn is_second_imei_id_attestation_required( + keystore2: &binder::Strong, +) -> bool { + let api_level = std::str::from_utf8(&get_system_prop("ro.vendor.api_level")) + .unwrap() + .parse::() + .unwrap(); + keystore2.getInterfaceVersion().unwrap() >= 3 && api_level > 33 +} + +/// Run a service command and collect the output. +pub fn run_service_command(command: &[&str]) -> std::io::Result { + Command::new("cmd").args(command).output() +} + +/// Get IMEI from telephony service. +pub fn get_imei(slot: i32) -> Option> { + let mut cmd = vec!["phone", "get-imei"]; + let slot_str = slot.to_string(); + cmd.push(slot_str.as_str()); + let output = run_service_command(&cmd).unwrap(); + if output.status.success() { + let stdout = String::from_utf8(output.stdout).unwrap(); + let mut split_out = stdout.split_whitespace(); + let imei = split_out.next_back().unwrap(); + if imei == "null" { + return None; + } + return Some(imei.as_bytes().to_vec()); + } + + None +} + +/// Get value of the given attestation id. +pub fn get_attest_id_value(attest_id: Tag, prop_name: &str) -> Option> { + match attest_id { + Tag::ATTESTATION_ID_IMEI => get_imei(0), + Tag::ATTESTATION_ID_SECOND_IMEI => get_imei(1), + Tag::ATTESTATION_ID_BRAND => { + let prop_val = get_system_prop(prop_name); + if prop_val.is_empty() { + Some(get_system_prop("ro.product.brand")) + } else { + Some(prop_val) + } + } + Tag::ATTESTATION_ID_PRODUCT => { + let prop_val = get_system_prop(prop_name); + if prop_val.is_empty() { + Some(get_system_prop("ro.product.name")) + } else { + Some(prop_val) + } + } + Tag::ATTESTATION_ID_MODEL => { + let prop_val = get_system_prop(prop_name); + if prop_val.is_empty() { + Some(get_system_prop("ro.product.model")) + } else { + Some(prop_val) + } + } + _ => Some(get_system_prop(prop_name)), + } +}