diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-05-10 18:09:36 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-05-10 18:09:36 +0000 |
commit | e8ab430b10f5753e8c51877cf7fb67331f04acf4 (patch) | |
tree | 982d69bdb901c83cb0742e4b0914918776645b53 | |
parent | e38c0e715a54f5fe7297effab05dee2dc7432164 (diff) | |
parent | 71908d88be9d88ba6fbec426651d3263a688c962 (diff) | |
download | security-busytown-mac-infra-release.tar.gz |
Merge "Snap for 11819167 from 99986d19bbf7c7245759e9ff130035b0e9c18797 to busytown-mac-infra-release" into busytown-mac-infra-releasebusytown-mac-infra-release
215 files changed, 9876 insertions, 11678 deletions
@@ -1,5 +1,4 @@ alanstokes@google.com -cbrubaker@google.com drysdale@google.com eranm@google.com hasinitg@google.com @@ -8,4 +7,5 @@ jeffv@google.com kroot@google.com sethmo@google.com swillden@google.com +trong@google.com zeuthen@google.com diff --git a/diced/OWNERS b/diced/OWNERS deleted file mode 100644 index 387cd934..00000000 --- a/diced/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -alanstokes@google.com -aliceywang@google.com -ascull@google.com diff --git a/diced/TEST_MAPPING b/diced/TEST_MAPPING deleted file mode 100644 index 1b078a42..00000000 --- a/diced/TEST_MAPPING +++ /dev/null @@ -1,22 +0,0 @@ -{ - "presubmit": [ - { - "name": "libdiced_open_dice.integration_test" - }, - { - "name": "libdiced_open_dice_nostd.integration_test" - }, - { - "name": "libopen_dice_cbor_bindgen_test" - }, - { - "name": "libopen_dice_bcc_bindgen_test" - }, - { - "name": "diced_utils_test" - }, - { - "name": "diced_sample_inputs_test" - } - ] -} diff --git a/diced/open_dice/Android.bp b/diced/open_dice/Android.bp deleted file mode 100644 index 2505b426..00000000 --- a/diced/open_dice/Android.bp +++ /dev/null @@ -1,256 +0,0 @@ -package { - default_visibility: [":__subpackages__"], - default_applicable_licenses: ["Android-Apache-2.0"], -} - -rust_defaults { - name: "libdiced_open_dice_defaults", - crate_name: "diced_open_dice", - srcs: ["src/lib.rs"], - static_libs: [ - "libopen_dice_cbor", - ], - vendor_available: true, - apex_available: [ - "//apex_available:platform", - "com.android.virt", - ], -} - -rust_library_rlib { - name: "libdiced_open_dice_nostd", - defaults: ["libdiced_open_dice_defaults"], - rustlibs: [ - "libopen_dice_bcc_bindgen_nostd", - "libopen_dice_cbor_bindgen_nostd", - "libzeroize_nostd", - ], - whole_static_libs: [ - "libopen_dice_cbor", - "libcrypto_baremetal", - ], - visibility: [ - "//packages/modules/Virtualization:__subpackages__", - ], -} - -rust_library { - name: "libdiced_open_dice", - defaults: ["libdiced_open_dice_defaults"], - rustlibs: [ - "libopen_dice_bcc_bindgen", - "libopen_dice_cbor_bindgen", - "libzeroize", - ], - features: [ - "std", - ], - shared_libs: [ - "libcrypto", - ], - whole_static_libs: [ - "libopen_dice_bcc", - ], - visibility: [ - "//system/security/diced:__subpackages__", - "//packages/modules/Virtualization:__subpackages__", - "//hardware/interfaces/security/dice/aidl:__subpackages__", - ], -} - -rust_defaults { - name: "libdiced_open_dice_test_defaults", - crate_name: "diced_open_dice_test", - srcs: ["tests/*.rs"], - test_suites: ["general-tests"], -} - -rust_test { - name: "libdiced_open_dice.integration_test", - defaults: ["libdiced_open_dice_test_defaults"], - rustlibs: [ - "libdiced_open_dice", - ], -} - -rust_test { - name: "libdiced_open_dice_nostd.integration_test", - defaults: ["libdiced_open_dice_test_defaults"], - rustlibs: [ - "libdiced_open_dice_nostd", - ], -} - -rust_defaults { - name: "libopen_dice_bindgen_nostd.rust_defaults", - bindgen_flags: [ - "--use-core", - "--ctypes-prefix=core::ffi", - "--raw-line=#![no_std]", - ], - no_stdlibs: true, - prefer_rlib: true, - stdlibs: [ - "libcore.rust_sysroot", - "libcompiler_builtins.rust_sysroot", - ], - target: { - musl: { - enabled: false, - }, - glibc: { - enabled: false, - }, - darwin: { - enabled: false, - }, - }, -} - -rust_defaults { - name: "libopen_dice.rust_defaults", - host_supported: true, - vendor_available: true, - apex_available: [ - "//apex_available:platform", - "com.android.compos", - "com.android.virt", - ], -} - -rust_defaults { - name: "libopen_dice_cbor_bindgen.rust_defaults", - defaults: ["libopen_dice.rust_defaults"], - wrapper_src: "bindgen/dice.h", - crate_name: "open_dice_cbor_bindgen", - source_stem: "bindings", - bindgen_flags: [ - "--size_t-is-usize", - "--rustified-enum DiceConfigType", - "--rustified-enum DiceMode", - "--rustified-enum DiceResult", - - // By generating only essential functions, we can make bindings concise and - // optimize compilation time. - "--allowlist-function=DiceDeriveCdiPrivateKeySeed", - "--allowlist-function=DiceDeriveCdiCertificateId", - "--allowlist-function=DiceMainFlow", - "--allowlist-function=DiceHash", - "--allowlist-function=DiceKdf", - "--allowlist-function=DiceKeypairFromSeed", - "--allowlist-function=DiceSign", - "--allowlist-function=DiceVerify", - "--allowlist-function=DiceGenerateCertificate", - - // We also need some constants in addition to the functions. - "--allowlist-var=DICE_CDI_SIZE", - "--allowlist-var=DICE_HASH_SIZE", - "--allowlist-var=DICE_HIDDEN_SIZE", - "--allowlist-var=DICE_INLINE_CONFIG_SIZE", - "--allowlist-var=DICE_PRIVATE_KEY_SEED_SIZE", - "--allowlist-var=DICE_ID_SIZE", - "--allowlist-var=DICE_PUBLIC_KEY_SIZE", - "--allowlist-var=DICE_PRIVATE_KEY_SIZE", - "--allowlist-var=DICE_SIGNATURE_SIZE", - ], -} - -rust_bindgen { - name: "libopen_dice_cbor_bindgen", - defaults: ["libopen_dice_cbor_bindgen.rust_defaults"], - whole_static_libs: ["libopen_dice_cbor"], -} - -rust_bindgen { - name: "libopen_dice_cbor_bindgen_nostd", - defaults: [ - "libopen_dice_cbor_bindgen.rust_defaults", - "libopen_dice_bindgen_nostd.rust_defaults", - ], - whole_static_libs: ["libopen_dice_cbor_baremetal"], -} - -rust_defaults { - name: "libopen_dice_bcc_bindgen.rust_defaults", - defaults: ["libopen_dice.rust_defaults"], - wrapper_src: "bindgen/android/bcc.h", - crate_name: "open_dice_bcc_bindgen", - source_stem: "bindings", - bindgen_flags: [ - "--size_t-is-usize", - - // By generating only essential functions, we can make bindings concise and - // optimize compilation time. - "--allowlist-function=BccFormatConfigDescriptor", - "--allowlist-function=BccMainFlow", - "--allowlist-function=BccHandoverMainFlow", - "--allowlist-function=BccHandoverParse", - - // We also need some constants in addition to the functions. - "--allowlist-var=BCC_INPUT_COMPONENT_NAME", - "--allowlist-var=BCC_INPUT_COMPONENT_VERSION", - "--allowlist-var=BCC_INPUT_RESETTABLE", - - // Prevent DiceInputValues from being generated a second time and - // import it instead from open_dice_cbor_bindgen. - "--blocklist-type=DiceInputValues_", - "--blocklist-type=DiceInputValues", - "--raw-line", - "pub use open_dice_cbor_bindgen::DiceInputValues;", - - // Prevent DiceResult from being generated a second time and - // import it instead from open_dice_cbor_bindgen. - "--blocklist-type=DiceResult", - "--raw-line", - "pub use open_dice_cbor_bindgen::DiceResult;", - ], - -} - -rust_bindgen { - name: "libopen_dice_bcc_bindgen", - defaults: ["libopen_dice_bcc_bindgen.rust_defaults"], - rustlibs: [ - "libopen_dice_cbor_bindgen", - ], - whole_static_libs: ["libopen_dice_bcc"], -} - -rust_bindgen { - name: "libopen_dice_bcc_bindgen_nostd", - defaults: [ - "libopen_dice_bcc_bindgen.rust_defaults", - "libopen_dice_bindgen_nostd.rust_defaults", - ], - rustlibs: [ - "libopen_dice_cbor_bindgen_nostd", - ], - whole_static_libs: ["libopen_dice_bcc_baremetal"], -} - -rust_test { - name: "libopen_dice_cbor_bindgen_test", - srcs: [ - ":libopen_dice_cbor_bindgen", - ], - crate_name: "open_dice_cbor_bindgen_test", - test_suites: ["general-tests"], - auto_gen_config: true, - clippy_lints: "none", - lints: "none", -} - -rust_test { - name: "libopen_dice_bcc_bindgen_test", - srcs: [ - ":libopen_dice_bcc_bindgen", - ], - crate_name: "open_dice_bcc_bindgen_test", - rustlibs: [ - "libopen_dice_cbor_bindgen", - ], - test_suites: ["general-tests"], - auto_gen_config: true, - clippy_lints: "none", - lints: "none", -} diff --git a/diced/open_dice/bindgen/android/bcc.h b/diced/open_dice/bindgen/android/bcc.h deleted file mode 100644 index 4dfc8626..00000000 --- a/diced/open_dice/bindgen/android/bcc.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 Google LLC -// -// 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 -// -// https://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 <dice/android/bcc.h> diff --git a/diced/open_dice/bindgen/dice.h b/diced/open_dice/bindgen/dice.h deleted file mode 100644 index 47fe9119..00000000 --- a/diced/open_dice/bindgen/dice.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2021 Google LLC -// -// 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 -// -// https://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 <dice/dice.h> -#include <dice/ops.h> diff --git a/diced/open_dice/src/bcc.rs b/diced/open_dice/src/bcc.rs deleted file mode 100644 index 1575113e..00000000 --- a/diced/open_dice/src/bcc.rs +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2023, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This module mirrors the content in open-dice/include/dice/android/bcc.h - -use crate::dice::{Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE}; -use crate::error::{check_result, DiceError, Result}; -use open_dice_bcc_bindgen::{ - BccConfigValues, BccFormatConfigDescriptor, BccHandoverMainFlow, BccHandoverParse, BccMainFlow, - BCC_INPUT_COMPONENT_NAME, BCC_INPUT_COMPONENT_VERSION, BCC_INPUT_RESETTABLE, -}; -use std::{ffi::CStr, ptr}; - -/// Formats a configuration descriptor following the BCC's specification. -/// See https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl -pub fn bcc_format_config_descriptor( - name: Option<&CStr>, - version: Option<u64>, - resettable: bool, - buffer: &mut [u8], -) -> Result<usize> { - let mut inputs = 0; - if name.is_some() { - inputs |= BCC_INPUT_COMPONENT_NAME; - } - if version.is_some() { - inputs |= BCC_INPUT_COMPONENT_VERSION; - } - if resettable { - inputs |= BCC_INPUT_RESETTABLE; - } - - let values = BccConfigValues { - inputs, - component_name: name.map_or(ptr::null(), |p| p.as_ptr()), - component_version: version.unwrap_or(0), - }; - - let mut buffer_size = 0; - // SAFETY: The function writes to the buffer, within the given bounds, and only reads the - // input values. It writes its result to buffer_size. - check_result(unsafe { - BccFormatConfigDescriptor(&values, buffer.len(), buffer.as_mut_ptr(), &mut buffer_size) - })?; - Ok(buffer_size) -} - -/// Executes the main BCC flow. -/// -/// Given a full set of input values along with the current BCC and CDI values, -/// computes the next CDI values and matching updated BCC. -pub fn bcc_main_flow( - current_cdi_attest: &Cdi, - current_cdi_seal: &Cdi, - current_bcc: &[u8], - input_values: &InputValues, - next_cdi_values: &mut CdiValues, - next_bcc: &mut [u8], -) -> Result<usize> { - let mut next_bcc_size = 0; - // SAFETY: `BccMainFlow` only reads the current `bcc` and CDI values and writes - // to `next_bcc` and next CDI values within its bounds. It also reads - // `input_values` as a constant input and doesn't store any pointer. - // The first argument can be null and is not used in the current implementation. - check_result(unsafe { - BccMainFlow( - ptr::null_mut(), // context - current_cdi_attest.as_ptr(), - current_cdi_seal.as_ptr(), - current_bcc.as_ptr(), - current_bcc.len(), - input_values.as_ptr(), - next_bcc.len(), - next_bcc.as_mut_ptr(), - &mut next_bcc_size, - next_cdi_values.cdi_attest.as_mut_ptr(), - next_cdi_values.cdi_seal.as_mut_ptr(), - ) - })?; - Ok(next_bcc_size) -} - -/// Executes the main BCC handover flow. -/// -/// A BCC handover combines the BCC and CDIs in a single CBOR object. -/// This function takes the current boot stage's BCC handover bundle and produces a -/// bundle for the next stage. -pub fn bcc_handover_main_flow( - current_bcc_handover: &[u8], - input_values: &InputValues, - next_bcc_handover: &mut [u8], -) -> Result<usize> { - let mut next_bcc_handover_size = 0; - // SAFETY - The function only reads `current_bcc_handover` and writes to `next_bcc_handover` - // within its bounds, - // It also reads `input_values` as a constant input and doesn't store any pointer. - // The first argument can be null and is not used in the current implementation. - check_result(unsafe { - BccHandoverMainFlow( - ptr::null_mut(), // context - current_bcc_handover.as_ptr(), - current_bcc_handover.len(), - input_values.as_ptr(), - next_bcc_handover.len(), - next_bcc_handover.as_mut_ptr(), - &mut next_bcc_handover_size, - ) - })?; - - Ok(next_bcc_handover_size) -} - -/// A BCC handover combines the BCC and CDIs in a single CBOR object. -/// This struct is used as return of the function `bcc_handover_parse`, its lifetime is tied -/// to the lifetime of the raw BCC handover slice. -#[derive(Debug)] -pub struct BccHandover<'a> { - /// Attestation CDI. - cdi_attest: &'a [u8; CDI_SIZE], - /// Sealing CDI. - cdi_seal: &'a [u8; CDI_SIZE], - /// Boot Certificate Chain. - bcc: Option<&'a [u8]>, -} - -impl<'a> DiceArtifacts for BccHandover<'a> { - fn cdi_attest(&self) -> &[u8; CDI_SIZE] { - self.cdi_attest - } - - fn cdi_seal(&self) -> &[u8; CDI_SIZE] { - self.cdi_seal - } - - fn bcc(&self) -> Option<&[u8]> { - self.bcc - } -} - -/// A BCC handover combines the BCC and CDIs in a single CBOR object. -/// This function parses the `bcc_handover` to extracts the BCC and CDIs. -/// The lifetime of the returned `BccHandover` is tied to the given `bcc_handover` slice. -pub fn bcc_handover_parse(bcc_handover: &[u8]) -> Result<BccHandover> { - let mut cdi_attest: *const u8 = ptr::null(); - let mut cdi_seal: *const u8 = ptr::null(); - let mut bcc: *const u8 = ptr::null(); - let mut bcc_size = 0; - // SAFETY: The `bcc_handover` is only read and never stored and the returned pointers should all - // point within the address range of the `bcc_handover` or be NULL. - check_result(unsafe { - BccHandoverParse( - bcc_handover.as_ptr(), - bcc_handover.len(), - &mut cdi_attest, - &mut cdi_seal, - &mut bcc, - &mut bcc_size, - ) - })?; - let cdi_attest = sub_slice(bcc_handover, cdi_attest, CDI_SIZE)?; - let cdi_seal = sub_slice(bcc_handover, cdi_seal, CDI_SIZE)?; - let bcc = sub_slice(bcc_handover, bcc, bcc_size).ok(); - Ok(BccHandover { - cdi_attest: cdi_attest.try_into().map_err(|_| DiceError::PlatformError)?, - cdi_seal: cdi_seal.try_into().map_err(|_| DiceError::PlatformError)?, - bcc, - }) -} - -/// Gets a slice the `addr` points to and of length `len`. -/// The slice should be contained in the buffer. -fn sub_slice(buffer: &[u8], addr: *const u8, len: usize) -> Result<&[u8]> { - if addr.is_null() || !buffer.as_ptr_range().contains(&addr) { - return Err(DiceError::PlatformError); - } - // SAFETY: This is safe because addr is not null and is within the range of the buffer. - let start: usize = unsafe { - addr.offset_from(buffer.as_ptr()).try_into().map_err(|_| DiceError::PlatformError)? - }; - start.checked_add(len).and_then(|end| buffer.get(start..end)).ok_or(DiceError::PlatformError) -} diff --git a/diced/open_dice/src/dice.rs b/diced/open_dice/src/dice.rs deleted file mode 100644 index 9266b6fc..00000000 --- a/diced/open_dice/src/dice.rs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2023, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Structs and functions about the types used in DICE. -//! This module mirrors the content in open-dice/include/dice/dice.h - -use crate::error::{check_result, Result}; -pub use open_dice_cbor_bindgen::DiceMode; -use open_dice_cbor_bindgen::{ - DiceConfigType, DiceDeriveCdiCertificateId, DiceDeriveCdiPrivateKeySeed, DiceInputValues, - DiceMainFlow, DICE_CDI_SIZE, DICE_HASH_SIZE, DICE_HIDDEN_SIZE, DICE_ID_SIZE, - DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE, - DICE_PUBLIC_KEY_SIZE, DICE_SIGNATURE_SIZE, -}; -use std::ptr; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -/// The size of a DICE hash. -pub const HASH_SIZE: usize = DICE_HASH_SIZE as usize; -/// The size of the DICE hidden value. -pub const HIDDEN_SIZE: usize = DICE_HIDDEN_SIZE as usize; -/// The size of a DICE inline config. -const INLINE_CONFIG_SIZE: usize = DICE_INLINE_CONFIG_SIZE as usize; -/// The size of a CDI. -pub const CDI_SIZE: usize = DICE_CDI_SIZE as usize; -/// The size of a private key seed. -pub const PRIVATE_KEY_SEED_SIZE: usize = DICE_PRIVATE_KEY_SEED_SIZE as usize; -/// The size of a private key. -pub const PRIVATE_KEY_SIZE: usize = DICE_PRIVATE_KEY_SIZE as usize; -/// The size of a public key. -pub const PUBLIC_KEY_SIZE: usize = DICE_PUBLIC_KEY_SIZE as usize; -/// The size of a signature. -pub const SIGNATURE_SIZE: usize = DICE_SIGNATURE_SIZE as usize; -/// The size of an ID. -pub const ID_SIZE: usize = DICE_ID_SIZE as usize; - -/// Array type of hashes used by DICE. -pub type Hash = [u8; HASH_SIZE]; -/// Array type of additional input. -pub type Hidden = [u8; HIDDEN_SIZE]; -/// Array type of inline configuration values. -pub type InlineConfig = [u8; INLINE_CONFIG_SIZE]; -/// Array type of CDIs. -pub type Cdi = [u8; CDI_SIZE]; -/// Array type of the public key. -pub type PublicKey = [u8; PUBLIC_KEY_SIZE]; -/// Array type of the signature. -pub type Signature = [u8; SIGNATURE_SIZE]; -/// Array type of DICE ID. -pub type DiceId = [u8; ID_SIZE]; - -/// A trait for types that represent Dice artifacts, which include: -/// -/// - Attestation CDI -/// - Sealing CDI -/// - Boot Certificate Chain -/// -/// Types that implement this trait provide an access these artifacts. -pub trait DiceArtifacts { - /// Returns a reference to the attestation CDI. - fn cdi_attest(&self) -> &[u8; CDI_SIZE]; - - /// Returns a reference to the sealing CDI. - fn cdi_seal(&self) -> &[u8; CDI_SIZE]; - - /// Returns a reference to the Boot Certificate Chain, if present. - fn bcc(&self) -> Option<&[u8]>; -} - -/// TODO(b/268587826): Clean up the memory cache after zeroing out the memory -/// for sensitive data like CDI values and private key. -/// CDI Values. -#[derive(Debug, Zeroize, ZeroizeOnDrop, Default)] -pub struct CdiValues { - /// Attestation CDI. - pub cdi_attest: [u8; CDI_SIZE], - /// Sealing CDI. - pub cdi_seal: [u8; CDI_SIZE], -} - -/// Private key seed. The data is zeroed out when the struct is dropped. -#[derive(Zeroize, ZeroizeOnDrop, Default)] -pub struct PrivateKeySeed([u8; PRIVATE_KEY_SEED_SIZE]); - -impl PrivateKeySeed { - /// Returns an array reference of the private key seed. - pub fn as_array(&self) -> &[u8; PRIVATE_KEY_SEED_SIZE] { - &self.0 - } - - /// Returns a mutable pointer to the slice buffer of the private key seed. - pub fn as_mut_ptr(&mut self) -> *mut u8 { - self.0.as_mut_ptr() - } -} - -/// Private key. The data is zeroed out when the struct is dropped. -#[derive(Zeroize, ZeroizeOnDrop)] -pub struct PrivateKey([u8; PRIVATE_KEY_SIZE]); - -impl Default for PrivateKey { - /// Creates a new `PrivateKey` instance with all bytes set to 0. - /// - /// Since the size of the private key array is too large to be initialized - /// with a default value, this implementation sets all the bytes in the array - /// to 0 using the `[0u8; PRIVATE_KEY_SIZE]` syntax. - fn default() -> Self { - Self([0u8; PRIVATE_KEY_SIZE]) - } -} - -impl PrivateKey { - /// Returns an array reference of the private key. - pub fn as_array(&self) -> &[u8; PRIVATE_KEY_SIZE] { - &self.0 - } - - /// Returns a mutable pointer to the slice buffer of the private key. - pub fn as_mut_ptr(&mut self) -> *mut u8 { - self.0.as_mut_ptr() - } -} - -/// Configuration descriptor for DICE input values. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Config<'a> { - /// Reference to an inline descriptor. - Inline(&'a InlineConfig), - /// Reference to a free form descriptor that will be hashed by the implementation. - Descriptor(&'a [u8]), -} - -impl Config<'_> { - fn dice_config_type(&self) -> DiceConfigType { - match self { - Self::Inline(_) => DiceConfigType::kDiceConfigTypeInline, - Self::Descriptor(_) => DiceConfigType::kDiceConfigTypeDescriptor, - } - } - - fn inline_config(&self) -> InlineConfig { - match self { - Self::Inline(inline) => **inline, - Self::Descriptor(_) => [0u8; INLINE_CONFIG_SIZE], - } - } - - fn descriptor_ptr(&self) -> *const u8 { - match self { - Self::Descriptor(descriptor) => descriptor.as_ptr(), - _ => ptr::null(), - } - } - - fn descriptor_size(&self) -> usize { - match self { - Self::Descriptor(descriptor) => descriptor.len(), - _ => 0, - } - } -} - -/// Wrap of `DiceInputValues`. -#[derive(Clone, Debug)] -pub struct InputValues(DiceInputValues); - -impl InputValues { - /// Creates a new `InputValues`. - pub fn new( - code_hash: Hash, - config: Config, - authority_hash: Hash, - mode: DiceMode, - hidden: Hidden, - ) -> Self { - Self(DiceInputValues { - code_hash, - code_descriptor: ptr::null(), - code_descriptor_size: 0, - config_type: config.dice_config_type(), - config_value: config.inline_config(), - config_descriptor: config.descriptor_ptr(), - config_descriptor_size: config.descriptor_size(), - authority_hash, - authority_descriptor: ptr::null(), - authority_descriptor_size: 0, - mode, - hidden, - }) - } - - /// Returns a raw pointer to the wrapped `DiceInputValues`. - pub fn as_ptr(&self) -> *const DiceInputValues { - &self.0 as *const DiceInputValues - } -} - -/// Derives a CDI private key seed from a `cdi_attest` value. -pub fn derive_cdi_private_key_seed(cdi_attest: &Cdi) -> Result<PrivateKeySeed> { - let mut seed = PrivateKeySeed::default(); - // SAFETY: The function writes to the buffer within the given bounds, and only reads the - // input values. The first argument context is not used in this function. - check_result(unsafe { - DiceDeriveCdiPrivateKeySeed( - ptr::null_mut(), // context - cdi_attest.as_ptr(), - seed.as_mut_ptr(), - ) - })?; - Ok(seed) -} - -/// Derives an ID from the given `cdi_public_key` value. -pub fn derive_cdi_certificate_id(cdi_public_key: &[u8]) -> Result<DiceId> { - let mut id = [0u8; ID_SIZE]; - // SAFETY: The function writes to the buffer within the given bounds, and only reads the - // input values. The first argument context is not used in this function. - check_result(unsafe { - DiceDeriveCdiCertificateId( - ptr::null_mut(), // context - cdi_public_key.as_ptr(), - cdi_public_key.len(), - id.as_mut_ptr(), - ) - })?; - Ok(id) -} - -/// Executes the main DICE flow. -/// -/// Given a full set of input values and the current CDI values, computes the -/// next CDI values and a matching certificate. -/// Returns the actual size of the next CDI certificate. -pub fn dice_main_flow( - current_cdi_attest: &Cdi, - current_cdi_seal: &Cdi, - input_values: &InputValues, - next_cdi_certificate: &mut [u8], - next_cdi_values: &mut CdiValues, -) -> Result<usize> { - let mut next_cdi_certificate_actual_size = 0; - // SAFETY: The function only reads the current CDI values and inputs and writes - // to `next_cdi_certificate` and next CDI values within its bounds. - // The first argument can be null and is not used in the current implementation. - check_result(unsafe { - DiceMainFlow( - ptr::null_mut(), // context - current_cdi_attest.as_ptr(), - current_cdi_seal.as_ptr(), - input_values.as_ptr(), - next_cdi_certificate.len(), - next_cdi_certificate.as_mut_ptr(), - &mut next_cdi_certificate_actual_size, - next_cdi_values.cdi_attest.as_mut_ptr(), - next_cdi_values.cdi_seal.as_mut_ptr(), - ) - })?; - Ok(next_cdi_certificate_actual_size) -} diff --git a/diced/open_dice/src/error.rs b/diced/open_dice/src/error.rs deleted file mode 100644 index 4c673354..00000000 --- a/diced/open_dice/src/error.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2023, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Errors and relating functions thrown in this library. - -use open_dice_cbor_bindgen::DiceResult; -use std::{fmt, result}; - -#[cfg(feature = "std")] -use std::error::Error; - -/// Error type used by DICE. -#[derive(Debug)] -pub enum DiceError { - /// Provided input was invalid. - InvalidInput, - /// Provided buffer was too small. - BufferTooSmall, - /// Platform error. - PlatformError, -} - -/// This makes `DiceError` accepted by anyhow. -#[cfg(feature = "std")] -impl Error for DiceError {} - -impl fmt::Display for DiceError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::InvalidInput => write!(f, "invalid input"), - Self::BufferTooSmall => write!(f, "buffer too small"), - Self::PlatformError => write!(f, "platform error"), - } - } -} - -/// DICE result type. -pub type Result<T> = result::Result<T, DiceError>; - -/// Checks the given `DiceResult`. Returns an error if it's not OK. -pub fn check_result(result: DiceResult) -> Result<()> { - match result { - DiceResult::kDiceResultOk => Ok(()), - DiceResult::kDiceResultInvalidInput => Err(DiceError::InvalidInput), - DiceResult::kDiceResultBufferTooSmall => Err(DiceError::BufferTooSmall), - DiceResult::kDiceResultPlatformError => Err(DiceError::PlatformError), - } -} diff --git a/diced/open_dice/src/lib.rs b/diced/open_dice/src/lib.rs deleted file mode 100644 index e7ec56be..00000000 --- a/diced/open_dice/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2023, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Implements safe wrappers around the public API of libopen-dice for -//! both std and nostd usages. - -#![cfg_attr(not(feature = "std"), no_std)] - -#[cfg(not(feature = "std"))] -extern crate core as std; - -mod bcc; -mod dice; -mod error; -mod ops; -#[cfg(feature = "std")] -mod retry; - -pub use bcc::{ - bcc_format_config_descriptor, bcc_handover_main_flow, bcc_handover_parse, bcc_main_flow, - BccHandover, -}; -pub use dice::{ - derive_cdi_certificate_id, derive_cdi_private_key_seed, dice_main_flow, Cdi, CdiValues, Config, - DiceArtifacts, DiceMode, Hash, Hidden, InlineConfig, InputValues, PrivateKey, PrivateKeySeed, - PublicKey, Signature, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE, ID_SIZE, PRIVATE_KEY_SEED_SIZE, -}; -pub use error::{check_result, DiceError, Result}; -pub use ops::{generate_certificate, hash, kdf, keypair_from_seed, sign, verify}; -#[cfg(feature = "std")] -pub use retry::{ - retry_bcc_format_config_descriptor, retry_bcc_main_flow, retry_dice_main_flow, - retry_generate_certificate, OwnedDiceArtifacts, -}; diff --git a/diced/open_dice/src/ops.rs b/diced/open_dice/src/ops.rs deleted file mode 100644 index 8222b266..00000000 --- a/diced/open_dice/src/ops.rs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2023, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This module mirrors the content in open-dice/include/dice/ops.h -//! It contains the set of functions that implement various operations that the -//! main DICE functions depend on. - -use crate::dice::{ - Hash, InputValues, PrivateKey, PublicKey, Signature, HASH_SIZE, PRIVATE_KEY_SEED_SIZE, - PRIVATE_KEY_SIZE, PUBLIC_KEY_SIZE, SIGNATURE_SIZE, -}; -use crate::error::{check_result, Result}; -use open_dice_cbor_bindgen::{ - DiceGenerateCertificate, DiceHash, DiceKdf, DiceKeypairFromSeed, DiceSign, DiceVerify, -}; -use std::ptr; - -/// Hashes the provided input using DICE's hash function `DiceHash`. -pub fn hash(input: &[u8]) -> Result<Hash> { - let mut output: Hash = [0; HASH_SIZE]; - // SAFETY: DiceHash takes a sized input buffer and writes to a constant-sized output buffer. - // The first argument context is not used in this function. - check_result(unsafe { - DiceHash( - ptr::null_mut(), // context - input.as_ptr(), - input.len(), - output.as_mut_ptr(), - ) - })?; - Ok(output) -} - -/// An implementation of HKDF-SHA512. Derives a key of `derived_key.len()` bytes from `ikm`, `salt`, -/// and `info`. The derived key is written to the `derived_key`. -pub fn kdf(ikm: &[u8], salt: &[u8], info: &[u8], derived_key: &mut [u8]) -> Result<()> { - // SAFETY: The function writes to the `derived_key`, within the given bounds, and only reads the - // input values. The first argument context is not used in this function. - check_result(unsafe { - DiceKdf( - ptr::null_mut(), // context - derived_key.len(), - ikm.as_ptr(), - ikm.len(), - salt.as_ptr(), - salt.len(), - info.as_ptr(), - info.len(), - derived_key.as_mut_ptr(), - ) - }) -} - -/// Deterministically generates a public and private key pair from `seed`. -/// Since this is deterministic, `seed` is as sensitive as a private key and can -/// be used directly as the private key. -pub fn keypair_from_seed(seed: &[u8; PRIVATE_KEY_SEED_SIZE]) -> Result<(PublicKey, PrivateKey)> { - let mut public_key = [0u8; PUBLIC_KEY_SIZE]; - let mut private_key = PrivateKey::default(); - // SAFETY: The function writes to the `public_key` and `private_key` within the given bounds, - // and only reads the `seed`. The first argument context is not used in this function. - check_result(unsafe { - DiceKeypairFromSeed( - ptr::null_mut(), // context - seed.as_ptr(), - public_key.as_mut_ptr(), - private_key.as_mut_ptr(), - ) - })?; - Ok((public_key, private_key)) -} - -/// Signs the `message` with the give `private_key` using `DiceSign`. -pub fn sign(message: &[u8], private_key: &[u8; PRIVATE_KEY_SIZE]) -> Result<Signature> { - let mut signature = [0u8; SIGNATURE_SIZE]; - // SAFETY: The function writes to the `signature` within the given bounds, and only reads the - // message and the private key. The first argument context is not used in this function. - check_result(unsafe { - DiceSign( - ptr::null_mut(), // context - message.as_ptr(), - message.len(), - private_key.as_ptr(), - signature.as_mut_ptr(), - ) - })?; - Ok(signature) -} - -/// Verifies the `signature` of the `message` with the given `public_key` using `DiceVerify`. -pub fn verify(message: &[u8], signature: &Signature, public_key: &PublicKey) -> Result<()> { - // SAFETY: only reads the messages, signature and public key as constant values. - // The first argument context is not used in this function. - check_result(unsafe { - DiceVerify( - ptr::null_mut(), // context - message.as_ptr(), - message.len(), - signature.as_ptr(), - public_key.as_ptr(), - ) - }) -} - -/// Generates an X.509 certificate from the given `subject_private_key_seed` and -/// `input_values`, and signed by `authority_private_key_seed`. -/// The subject private key seed is supplied here so the implementation can choose -/// between asymmetric mechanisms, for example ECDSA vs Ed25519. -/// Returns the actual size of the generated certificate. -pub fn generate_certificate( - subject_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], - authority_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], - input_values: &InputValues, - certificate: &mut [u8], -) -> Result<usize> { - let mut certificate_actual_size = 0; - // SAFETY: The function writes to the `certificate` within the given bounds, and only reads the - // input values and the key seeds. The first argument context is not used in this function. - check_result(unsafe { - DiceGenerateCertificate( - ptr::null_mut(), // context - subject_private_key_seed.as_ptr(), - authority_private_key_seed.as_ptr(), - input_values.as_ptr(), - certificate.len(), - certificate.as_mut_ptr(), - &mut certificate_actual_size, - ) - })?; - Ok(certificate_actual_size) -} diff --git a/diced/open_dice/src/retry.rs b/diced/open_dice/src/retry.rs deleted file mode 100644 index 76a214c9..00000000 --- a/diced/open_dice/src/retry.rs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2023, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This module implements a retry version for multiple DICE functions that -//! require preallocated output buffer. As the retry functions require -//! memory allocation on heap, currently we only expose these functions in -//! std environment. - -use crate::bcc::{bcc_format_config_descriptor, bcc_main_flow}; -use crate::dice::{ - dice_main_flow, Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE, PRIVATE_KEY_SEED_SIZE, -}; -use crate::error::{DiceError, Result}; -use crate::ops::generate_certificate; -use std::ffi::CStr; - -/// Artifacts stores a set of dice artifacts comprising CDI_ATTEST, CDI_SEAL, -/// and the BCC formatted attestation certificate chain. -/// As we align with the DICE standards today, this is the certificate chain -/// is also called DICE certificate chain. -#[derive(Debug)] -pub struct OwnedDiceArtifacts { - /// CDI Values. - cdi_values: CdiValues, - /// Boot Certificate Chain. - bcc: Vec<u8>, -} - -impl DiceArtifacts for OwnedDiceArtifacts { - fn cdi_attest(&self) -> &[u8; CDI_SIZE] { - &self.cdi_values.cdi_attest - } - - fn cdi_seal(&self) -> &[u8; CDI_SIZE] { - &self.cdi_values.cdi_seal - } - - fn bcc(&self) -> Option<&[u8]> { - Some(&self.bcc) - } -} - -/// Retries the given function with bigger output buffer size. -fn retry_with_bigger_buffer<F>(mut f: F) -> Result<Vec<u8>> -where - F: FnMut(&mut Vec<u8>) -> Result<usize>, -{ - const INITIAL_BUFFER_SIZE: usize = 256; - const MAX_BUFFER_SIZE: usize = 64 * 1024 * 1024; - - let mut buffer = vec![0u8; INITIAL_BUFFER_SIZE]; - while buffer.len() <= MAX_BUFFER_SIZE { - match f(&mut buffer) { - Err(DiceError::BufferTooSmall) => { - let new_size = buffer.len() * 2; - buffer.resize(new_size, 0); - } - Err(e) => return Err(e), - Ok(actual_size) => { - if actual_size > buffer.len() { - panic!( - "actual_size larger than buffer size: open-dice function - may have written past the end of the buffer." - ); - } - buffer.truncate(actual_size); - return Ok(buffer); - } - } - } - Err(DiceError::PlatformError) -} - -/// Formats a configuration descriptor following the BCC's specification. -pub fn retry_bcc_format_config_descriptor( - name: Option<&CStr>, - version: Option<u64>, - resettable: bool, -) -> Result<Vec<u8>> { - retry_with_bigger_buffer(|buffer| { - bcc_format_config_descriptor(name, version, resettable, buffer) - }) -} - -/// Executes the main BCC flow. -/// -/// Given a full set of input values along with the current BCC and CDI values, -/// computes the next CDI values and matching updated BCC. -pub fn retry_bcc_main_flow( - current_cdi_attest: &Cdi, - current_cdi_seal: &Cdi, - bcc: &[u8], - input_values: &InputValues, -) -> Result<OwnedDiceArtifacts> { - let mut next_cdi_values = CdiValues::default(); - let next_bcc = retry_with_bigger_buffer(|next_bcc| { - bcc_main_flow( - current_cdi_attest, - current_cdi_seal, - bcc, - input_values, - &mut next_cdi_values, - next_bcc, - ) - })?; - Ok(OwnedDiceArtifacts { cdi_values: next_cdi_values, bcc: next_bcc }) -} - -/// Executes the main DICE flow. -/// -/// Given a full set of input values and the current CDI values, computes the -/// next CDI values and a matching certificate. -pub fn retry_dice_main_flow( - current_cdi_attest: &Cdi, - current_cdi_seal: &Cdi, - input_values: &InputValues, -) -> Result<(CdiValues, Vec<u8>)> { - let mut next_cdi_values = CdiValues::default(); - let next_cdi_certificate = retry_with_bigger_buffer(|next_cdi_certificate| { - dice_main_flow( - current_cdi_attest, - current_cdi_seal, - input_values, - next_cdi_certificate, - &mut next_cdi_values, - ) - })?; - Ok((next_cdi_values, next_cdi_certificate)) -} - -/// Generates an X.509 certificate from the given `subject_private_key_seed` and -/// `input_values`, and signed by `authority_private_key_seed`. -/// The subject private key seed is supplied here so the implementation can choose -/// between asymmetric mechanisms, for example ECDSA vs Ed25519. -/// Returns the generated certificate. -pub fn retry_generate_certificate( - subject_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], - authority_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], - input_values: &InputValues, -) -> Result<Vec<u8>> { - retry_with_bigger_buffer(|certificate| { - generate_certificate( - subject_private_key_seed, - authority_private_key_seed, - input_values, - certificate, - ) - }) -} diff --git a/diced/open_dice/tests/api_test.rs b/diced/open_dice/tests/api_test.rs deleted file mode 100644 index a47265b3..00000000 --- a/diced/open_dice/tests/api_test.rs +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use diced_open_dice::{ - derive_cdi_certificate_id, derive_cdi_private_key_seed, hash, kdf, keypair_from_seed, sign, - verify, CDI_SIZE, HASH_SIZE, ID_SIZE, PRIVATE_KEY_SEED_SIZE, -}; - -#[test] -fn hash_succeeds() { - const EXPECTED_HASH: [u8; HASH_SIZE] = [ - 0x30, 0x9e, 0xcc, 0x48, 0x9c, 0x12, 0xd6, 0xeb, 0x4c, 0xc4, 0x0f, 0x50, 0xc9, 0x02, 0xf2, - 0xb4, 0xd0, 0xed, 0x77, 0xee, 0x51, 0x1a, 0x7c, 0x7a, 0x9b, 0xcd, 0x3c, 0xa8, 0x6d, 0x4c, - 0xd8, 0x6f, 0x98, 0x9d, 0xd3, 0x5b, 0xc5, 0xff, 0x49, 0x96, 0x70, 0xda, 0x34, 0x25, 0x5b, - 0x45, 0xb0, 0xcf, 0xd8, 0x30, 0xe8, 0x1f, 0x60, 0x5d, 0xcf, 0x7d, 0xc5, 0x54, 0x2e, 0x93, - 0xae, 0x9c, 0xd7, 0x6f, - ]; - assert_eq!(EXPECTED_HASH, hash(b"hello world").expect("hash failed")); -} - -#[test] -fn kdf_succeeds() { - let mut derived_key = [0u8; PRIVATE_KEY_SEED_SIZE]; - kdf(b"myInitialKeyMaterial", b"mySalt", b"myInfo", &mut derived_key).unwrap(); - const EXPECTED_DERIVED_KEY: [u8; PRIVATE_KEY_SEED_SIZE] = [ - 0x91, 0x9b, 0x8d, 0x29, 0xc4, 0x1b, 0x93, 0xd7, 0xeb, 0x09, 0xfa, 0xd7, 0xc9, 0x87, 0xb0, - 0xd1, 0xcc, 0x26, 0xef, 0x07, 0x83, 0x42, 0xcf, 0xa3, 0x45, 0x0a, 0x57, 0xe9, 0x19, 0x86, - 0xef, 0x48, - ]; - assert_eq!(EXPECTED_DERIVED_KEY, derived_key); -} - -#[test] -fn derive_cdi_certificate_id_succeeds() { - const EXPECTED_ID: [u8; ID_SIZE] = [ - 0x7a, 0x36, 0x45, 0x2c, 0x02, 0xf6, 0x2b, 0xec, 0xf9, 0x80, 0x06, 0x75, 0x87, 0xa5, 0xc1, - 0x44, 0x0c, 0xd3, 0xc0, 0x6d, - ]; - assert_eq!(EXPECTED_ID, derive_cdi_certificate_id(b"MyPubKey").unwrap()); -} - -const EXPECTED_SEED: &[u8] = &[ - 0xfa, 0x3c, 0x2f, 0x58, 0x37, 0xf5, 0x8e, 0x96, 0x16, 0x09, 0xf5, 0x22, 0xa1, 0xf1, 0xba, 0xaa, - 0x19, 0x95, 0x01, 0x79, 0x2e, 0x60, 0x56, 0xaf, 0xf6, 0x41, 0xe7, 0xff, 0x48, 0xf5, 0x3a, 0x08, - 0x84, 0x8a, 0x98, 0x85, 0x6d, 0xf5, 0x69, 0x21, 0x03, 0xcd, 0x09, 0xc3, 0x28, 0xd6, 0x06, 0xa7, - 0x57, 0xbd, 0x48, 0x4b, 0x0f, 0x79, 0x0f, 0xf8, 0x2f, 0xf0, 0x0a, 0x41, 0x94, 0xd8, 0x8c, 0xa8, -]; - -const EXPECTED_CDI_ATTEST: &[u8] = &[ - 0xfa, 0x3c, 0x2f, 0x58, 0x37, 0xf5, 0x8e, 0x96, 0x16, 0x09, 0xf5, 0x22, 0xa1, 0xf1, 0xba, 0xaa, - 0x19, 0x95, 0x01, 0x79, 0x2e, 0x60, 0x56, 0xaf, 0xf6, 0x41, 0xe7, 0xff, 0x48, 0xf5, 0x3a, 0x08, -]; - -const EXPECTED_CDI_PRIVATE_KEY_SEED: &[u8] = &[ - 0x5f, 0xcc, 0x8e, 0x1a, 0xd1, 0xc2, 0xb3, 0xe9, 0xfb, 0xe1, 0x68, 0xf0, 0xf6, 0x98, 0xfe, 0x0d, - 0xee, 0xd4, 0xb5, 0x18, 0xcb, 0x59, 0x70, 0x2d, 0xee, 0x06, 0xe5, 0x70, 0xf1, 0x72, 0x02, 0x6e, -]; - -const EXPECTED_PUB_KEY: &[u8] = &[ - 0x47, 0x42, 0x4b, 0xbd, 0xd7, 0x23, 0xb4, 0xcd, 0xca, 0xe2, 0x8e, 0xdc, 0x6b, 0xfc, 0x23, 0xc9, - 0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52, 0xf1, 0x61, 0x06, 0x37, -]; -const EXPECTED_PRIV_KEY: &[u8] = &[ - 0x5f, 0xcc, 0x8e, 0x1a, 0xd1, 0xc2, 0xb3, 0xe9, 0xfb, 0xe1, 0x68, 0xf0, 0xf6, 0x98, 0xfe, 0x0d, - 0xee, 0xd4, 0xb5, 0x18, 0xcb, 0x59, 0x70, 0x2d, 0xee, 0x06, 0xe5, 0x70, 0xf1, 0x72, 0x02, 0x6e, - 0x47, 0x42, 0x4b, 0xbd, 0xd7, 0x23, 0xb4, 0xcd, 0xca, 0xe2, 0x8e, 0xdc, 0x6b, 0xfc, 0x23, 0xc9, - 0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52, 0xf1, 0x61, 0x06, 0x37, -]; - -const EXPECTED_SIGNATURE: &[u8] = &[ - 0x44, 0xae, 0xcc, 0xe2, 0xb9, 0x96, 0x18, 0x39, 0x0e, 0x61, 0x0f, 0x53, 0x07, 0xbf, 0xf2, 0x32, - 0x3d, 0x44, 0xd4, 0xf2, 0x07, 0x23, 0x30, 0x85, 0x32, 0x18, 0xd2, 0x69, 0xb8, 0x29, 0x3c, 0x26, - 0xe6, 0x0d, 0x9c, 0xa5, 0xc2, 0x73, 0xcd, 0x8c, 0xb8, 0x3c, 0x3e, 0x5b, 0xfd, 0x62, 0x8d, 0xf6, - 0xc4, 0x27, 0xa6, 0xe9, 0x11, 0x06, 0x5a, 0xb2, 0x2b, 0x64, 0xf7, 0xfc, 0xbb, 0xab, 0x4a, 0x0e, -]; - -#[test] -fn hash_derive_sign_verify() { - let seed = hash(b"MySeedString").unwrap(); - assert_eq!(seed, EXPECTED_SEED); - let cdi_attest = &seed[..CDI_SIZE]; - assert_eq!(cdi_attest, EXPECTED_CDI_ATTEST); - let cdi_private_key_seed = derive_cdi_private_key_seed(cdi_attest.try_into().unwrap()).unwrap(); - assert_eq!(cdi_private_key_seed.as_array(), EXPECTED_CDI_PRIVATE_KEY_SEED); - let (pub_key, priv_key) = keypair_from_seed(cdi_private_key_seed.as_array()).unwrap(); - assert_eq!(&pub_key, EXPECTED_PUB_KEY); - assert_eq!(priv_key.as_array(), EXPECTED_PRIV_KEY); - let mut signature = sign(b"MyMessage", priv_key.as_array()).unwrap(); - assert_eq!(&signature, EXPECTED_SIGNATURE); - assert!(verify(b"MyMessage", &signature, &pub_key).is_ok()); - assert!(verify(b"MyMessage_fail", &signature, &pub_key).is_err()); - signature[0] += 1; - assert!(verify(b"MyMessage", &signature, &pub_key).is_err()); -} diff --git a/diced/open_dice_cbor/lib.rs b/diced/open_dice_cbor/lib.rs deleted file mode 100644 index 1650d936..00000000 --- a/diced/open_dice_cbor/lib.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2021, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// TODO(b/267575445): Move the tests to other target and remove the folder `open_dice_cbor` completely. -#[cfg(test)] -mod test { - use diced_open_dice::DiceArtifacts; - use diced_sample_inputs::make_sample_bcc_and_cdis; - - static SAMPLE_CDI_ATTEST_TEST_VECTOR: &[u8] = &[ - 0x3e, 0x57, 0x65, 0x5d, 0x48, 0x02, 0xbd, 0x5c, 0x66, 0xcc, 0x1f, 0x0f, 0xbe, 0x5e, 0x32, - 0xb6, 0x9e, 0x3d, 0x04, 0xaf, 0x00, 0x15, 0xbc, 0xdd, 0x1f, 0xbc, 0x59, 0xe4, 0xc3, 0x87, - 0x95, 0x5e, - ]; - - static SAMPLE_CDI_SEAL_TEST_VECTOR: &[u8] = &[ - 0x36, 0x1b, 0xd2, 0xb3, 0xc4, 0xda, 0x77, 0xb2, 0x9c, 0xba, 0x39, 0x53, 0x82, 0x93, 0xd9, - 0xb8, 0x9f, 0x73, 0x2d, 0x27, 0x06, 0x15, 0xa8, 0xcb, 0x6d, 0x1d, 0xf2, 0xb1, 0x54, 0xbb, - 0x62, 0xf1, - ]; - - static SAMPLE_BCC_TEST_VECTOR: &[u8] = &[ - 0x84, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x3e, - 0x85, 0xe5, 0x72, 0x75, 0x55, 0xe5, 0x1e, 0xe7, 0xf3, 0x35, 0x94, 0x8e, 0xbb, 0xbd, 0x74, - 0x1e, 0x1d, 0xca, 0x49, 0x9c, 0x97, 0x39, 0x77, 0x06, 0xd3, 0xc8, 0x6e, 0x8b, 0xd7, 0x33, - 0xf9, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28, 0x34, - 0x32, 0x64, 0x38, 0x38, 0x36, 0x34, 0x66, 0x39, 0x37, 0x62, 0x36, 0x35, 0x34, 0x37, 0x61, - 0x35, 0x30, 0x63, 0x31, 0x65, 0x30, 0x61, 0x37, 0x34, 0x39, 0x66, 0x38, 0x65, 0x66, 0x38, - 0x62, 0x38, 0x31, 0x65, 0x63, 0x36, 0x32, 0x61, 0x66, 0x02, 0x78, 0x28, 0x31, 0x66, 0x36, - 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, - 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, - 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x16, - 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26, - 0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, - 0x25, 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, - 0xd3, 0xfb, 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7, - 0x15, 0x98, 0x14, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, - 0x63, 0x41, 0x42, 0x4c, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01, 0x11, 0x73, - 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x47, 0xae, 0x42, 0x27, 0x4c, 0xcb, 0x65, - 0x4d, 0xee, 0x74, 0x2d, 0x05, 0x78, 0x2a, 0x08, 0x2a, 0xa5, 0xf0, 0xcf, 0xea, 0x3e, 0x60, - 0xee, 0x97, 0x11, 0x4b, 0x5b, 0xe6, 0x05, 0x0c, 0xe8, 0x90, 0xf5, 0x22, 0xc4, 0xc6, 0x67, - 0x7a, 0x22, 0x27, 0x17, 0xb3, 0x79, 0xcc, 0x37, 0x64, 0x5e, 0x19, 0x4f, 0x96, 0x37, 0x67, - 0x3c, 0xd0, 0xc5, 0xed, 0x0f, 0xdd, 0xe7, 0x2e, 0x4f, 0x70, 0x97, 0x30, 0x3a, 0x00, 0x47, - 0x44, 0x54, 0x58, 0x40, 0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, - 0x97, 0x4a, 0xcb, 0x3c, 0xe7, 0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, - 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf, 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, - 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd, 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, - 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, - 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, - 0x20, 0x06, 0x21, 0x58, 0x20, 0xb1, 0x02, 0xcc, 0x2c, 0xb2, 0x6a, 0x3b, 0xe9, 0xc1, 0xd3, - 0x95, 0x10, 0xa0, 0xe1, 0xff, 0x51, 0xde, 0x57, 0xd5, 0x65, 0x28, 0xfd, 0x7f, 0xeb, 0xd4, - 0xca, 0x15, 0xf3, 0xca, 0xdf, 0x37, 0x88, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, - 0x40, 0x58, 0xd8, 0x03, 0x24, 0x53, 0x60, 0x57, 0xa9, 0x09, 0xfa, 0xab, 0xdc, 0x57, 0x1e, - 0xf0, 0xe5, 0x1e, 0x51, 0x6f, 0x9e, 0xa3, 0x42, 0xe6, 0x6a, 0x8c, 0xaa, 0xad, 0x08, 0x48, - 0xde, 0x7f, 0x4f, 0x6e, 0x2f, 0x7f, 0x39, 0x6c, 0xa1, 0xf8, 0x42, 0x71, 0xfe, 0x17, 0x3d, - 0xca, 0x31, 0x83, 0x92, 0xed, 0xbb, 0x40, 0xb8, 0x10, 0xe0, 0xf2, 0x5a, 0x99, 0x53, 0x38, - 0x46, 0x33, 0x97, 0x78, 0x05, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, - 0x01, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, - 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, - 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x02, 0x78, - 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34, 0x38, 0x37, 0x30, - 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36, 0x37, 0x65, 0x61, 0x34, - 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, 0x35, 0x3a, 0x00, 0x47, 0x44, - 0x50, 0x58, 0x40, 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, - 0x83, 0x7f, 0x46, 0x8d, 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, - 0x56, 0xb3, 0xbf, 0x2f, 0xfa, 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18, - 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, 0xd2, - 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, - 0x00, 0x01, 0x11, 0x71, 0x63, 0x41, 0x56, 0x42, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, - 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x93, 0x17, 0xe1, - 0x11, 0x27, 0x59, 0xd0, 0xef, 0x75, 0x0b, 0x2b, 0x1c, 0x0f, 0x5f, 0x52, 0xc3, 0x29, 0x23, - 0xb5, 0x2a, 0xe6, 0x12, 0x72, 0x6f, 0x39, 0x86, 0x65, 0x2d, 0xf2, 0xe4, 0xe7, 0xd0, 0xaf, - 0x0e, 0xa7, 0x99, 0x16, 0x89, 0x97, 0x21, 0xf7, 0xdc, 0x89, 0xdc, 0xde, 0xbb, 0x94, 0x88, - 0x1f, 0xda, 0xe2, 0xf3, 0xe0, 0x54, 0xf9, 0x0e, 0x29, 0xb1, 0xbd, 0xe1, 0x0c, 0x0b, 0xd7, - 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, - 0x55, 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa, 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, - 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43, 0x13, 0xbf, 0xfe, 0xd3, 0x36, - 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab, 0x3d, 0x0f, 0x89, 0xa4, - 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98, 0x3a, 0x00, 0x47, - 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, - 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x96, 0x6d, 0x96, 0x42, 0xda, 0x64, - 0x51, 0xad, 0xfa, 0x00, 0xbc, 0xbc, 0x95, 0x8a, 0xb0, 0xb9, 0x76, 0x01, 0xe6, 0xbd, 0xc0, - 0x26, 0x79, 0x26, 0xfc, 0x0f, 0x1d, 0x87, 0x65, 0xf1, 0xf3, 0x99, 0x3a, 0x00, 0x47, 0x44, - 0x58, 0x41, 0x20, 0x58, 0x40, 0x10, 0x7f, 0x77, 0xad, 0x70, 0xbd, 0x52, 0x81, 0x28, 0x8d, - 0x24, 0x81, 0xb4, 0x3f, 0x21, 0x68, 0x9f, 0xc3, 0x80, 0x68, 0x86, 0x55, 0xfb, 0x2e, 0x6d, - 0x96, 0xe1, 0xe1, 0xb7, 0x28, 0x8d, 0x63, 0x85, 0xba, 0x2a, 0x01, 0x33, 0x87, 0x60, 0x63, - 0xbb, 0x16, 0x3f, 0x2f, 0x3d, 0xf4, 0x2d, 0x48, 0x5b, 0x87, 0xed, 0xda, 0x34, 0xeb, 0x9c, - 0x4d, 0x14, 0xac, 0x65, 0xf4, 0xfa, 0xef, 0x45, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, - 0x59, 0x01, 0x8f, 0xa9, 0x01, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, - 0x39, 0x37, 0x34, 0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, - 0x32, 0x36, 0x37, 0x65, 0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, - 0x32, 0x35, 0x02, 0x78, 0x28, 0x35, 0x64, 0x34, 0x65, 0x64, 0x37, 0x66, 0x34, 0x31, 0x37, - 0x61, 0x39, 0x35, 0x34, 0x61, 0x31, 0x38, 0x31, 0x34, 0x30, 0x37, 0x62, 0x35, 0x38, 0x38, - 0x35, 0x61, 0x66, 0x64, 0x37, 0x32, 0x61, 0x35, 0x62, 0x66, 0x34, 0x30, 0x64, 0x61, 0x36, - 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x47, 0x44, - 0x53, 0x58, 0x1a, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x67, 0x41, 0x6e, 0x64, 0x72, 0x6f, - 0x69, 0x64, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x0c, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, - 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x26, 0x1a, 0xbd, 0x26, 0xd8, 0x37, 0x8f, 0x4a, 0xf2, - 0x9e, 0x49, 0x4d, 0x93, 0x23, 0xc4, 0x6e, 0x02, 0xda, 0xe0, 0x00, 0x02, 0xe7, 0xed, 0x29, - 0xdf, 0x2b, 0xb3, 0x69, 0xf3, 0x55, 0x0e, 0x4c, 0x22, 0xdc, 0xcf, 0xf5, 0x92, 0xc9, 0xfa, - 0x78, 0x98, 0xf1, 0x0e, 0x55, 0x5f, 0xf4, 0x45, 0xed, 0xc0, 0x0a, 0x72, 0x2a, 0x7a, 0x3a, - 0xd2, 0xb1, 0xf7, 0x76, 0xfe, 0x2a, 0x6b, 0x7b, 0x2a, 0x53, 0x3a, 0x00, 0x47, 0x44, 0x54, - 0x58, 0x40, 0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, - 0x03, 0xb8, 0xd6, 0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37, - 0x68, 0x4e, 0x1d, 0xc0, 0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, 0xd2, - 0x9c, 0xfc, 0x12, 0x9e, 0x77, 0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, 0x10, 0xd5, - 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, - 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, - 0x21, 0x58, 0x20, 0xdb, 0xe7, 0x5b, 0x3f, 0xa3, 0x42, 0xb0, 0x9c, 0xf8, 0x40, 0x8c, 0xb0, - 0x9c, 0xf0, 0x0a, 0xaf, 0xdf, 0x6f, 0xe5, 0x09, 0x21, 0x11, 0x92, 0xe1, 0xf8, 0xc5, 0x09, - 0x02, 0x3d, 0x1f, 0xb7, 0xc5, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xc4, - 0xc1, 0xd7, 0x1c, 0x2d, 0x26, 0x89, 0x22, 0xcf, 0xa6, 0x99, 0x77, 0x30, 0x84, 0x86, 0x27, - 0x59, 0x8f, 0xd8, 0x08, 0x75, 0xe0, 0xb2, 0xef, 0xf9, 0xfa, 0xa5, 0x40, 0x8c, 0xd3, 0xeb, - 0xbb, 0xda, 0xf2, 0xc8, 0xae, 0x41, 0x22, 0x50, 0x9c, 0xe8, 0xb2, 0x9c, 0x9b, 0x3f, 0x8a, - 0x78, 0x76, 0xab, 0xd0, 0xbe, 0xfc, 0xe4, 0x79, 0xcb, 0x1b, 0x2b, 0xaa, 0x4d, 0xdd, 0x15, - 0x61, 0x42, 0x06, - ]; - - // This test invokes make_sample_bcc_and_cdis and compares the result bitwise to the target - // vectors. The function uses main_flow, bcc_main_flow, format_config_descriptor, - // derive_cdi_private_key_seed, and keypair_from_seed. This test is sensitive to errors - // and changes in any of those functions. - #[test] - fn main_flow_and_bcc_main_flow() { - let dice_artifacts = make_sample_bcc_and_cdis().unwrap(); - assert_eq!(dice_artifacts.cdi_attest(), SAMPLE_CDI_ATTEST_TEST_VECTOR); - assert_eq!(dice_artifacts.cdi_seal(), SAMPLE_CDI_SEAL_TEST_VECTOR); - assert_eq!(dice_artifacts.bcc(), Some(SAMPLE_BCC_TEST_VECTOR)); - } -} diff --git a/diced/src/sample_inputs.rs b/diced/src/sample_inputs.rs deleted file mode 100644 index 8b914f3c..00000000 --- a/diced/src/sample_inputs.rs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2021, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This module provides a set of sample input values for a DICE chain, a sample UDS, -//! as well as tuple of CDIs and BCC derived thereof. - -use anyhow::{anyhow, Context, Result}; -use ciborium::{de, ser, value::Value}; -use coset::{iana, Algorithm, AsCborValue, CoseKey, KeyOperation, KeyType, Label}; -use diced_open_dice::{ - derive_cdi_private_key_seed, keypair_from_seed, retry_bcc_format_config_descriptor, - retry_bcc_main_flow, retry_dice_main_flow, Config, DiceArtifacts, DiceMode, InputValues, - OwnedDiceArtifacts, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE, -}; -use std::ffi::CStr; - -/// Sample UDS used to perform the root dice flow by `make_sample_bcc_and_cdis`. -pub const UDS: &[u8; CDI_SIZE] = &[ - 0x65, 0x4f, 0xab, 0xa9, 0xa5, 0xad, 0x0f, 0x5e, 0x15, 0xc3, 0x12, 0xf7, 0x77, 0x45, 0xfa, 0x55, - 0x18, 0x6a, 0xa6, 0x34, 0xb6, 0x7c, 0x82, 0x7b, 0x89, 0x4c, 0xc5, 0x52, 0xd3, 0x27, 0x35, 0x8e, -]; - -const CODE_HASH_ABL: [u8; HASH_SIZE] = [ - 0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26, - 0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, 0x25, - 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, 0xd3, 0xfb, - 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7, 0x15, 0x98, 0x14, -]; -const AUTHORITY_HASH_ABL: [u8; HASH_SIZE] = [ - 0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7, - 0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf, - 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd, - 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d, -]; -const HIDDEN_ABL: [u8; HIDDEN_SIZE] = [ - 0xa2, 0x01, 0xd0, 0xc0, 0xaa, 0x75, 0x3c, 0x06, 0x43, 0x98, 0x6c, 0xc3, 0x5a, 0xb5, 0x5f, 0x1f, - 0x0f, 0x92, 0x44, 0x3b, 0x0e, 0xd4, 0x29, 0x75, 0xe3, 0xdb, 0x36, 0xda, 0xc8, 0x07, 0x97, 0x4d, - 0xff, 0xbc, 0x6a, 0xa4, 0x8a, 0xef, 0xc4, 0x7f, 0xf8, 0x61, 0x7d, 0x51, 0x4d, 0x2f, 0xdf, 0x7e, - 0x8c, 0x3d, 0xa3, 0xfc, 0x63, 0xd4, 0xd4, 0x74, 0x8a, 0xc4, 0x14, 0x45, 0x83, 0x6b, 0x12, 0x7e, -]; -const CODE_HASH_AVB: [u8; HASH_SIZE] = [ - 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, 0x7f, 0x46, 0x8d, - 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, 0xb3, 0xbf, 0x2f, 0xfa, - 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, - 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, -]; -const AUTHORITY_HASH_AVB: [u8; HASH_SIZE] = [ - 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa, - 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43, - 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab, - 0x3d, 0x0f, 0x89, 0xa4, 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98, -]; -const HIDDEN_AVB: [u8; HIDDEN_SIZE] = [ - 0x5b, 0x3f, 0xc9, 0x6b, 0xe3, 0x95, 0x59, 0x40, 0x5e, 0x64, 0xe5, 0x64, 0x3f, 0xfd, 0x21, 0x09, - 0x9d, 0xf3, 0xcd, 0xc7, 0xa4, 0x2a, 0xe2, 0x97, 0xdd, 0xe2, 0x4f, 0xb0, 0x7d, 0x7e, 0xf5, 0x8e, - 0xd6, 0x4d, 0x84, 0x25, 0x54, 0x41, 0x3f, 0x8f, 0x78, 0x64, 0x1a, 0x51, 0x27, 0x9d, 0x55, 0x8a, - 0xe9, 0x90, 0x35, 0xab, 0x39, 0x80, 0x4b, 0x94, 0x40, 0x84, 0xa2, 0xfd, 0x73, 0xeb, 0x35, 0x7a, -]; -const AUTHORITY_HASH_ANDROID: [u8; HASH_SIZE] = [ - 0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, 0x03, 0xb8, 0xd6, - 0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37, 0x68, 0x4e, 0x1d, 0xc0, - 0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, 0xd2, 0x9c, 0xfc, 0x12, 0x9e, 0x77, - 0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, 0x10, 0xd5, 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, -]; - -fn ed25519_public_key_to_cbor_value(public_key: &[u8]) -> Result<Value> { - let key = CoseKey { - kty: KeyType::Assigned(iana::KeyType::OKP), - alg: Some(Algorithm::Assigned(iana::Algorithm::EdDSA)), - key_ops: vec![KeyOperation::Assigned(iana::KeyOperation::Verify)].into_iter().collect(), - params: vec![ - ( - Label::Int(iana::Ec2KeyParameter::Crv as i64), - Value::from(iana::EllipticCurve::Ed25519 as u64), - ), - (Label::Int(iana::Ec2KeyParameter::X as i64), Value::Bytes(public_key.to_vec())), - ], - ..Default::default() - }; - key.to_cbor_value() - .map_err(|e| anyhow!(format!("Failed to serialize the key to CBOR data. Error: {e}"))) -} - -/// Makes a DICE chain (BCC) from the sample input. -/// -/// The DICE chain is of the following format: -/// public key derived from UDS -> ABL certificate -> AVB certificate -> Android certificate -pub fn make_sample_bcc_and_cdis() -> Result<OwnedDiceArtifacts> { - let private_key_seed = derive_cdi_private_key_seed(UDS) - .context("In make_sample_bcc_and_cdis: Trying to derive private key seed.")?; - - // Gets the root public key in DICE chain (BCC). - let (public_key, _) = keypair_from_seed(private_key_seed.as_array()) - .context("In make_sample_bcc_and_cids: Failed to generate key pair.")?; - let ed25519_public_key_value = ed25519_public_key_to_cbor_value(&public_key)?; - - // Gets the ABL certificate to as the root certificate of DICE chain. - let config_descriptor = retry_bcc_format_config_descriptor( - Some(CStr::from_bytes_with_nul(b"ABL\0").unwrap()), - Some(1), // version - true, - )?; - let input_values = InputValues::new( - CODE_HASH_ABL, - Config::Descriptor(config_descriptor.as_slice()), - AUTHORITY_HASH_ABL, - DiceMode::kDiceModeNormal, - HIDDEN_ABL, - ); - let (cdi_values, cert) = retry_dice_main_flow(UDS, UDS, &input_values) - .context("In make_sample_bcc_and_cdis: Trying to run first main flow.")?; - let bcc_value = Value::Array(vec![ - ed25519_public_key_value, - de::from_reader(&cert[..]).context("Deserialize root DICE certificate failed")?, - ]); - let mut bcc: Vec<u8> = vec![]; - ser::into_writer(&bcc_value, &mut bcc)?; - - // Appends AVB certificate to DICE chain. - let config_descriptor = retry_bcc_format_config_descriptor( - Some(CStr::from_bytes_with_nul(b"AVB\0").unwrap()), - Some(1), // version - true, - )?; - let input_values = InputValues::new( - CODE_HASH_AVB, - Config::Descriptor(config_descriptor.as_slice()), - AUTHORITY_HASH_AVB, - DiceMode::kDiceModeNormal, - HIDDEN_AVB, - ); - let dice_artifacts = - retry_bcc_main_flow(&cdi_values.cdi_attest, &cdi_values.cdi_seal, &bcc, &input_values) - .context("In make_sample_bcc_and_cdis: Trying to run first bcc main flow.")?; - - // Appends Android certificate to DICE chain. - let config_descriptor = retry_bcc_format_config_descriptor( - Some(CStr::from_bytes_with_nul(b"Android\0").unwrap()), - Some(12), // version - true, - )?; - let input_values = InputValues::new( - [0u8; HASH_SIZE], // code_hash - Config::Descriptor(config_descriptor.as_slice()), - AUTHORITY_HASH_ANDROID, - DiceMode::kDiceModeNormal, - [0u8; HIDDEN_SIZE], // hidden - ); - retry_bcc_main_flow( - dice_artifacts.cdi_attest(), - dice_artifacts.cdi_seal(), - dice_artifacts.bcc().ok_or_else(|| anyhow!("bcc is none"))?, - &input_values, - ) - .context("In make_sample_bcc_and_cdis: Trying to run second bcc main flow.") -} - -#[cfg(test)] -mod test { - use super::*; - - // This simple test checks if the invocation succeeds, essentially it tests - // if the initial bcc is accepted by `diced_open_dice::retry_bcc_main_flow`. - #[test] - fn make_sample_bcc_and_cdis_test() { - make_sample_bcc_and_cdis().unwrap(); - } -} diff --git a/diced/src/utils.rs b/diced/src/utils.rs deleted file mode 100644 index 0931e685..00000000 --- a/diced/src/utils.rs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2021, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Implements utility functions and types for diced and the dice HAL. - -/// This submodule implements a limited set of CBOR generation functionality. Essentially, -/// a cbor header generator and some convenience functions for number and BSTR encoding. -pub mod cbor { - use anyhow::{anyhow, Context, Result}; - use std::convert::TryInto; - use std::io::Write; - - /// CBOR encodes a positive number. - pub fn encode_number(n: u64, buffer: &mut dyn Write) -> Result<()> { - encode_header(0, n, buffer) - } - - /// CBOR encodes a binary string. - pub fn encode_bstr(bstr: &[u8], buffer: &mut dyn Write) -> Result<()> { - encode_header( - 2, - bstr.len().try_into().context("In encode_bstr: Failed to convert usize to u64.")?, - buffer, - ) - .context("In encode_bstr: While writing header.")?; - let written = buffer.write(bstr).context("In encode_bstr: While writing payload.")?; - if written != bstr.len() { - return Err(anyhow!("In encode_bstr: Buffer too small. ({}, {})", written, bstr.len())); - } - Ok(()) - } - - /// Formats a CBOR header. `t` is the type, and n is the header argument. - pub fn encode_header(t: u8, n: u64, buffer: &mut dyn Write) -> Result<()> { - match n { - n if n < 24 => { - let written = - buffer.write(&u8::to_be_bytes((t << 5) | (n as u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - } - n if n <= 0xFF => { - let written = - buffer.write(&u8::to_be_bytes((t << 5) | (24u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - let written = buffer.write(&u8::to_be_bytes(n as u8)).with_context(|| { - format!("In encode_header: Failed to write size ({}, {})", t, n) - })?; - if written != 1 { - return Err(anyhow!( - "In encode_header while writing size: Buffer to small. ({}, {})", - t, - n - )); - } - } - n if n <= 0xFFFF => { - let written = - buffer.write(&u8::to_be_bytes((t << 5) | (25u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - let written = buffer.write(&u16::to_be_bytes(n as u16)).with_context(|| { - format!("In encode_header: Failed to write size ({}, {})", t, n) - })?; - if written != 2 { - return Err(anyhow!( - "In encode_header while writing size: Buffer to small. ({}, {})", - t, - n - )); - } - } - n if n <= 0xFFFFFFFF => { - let written = - buffer.write(&u8::to_be_bytes((t << 5) | (26u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - let written = buffer.write(&u32::to_be_bytes(n as u32)).with_context(|| { - format!("In encode_header: Failed to write size ({}, {})", t, n) - })?; - if written != 4 { - return Err(anyhow!( - "In encode_header while writing size: Buffer to small. ({}, {})", - t, - n - )); - } - } - n => { - let written = - buffer.write(&u8::to_be_bytes((t << 5) | (27u8 & 0x1F))).with_context( - || format!("In encode_header: Failed to write header ({}, {})", t, n), - )?; - if written != 1 { - return Err(anyhow!("In encode_header: Buffer to small. ({}, {})", t, n)); - } - let written = buffer.write(&u64::to_be_bytes(n)).with_context(|| { - format!("In encode_header: Failed to write size ({}, {})", t, n) - })?; - if written != 8 { - return Err(anyhow!( - "In encode_header while writing size: Buffer to small. ({}, {})", - t, - n - )); - } - } - } - Ok(()) - } - - #[cfg(test)] - mod test { - use super::*; - - fn encode_header_helper(t: u8, n: u64) -> Vec<u8> { - let mut b: Vec<u8> = vec![]; - encode_header(t, n, &mut b).unwrap(); - b - } - - #[test] - fn encode_header_test() { - assert_eq!(&encode_header_helper(0, 0), &[0b000_00000]); - assert_eq!(&encode_header_helper(0, 23), &[0b000_10111]); - assert_eq!(&encode_header_helper(0, 24), &[0b000_11000, 24]); - assert_eq!(&encode_header_helper(0, 0xff), &[0b000_11000, 0xff]); - assert_eq!(&encode_header_helper(0, 0x100), &[0b000_11001, 0x01, 0x00]); - assert_eq!(&encode_header_helper(0, 0xffff), &[0b000_11001, 0xff, 0xff]); - assert_eq!(&encode_header_helper(0, 0x10000), &[0b000_11010, 0x00, 0x01, 0x00, 0x00]); - assert_eq!( - &encode_header_helper(0, 0xffffffff), - &[0b000_11010, 0xff, 0xff, 0xff, 0xff] - ); - assert_eq!( - &encode_header_helper(0, 0x100000000), - &[0b000_11011, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00] - ); - assert_eq!( - &encode_header_helper(0, 0xffffffffffffffff), - &[0b000_11011, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff] - ); - } - } -} diff --git a/fsverity/fsverity_manifest_generator.py b/fsverity/fsverity_manifest_generator.py index 181758aa..ca7ac5cb 100644 --- a/fsverity/fsverity_manifest_generator.py +++ b/fsverity/fsverity_manifest_generator.py @@ -35,7 +35,7 @@ def _digest(fsverity_path, input_file): return bytes(bytearray.fromhex(out)) if __name__ == '__main__': - p = argparse.ArgumentParser() + p = argparse.ArgumentParser(fromfile_prefix_chars='@') p.add_argument( '--output', help='Path to the output manifest', @@ -52,7 +52,7 @@ if __name__ == '__main__': 'inputs', nargs='*', help='input file for the build manifest') - args = p.parse_args(sys.argv[1:]) + args = p.parse_args() digests = FSVerityDigests() for f in sorted(args.inputs): diff --git a/fsverity/libfsverity_rs/Android.bp b/fsverity/libfsverity_rs/Android.bp new file mode 100644 index 00000000..91b12486 --- /dev/null +++ b/fsverity/libfsverity_rs/Android.bp @@ -0,0 +1,17 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_library { + name: "libfsverity_rs", + crate_name: "fsverity", + srcs: ["lib.rs"], + edition: "2021", + rustlibs: [ + "libnix", + ], + apex_available: [ + "com.android.compos", + "com.android.virt", + ], +} diff --git a/fsverity/libfsverity_rs/lib.rs b/fsverity/libfsverity_rs/lib.rs new file mode 100644 index 00000000..473b2d5c --- /dev/null +++ b/fsverity/libfsverity_rs/lib.rs @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! A wrapper library to use fs-verity + +mod sys; + +use crate::sys::*; +use std::io; +use std::os::fd::AsRawFd; +use std::os::unix::io::BorrowedFd; + +fn read_metadata(fd: i32, metadata_type: u64, offset: u64, buf: &mut [u8]) -> io::Result<usize> { + let mut arg = fsverity_read_metadata_arg { + metadata_type, + offset, + length: buf.len() as u64, + buf_ptr: buf.as_mut_ptr() as u64, + __reserved: 0, + }; + // SAFETY: the ioctl doesn't change the sematics in the current process + Ok(unsafe { read_verity_metadata(fd, &mut arg) }? as usize) +} + +/// Read the raw Merkle tree from the fd, if it exists. The API semantics is similar to a regular +/// pread(2), and may not return full requested buffer. +pub fn read_merkle_tree(fd: i32, offset: u64, buf: &mut [u8]) -> io::Result<usize> { + read_metadata(fd, FS_VERITY_METADATA_TYPE_MERKLE_TREE, offset, buf) +} + +/// Read the fs-verity signature from the fd (if exists). The returned signature should be complete. +pub fn read_signature(fd: i32, buf: &mut [u8]) -> io::Result<usize> { + read_metadata(fd, FS_VERITY_METADATA_TYPE_SIGNATURE, 0 /* offset */, buf) +} + +/// Enable fs-verity to the `fd`, with sha256 hash algorithm and 4KB block size. +pub fn enable(fd: BorrowedFd) -> io::Result<()> { + let arg = fsverity_enable_arg { + version: 1, + hash_algorithm: FS_VERITY_HASH_ALG_SHA256, + block_size: 4096, + salt_size: 0, + salt_ptr: 0, + sig_size: 0, + __reserved1: 0, + sig_ptr: 0, + __reserved2: [0; 11], + }; + // SAFETY: the ioctl doesn't change the sematics in the current process + if unsafe { enable_verity(fd.as_raw_fd(), &arg) } == Ok(0) { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} diff --git a/fsverity/libfsverity_rs/sys.rs b/fsverity/libfsverity_rs/sys.rs new file mode 100644 index 00000000..8ce0836d --- /dev/null +++ b/fsverity/libfsverity_rs/sys.rs @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Stable API definition copied from uapi/linux/fsverity.h + +use nix::{ioctl_readwrite, ioctl_write_ptr}; + +const FS_IOCTL_MAGIC: u8 = b'f'; +const FS_IOC_ENABLE_VERITY: u8 = 133; +const FS_IOCTL_READ_VERITY_METADATA: u8 = 135; + +pub const FS_VERITY_HASH_ALG_SHA256: u32 = 1; +pub const FS_VERITY_METADATA_TYPE_MERKLE_TREE: u64 = 1; +pub const FS_VERITY_METADATA_TYPE_SIGNATURE: u64 = 3; + +#[repr(C)] +pub struct fsverity_read_metadata_arg { + pub metadata_type: u64, + pub offset: u64, + pub length: u64, + pub buf_ptr: u64, + pub __reserved: u64, +} + +ioctl_readwrite!( + read_verity_metadata, + FS_IOCTL_MAGIC, + FS_IOCTL_READ_VERITY_METADATA, + fsverity_read_metadata_arg +); + +#[repr(C)] +pub struct fsverity_enable_arg { + pub version: u32, + pub hash_algorithm: u32, + pub block_size: u32, + pub salt_size: u32, + pub salt_ptr: u64, + pub sig_size: u32, + pub __reserved1: u32, + pub sig_ptr: u64, + pub __reserved2: [u64; 11], +} + +ioctl_write_ptr!(enable_verity, FS_IOCTL_MAGIC, FS_IOC_ENABLE_VERITY, fsverity_enable_arg); diff --git a/fsverity_init/Android.bp b/fsverity_init/Android.bp index 83c59457..55884937 100644 --- a/fsverity_init/Android.bp +++ b/fsverity_init/Android.bp @@ -10,11 +10,11 @@ package { cc_binary { name: "fsverity_init", srcs: [ - "main.cpp", + "fsverity_init.cpp", ], static_libs: [ + "aconfig_fsverity_init_c_lib", "libc++fs", - "libfsverity_init", "libmini_keyctl_static", ], shared_libs: [ @@ -22,22 +22,21 @@ cc_binary { "libkeyutils", "liblog", ], - cflags: ["-Werror", "-Wall", "-Wextra"], + cflags: [ + "-Werror", + "-Wall", + "-Wextra", + ], } -cc_library { - name: "libfsverity_init", - srcs: ["fsverity_init.cpp"], - static_libs: [ - "libc++fs", - "libmini_keyctl_static", - ], - shared_libs: [ - "libbase", - "libkeyutils", - "liblog", - ], - cflags: ["-Werror", "-Wall", "-Wextra"], - export_include_dirs: ["include"], - recovery_available: true, +aconfig_declarations { + name: "aconfig_fsverity_init", + package: "android.security.flag", + container: "system", + srcs: ["flags.aconfig"], +} + +cc_aconfig_library { + name: "aconfig_fsverity_init_c_lib", + aconfig_declarations: "aconfig_fsverity_init", } diff --git a/fsverity_init/flags.aconfig b/fsverity_init/flags.aconfig new file mode 100644 index 00000000..495c71c4 --- /dev/null +++ b/fsverity_init/flags.aconfig @@ -0,0 +1,10 @@ +package: "android.security.flag" +container: "system" + +flag { + name: "deprecate_fsverity_init" + namespace: "hardware_backed_security" + description: "Feature flag for deprecate fsverity_init" + bug: "290064770" + is_fixed_read_only: true +} diff --git a/fsverity_init/fsverity_init.cpp b/fsverity_init/fsverity_init.cpp index 61f84ddf..717beebc 100644 --- a/fsverity_init/fsverity_init.cpp +++ b/fsverity_init/fsverity_init.cpp @@ -14,6 +14,25 @@ * limitations under the License. */ +// +// fsverity_init is a tool for loading X.509 certificates into the kernel keyring used by the +// fsverity builtin signature verification kernel feature +// (https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#built-in-signature-verification). +// Starting in Android 14, Android has actually stopped using this feature, as it was too inflexible +// and caused problems. It has been replaced by userspace signature verification. Also, some uses +// of fsverity in Android are now for integrity-only use cases. +// +// Regardless, there may exist fsverity files on-disk that were created by Android 13 or earlier. +// These files still have builtin signatures. If the kernel is an older kernel that still has +// CONFIG_FS_VERITY_BUILTIN_SIGNATURES enabled, these files cannot be opened unless the +// corresponding key is in the ".fs-verity" keyring. Therefore, this tool still has to exist and be +// used to load keys into the kernel, even though this has no security purpose anymore. +// +// This tool can be removed as soon as all supported kernels are guaranteed to have +// CONFIG_FS_VERITY_BUILTIN_SIGNATURES disabled, or alternatively as soon as support for upgrades +// from Android 13 or earlier is no longer required. +// + #define LOG_TAG "fsverity_init" #include <sys/types.h> @@ -23,33 +42,11 @@ #include <android-base/file.h> #include <android-base/logging.h> -#include <android-base/properties.h> #include <android-base/strings.h> +#include <android_security_flag.h> #include <log/log.h> #include <mini_keyctl_utils.h> -bool LoadKeyToKeyring(key_serial_t keyring_id, const char* desc, const char* data, size_t size) { - key_serial_t key = add_key("asymmetric", desc, data, size, keyring_id); - if (key < 0) { - PLOG(ERROR) << "Failed to add key"; - return false; - } - return true; -} - -bool LoadKeyFromStdin(key_serial_t keyring_id, const char* keyname) { - std::string content; - if (!android::base::ReadFdToString(STDIN_FILENO, &content)) { - LOG(ERROR) << "Failed to read key from stdin"; - return false; - } - if (!LoadKeyToKeyring(keyring_id, keyname, content.c_str(), content.size())) { - LOG(ERROR) << "Failed to load key from stdin"; - return false; - } - return true; -} - void LoadKeyFromFile(key_serial_t keyring_id, const char* keyname, const std::string& path) { LOG(INFO) << "LoadKeyFromFile path=" << path << " keyname=" << keyname; std::string content; @@ -57,8 +54,8 @@ void LoadKeyFromFile(key_serial_t keyring_id, const char* keyname, const std::st LOG(ERROR) << "Failed to read key from " << path; return; } - if (!LoadKeyToKeyring(keyring_id, keyname, content.c_str(), content.size())) { - LOG(ERROR) << "Failed to load key from " << path; + if (add_key("asymmetric", keyname, content.c_str(), content.size(), keyring_id) < 0) { + PLOG(ERROR) << "Failed to add key from " << path; } } @@ -81,3 +78,35 @@ void LoadKeyFromVerifiedPartitions(key_serial_t keyring_id) { LoadKeyFromDirectory(keyring_id, "fsv_system_", "/system/etc/security/fsverity"); LoadKeyFromDirectory(keyring_id, "fsv_product_", "/product/etc/security/fsverity"); } + +int main(int argc, const char** argv) { + if (android::security::flag::deprecate_fsverity_init()) { + // Don't load keys to the built-in fs-verity keyring in kernel. This will make existing + // files not readable. We expect to only enable the flag when there are no such files or + // when failure is ok (e.g. with a fallback). + return 0; + } + + if (argc < 2) { + LOG(ERROR) << "Not enough arguments"; + return -1; + } + + key_serial_t keyring_id = android::GetKeyringId(".fs-verity"); + if (keyring_id < 0) { + // This is expected on newer kernels. See comment at the beginning of this file. + LOG(DEBUG) << "no initialization required"; + return 0; + } + + const std::string_view command = argv[1]; + + if (command == "--load-verified-keys") { + LoadKeyFromVerifiedPartitions(keyring_id); + } else { + LOG(ERROR) << "Unknown argument(s)."; + return -1; + } + + return 0; +} diff --git a/fsverity_init/include/fsverity_init.h b/fsverity_init/include/fsverity_init.h deleted file mode 100644 index c3bc93b4..00000000 --- a/fsverity_init/include/fsverity_init.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <mini_keyctl_utils.h> - -bool LoadKeyFromStdin(key_serial_t keyring_id, const char* keyname); -void LoadKeyFromFile(key_serial_t keyring_id, const char* keyname, const std::string& path); -void LoadKeyFromVerifiedPartitions(key_serial_t keyring_id); diff --git a/fsverity_init/main.cpp b/fsverity_init/main.cpp deleted file mode 100644 index b502b91c..00000000 --- a/fsverity_init/main.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <string> - -#include <android-base/file.h> -#include <android-base/logging.h> -#include <android-base/properties.h> -#include <fsverity_init.h> -#include <log/log.h> -#include <mini_keyctl_utils.h> - -int main(int argc, const char** argv) { - if (argc < 2) { - LOG(ERROR) << "Not enough arguments"; - return -1; - } - - key_serial_t keyring_id = android::GetKeyringId(".fs-verity"); - if (keyring_id < 0) { - LOG(ERROR) << "Failed to find .fs-verity keyring id"; - return -1; - } - - const std::string_view command = argv[1]; - - if (command == "--load-verified-keys") { - LoadKeyFromVerifiedPartitions(keyring_id); - } else if (command == "--load-extra-key") { - if (argc != 3) { - LOG(ERROR) << "--load-extra-key requires <key_name> argument."; - return -1; - } - if (!LoadKeyFromStdin(keyring_id, argv[2])) { - return -1; - } - } else if (command == "--lock") { - if (!android::base::GetBoolProperty("ro.debuggable", false)) { - if (keyctl_restrict_keyring(keyring_id, nullptr, nullptr) < 0) { - PLOG(ERROR) << "Cannot restrict .fs-verity keyring"; - } - } - } else { - LOG(ERROR) << "Unknown argument(s)."; - return -1; - } - - return 0; -} diff --git a/identity/Android.bp b/identity/Android.bp index 4f203e6a..a5635321 100644 --- a/identity/Android.bp +++ b/identity/Android.bp @@ -1,4 +1,5 @@ package { + default_team: "trendy_team_android_hardware_backed_security", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "system_security_license" @@ -17,55 +18,63 @@ cc_defaults { "-Wno-deprecated-declarations", ], sanitize: { - misc_undefined : ["integer"], + misc_undefined: ["integer"], }, } -cc_binary { - name: "credstore", +cc_defaults { + name: "credstore_defaults", defaults: [ "identity_defaults", "identity_use_latest_hal_aidl_cpp_static", "keymint_use_latest_hal_aidl_ndk_shared", "keymint_use_latest_hal_aidl_cpp_static", + "android.hardware.identity-support-lib-deps", ], - srcs: [ "Credential.cpp", "CredentialData.cpp", "CredentialStore.cpp", "CredentialStoreFactory.cpp", - "RemotelyProvisionedKey.cpp", "Session.cpp", "Util.cpp", "WritableCredential.cpp", - "main.cpp", ], - init_rc: ["credstore.rc"], shared_libs: [ - "android.hardware.identity-support-lib", "android.hardware.keymaster@4.0", "android.security.authorization-ndk", - "android.security.remoteprovisioning-cpp", "libbase", "libbinder", "libbinder_ndk", - "libcredstore_aidl", "libcrypto", "libhidlbase", - "libkeymaster4support", - "libkeystore-attestation-application-id", + "liblog", "libutils", "libutilscallstack", - "libvintf", + "libkeystore-attestation-application-id", ], static_libs: [ - "android.hardware.security.rkp-V3-cpp", "android.hardware.keymaster-V3-cpp", + "android.hardware.identity-support-lib", + "android.hardware.security.rkp-V3-cpp", "android.security.rkp_aidl-cpp", - "libcppbor_external", + "libcppbor", + "libcredstore_aidl", + "libkeymaster4support", + "librkp_support", + ], +} + +cc_binary { + name: "credstore", + defaults: [ + "credstore_defaults", + ], + srcs: [ + "main.cpp", ], + init_rc: ["credstore.rc"], } filegroup { @@ -90,11 +99,11 @@ filegroup { path: "binder", } -cc_library_shared { +cc_library_static { name: "libcredstore_aidl", srcs: [ ":credstore_aidl", - ], + ], aidl: { export_aidl_headers: true, include_dirs: [ @@ -104,9 +113,30 @@ cc_library_shared { shared_libs: [ "libbinder", "libutils", + ], + static_libs: [ "libkeymaster4support", ], export_shared_lib_headers: [ "libbinder", ], } + +cc_fuzz { + name: "credstore_service_fuzzer", + defaults: [ + "credstore_defaults", + "service_fuzzer_defaults", + "fuzzer_disable_leaks", + ], + srcs: [ + "fuzzers/credstore_service_fuzzer.cpp", + ], + fuzz_config: { + triage_assignee: "waghpawan@google.com", + cc: [ + "trong@google.com", + "zeuthen@google.com", + ], + }, +} diff --git a/identity/CredentialData.cpp b/identity/CredentialData.cpp index 1bf1527b..ecf2258c 100644 --- a/identity/CredentialData.cpp +++ b/identity/CredentialData.cpp @@ -520,10 +520,13 @@ AuthKeyData* CredentialData::findAuthKey_(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys) { AuthKeyData* candidate = nullptr; - int64_t nowMilliSeconds = - std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) * 1000; + time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + int64_t nowMilliSeconds; + if (__builtin_mul_overflow(int64_t(now), int64_t(1000), &nowMilliSeconds)) { + LOG(ERROR) << "Overflow converting " << now << " to milliseconds"; + return nullptr; + } - int n = 0; for (AuthKeyData& data : authKeyDatas_) { if (nowMilliSeconds > data.expirationDateMillisSinceEpoch) { if (!allowUsingExpiredKeys) { @@ -536,7 +539,6 @@ AuthKeyData* CredentialData::findAuthKey_(bool allowUsingExhaustedKeys, candidate = &data; } } - n++; } if (candidate == nullptr) { diff --git a/identity/CredentialStore.cpp b/identity/CredentialStore.cpp index e2b3cf46..57361c03 100644 --- a/identity/CredentialStore.cpp +++ b/identity/CredentialStore.cpp @@ -20,19 +20,15 @@ #include <optional> #include <android-base/logging.h> -#include <android-base/properties.h> #include <android/hardware/security/keymint/IRemotelyProvisionedComponent.h> #include <android/hardware/security/keymint/RpcHardwareInfo.h> -#include <android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.h> -#include <android/security/remoteprovisioning/RemotelyProvisionedKey.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> -#include <vintf/VintfObject.h> +#include <rkp/support/rkpd_client.h> #include "Credential.h" #include "CredentialData.h" #include "CredentialStore.h" -#include "RemotelyProvisionedKey.h" #include "Session.h" #include "Util.h" #include "WritableCredential.h" @@ -42,13 +38,8 @@ namespace security { namespace identity { namespace { -using ::android::security::remoteprovisioning::IRemotelyProvisionedKeyPool; -using ::android::security::rkp::IRemoteProvisioning; - -bool useRkpd() { - return android::base::GetBoolProperty("remote_provisioning.enable_rkpd", - /*default_value=*/true); -} +using ::android::security::rkp::RemotelyProvisionedKey; +using ::android::security::rkp::support::getRpcKey; } // namespace @@ -189,61 +180,23 @@ Status CredentialStore::setRemotelyProvisionedAttestationKey( std::vector<uint8_t> encodedCertChain; Status status; - if (useRkpd()) { - LOG(INFO) << "Fetching attestation key from RKPD"; - - uid_t callingUid = android::IPCThreadState::self()->getCallingUid(); - auto rpcKeyFuture = getRpcKeyFuture(rpc_, callingUid); - if (!rpcKeyFuture) { - return Status::fromServiceSpecificError(ERROR_GENERIC, "Error in getRpcKeyFuture()"); - } - - if (rpcKeyFuture->wait_for(std::chrono::seconds(10)) != std::future_status::ready) { - return Status::fromServiceSpecificError( - ERROR_GENERIC, "Waiting for remotely provisioned attestation key timed out"); - } - - std::optional<::android::security::rkp::RemotelyProvisionedKey> key = rpcKeyFuture->get(); - if (!key) { - return Status::fromServiceSpecificError( - ERROR_GENERIC, "Failed to get remotely provisioned attestation key"); - } - - if (key->keyBlob.empty()) { - return Status::fromServiceSpecificError( - ERROR_GENERIC, "Remotely provisioned attestation key blob is empty"); - } - - keyBlob = std::move(key->keyBlob); - encodedCertChain = std::move(key->encodedCertChain); - } else { - LOG(INFO) << "Fetching attestation key from remotely provisioned key pool."; - - sp<IRemotelyProvisionedKeyPool> keyPool = - android::waitForService<IRemotelyProvisionedKeyPool>( - IRemotelyProvisionedKeyPool::descriptor); - if (!keyPool) { - return Status::fromServiceSpecificError( - ERROR_GENERIC, "Error getting IRemotelyProvisionedKeyPool HAL"); - } - - std::optional<std::string> rpcId = getRpcId(rpc_); - if (!rpcId) { - return Status::fromServiceSpecificError( - ERROR_GENERIC, "Error getting remotely provisioned component id"); - } + LOG(INFO) << "Fetching attestation key from RKPD"; - uid_t callingUid = android::IPCThreadState::self()->getCallingUid(); - ::android::security::remoteprovisioning::RemotelyProvisionedKey key; - Status status = keyPool->getAttestationKey(callingUid, *rpcId, &key); - if (!status.isOk()) { - return status; - } + uid_t callingUid = android::IPCThreadState::self()->getCallingUid(); + std::optional<RemotelyProvisionedKey> key = getRpcKey(rpc_, callingUid); + if (!key) { + return Status::fromServiceSpecificError( + ERROR_GENERIC, "Failed to get remotely provisioned attestation key"); + } - keyBlob = std::move(key.keyBlob); - encodedCertChain = std::move(key.encodedCertChain); + if (key->keyBlob.empty()) { + return Status::fromServiceSpecificError( + ERROR_GENERIC, "Remotely provisioned attestation key blob is empty"); } + keyBlob = std::move(key->keyBlob); + encodedCertChain = std::move(key->encodedCertChain); + status = halWritableCredential->setRemotelyProvisionedAttestationKey(keyBlob, encodedCertChain); if (!status.isOk()) { LOG(ERROR) << "Error setting remotely provisioned attestation key on credential"; diff --git a/identity/CredentialStore.h b/identity/CredentialStore.h index 57c94e04..8bc02e8e 100644 --- a/identity/CredentialStore.h +++ b/identity/CredentialStore.h @@ -22,8 +22,6 @@ #include <android/hardware/identity/IIdentityCredentialStore.h> #include <android/security/identity/BnCredentialStore.h> -#include <android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.h> -#include <android/security/rkp/IRemoteProvisioning.h> namespace android { namespace security { @@ -41,7 +39,6 @@ using ::android::hardware::identity::IIdentityCredentialStore; using ::android::hardware::identity::IPresentationSession; using ::android::hardware::identity::IWritableIdentityCredential; using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; -using ::android::security::remoteprovisioning::IRemotelyProvisionedKeyPool; class CredentialStore : public BnCredentialStore { public: diff --git a/identity/fuzzers/credstore_service_fuzzer.cpp b/identity/fuzzers/credstore_service_fuzzer.cpp new file mode 100644 index 00000000..008cb0d8 --- /dev/null +++ b/identity/fuzzers/credstore_service_fuzzer.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android-base/logging.h> +#include <fuzzbinder/libbinder_driver.h> +#include <sys/stat.h> + +#include "CredentialStoreFactory.h" + +using android::security::identity::CredentialStoreFactory; +using namespace android; + +void clearDirectory(const char* dirpath, bool recursive) { + DIR* dir = opendir(dirpath); + CHECK(dir != nullptr); + dirent* e; + struct stat s; + while ((e = readdir(dir)) != nullptr) { + if ((strcmp(e->d_name, ".") == 0) || (strcmp(e->d_name, "..") == 0)) { + continue; + } + std::string filename(dirpath); + filename.push_back('/'); + filename.append(e->d_name); + int stat_result = lstat(filename.c_str(), &s); + CHECK_EQ(0, stat_result) << "unable to stat " << filename; + if (S_ISDIR(s.st_mode)) { + if (recursive) { + clearDirectory(filename.c_str(), true); + int rmdir_result = rmdir(filename.c_str()); + CHECK_EQ(0, rmdir_result) << filename; + } + } else { + int unlink_result = unlink(filename.c_str()); + CHECK_EQ(0, unlink_result) << filename; + } + } + closedir(dir); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::string dataDir = "/data/cred_store_fuzzer"; + mkdir(dataDir.c_str(), 0700); + sp<CredentialStoreFactory> service = sp<CredentialStoreFactory>::make(dataDir); + fuzzService(service, FuzzedDataProvider(data, size)); + clearDirectory(dataDir.c_str(), true); + rmdir(dataDir.c_str()); + return 0; +} diff --git a/identity/util/Android.bp b/identity/util/Android.bp index 71d77187..771fe792 100644 --- a/identity/util/Android.bp +++ b/identity/util/Android.bp @@ -13,6 +13,7 @@ // limitations under the License. package { + default_team: "trendy_team_android_hardware_backed_security", default_applicable_licenses: ["Android-Apache-2.0"], } diff --git a/keystore-engine/Android.bp b/keystore-engine/Android.bp index cb75cde7..7fbfe53a 100644 --- a/keystore-engine/Android.bp +++ b/keystore-engine/Android.bp @@ -21,9 +21,14 @@ package { default_applicable_licenses: ["system_security_license"], } -cc_library_shared { +// This is expected to be cc_test_library but due to issue mentioned in b/298668920, b/314110490 +// we are creating cc_library and using static library to link with `keystore_client_tests`. +cc_library { name: "libkeystore-engine", + defaults: [ + "keystore2_use_latest_aidl_ndk_shared", + ], srcs: [ "android_engine.cpp", "keystore2_engine.cpp", @@ -36,7 +41,7 @@ cc_library_shared { ], shared_libs: [ - "android.system.keystore2-V1-ndk", + "android.system.keystore2-V4-ndk", "libbinder_ndk", "libcrypto", "libcutils", diff --git a/keystore/Android.bp b/keystore/Android.bp index 221ead9b..c79d00ba 100644 --- a/keystore/Android.bp +++ b/keystore/Android.bp @@ -69,19 +69,16 @@ cc_library { defaults: ["keystore_defaults"], srcs: [ - ":IKeyAttestationApplicationIdProvider.aidl", "keystore_attestation_id.cpp", - "KeyAttestationApplicationId.cpp", - "KeyAttestationPackageInfo.cpp", - "Signature.cpp", ], shared_libs: [ + "android.security.aaid_aidl-cpp", "libbase", "libbinder", + "libcrypto", "libhidlbase", "liblog", "libutils", - "libcrypto", ], export_include_dirs: ["include"], diff --git a/keystore/KeyAttestationApplicationId.cpp b/keystore/KeyAttestationApplicationId.cpp deleted file mode 100644 index 1838b07d..00000000 --- a/keystore/KeyAttestationApplicationId.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* -** -** Copyright 2016, 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 "include/keystore/KeyAttestationApplicationId.h" - -#include <binder/Parcel.h> - -namespace android { -namespace security { -namespace keymaster { - -KeyAttestationApplicationId::KeyAttestationApplicationId() = default; - -KeyAttestationApplicationId::KeyAttestationApplicationId( - std::optional<KeyAttestationPackageInfo> package) - : packageInfos_(new std::vector<std::optional<KeyAttestationPackageInfo>>()) { - packageInfos_->push_back(std::move(package)); -} - -KeyAttestationApplicationId::KeyAttestationApplicationId(PackageInfoVector packages) - : packageInfos_(std::make_shared<PackageInfoVector>(std::move(packages))) {} - -status_t KeyAttestationApplicationId::writeToParcel(Parcel* parcel) const { - return parcel->writeParcelableVector(packageInfos_); -} - -status_t KeyAttestationApplicationId::readFromParcel(const Parcel* parcel) { - std::optional<std::vector<std::optional<KeyAttestationPackageInfo>>> temp_vector; - auto rc = parcel->readParcelableVector(&temp_vector); - if (rc != NO_ERROR) return rc; - packageInfos_.reset(); - if (temp_vector) { - packageInfos_ = std::make_shared<PackageInfoVector>(std::move(*temp_vector)); - } - return NO_ERROR; -} - -} // namespace keymaster -} // namespace security -} // namespace android diff --git a/keystore/KeyAttestationPackageInfo.cpp b/keystore/KeyAttestationPackageInfo.cpp deleted file mode 100644 index 8e9a36a2..00000000 --- a/keystore/KeyAttestationPackageInfo.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* -** -** Copyright 2016, 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 "include/keystore/KeyAttestationPackageInfo.h" - -#include <binder/Parcel.h> - -namespace android { -namespace security { -namespace keymaster { - -KeyAttestationPackageInfo::KeyAttestationPackageInfo() = default; - -KeyAttestationPackageInfo::KeyAttestationPackageInfo(const String16& packageName, - int64_t versionCode, - SharedSignaturesVector signatures) - : packageName_(packageName), versionCode_(versionCode), signatures_(signatures) {} - -status_t KeyAttestationPackageInfo::writeToParcel(Parcel* parcel) const { - auto rc = parcel->writeString16(packageName_); - if (rc != NO_ERROR) return rc; - rc = parcel->writeInt64(versionCode_); - if (rc != NO_ERROR) return rc; - return parcel->writeParcelableVector(signatures_); -} - -status_t KeyAttestationPackageInfo::readFromParcel(const Parcel* parcel) { - auto rc = parcel->readString16(&packageName_); - if (rc != NO_ERROR) return rc; - rc = parcel->readInt64(&versionCode_); - if (rc != NO_ERROR) return rc; - - std::optional<SignaturesVector> temp_vector; - rc = parcel->readParcelableVector(&temp_vector); - if (rc != NO_ERROR) return rc; - signatures_.reset(); - if (temp_vector) { - signatures_ = std::make_shared<SignaturesVector>(std::move(*temp_vector)); - } - return NO_ERROR; -} - -} // namespace keymaster -} // namespace security -} // namespace android diff --git a/keystore/Signature.cpp b/keystore/Signature.cpp deleted file mode 100644 index 284f358c..00000000 --- a/keystore/Signature.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* -** -** Copyright 2016, 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 "include/keystore/Signature.h" - -#include <binder/Parcel.h> - -namespace android { -namespace content { -namespace pm { - -status_t Signature::writeToParcel(Parcel* parcel) const { - return parcel->writeByteVector(sig_data_); -} - -status_t Signature::readFromParcel(const Parcel* parcel) { - return parcel->readByteVector(&sig_data_); -} - -Signature::Signature(std::vector<uint8_t> signature_data) : sig_data_(std::move(signature_data)) {} - -} // namespace pm -} // namespace content -} // namespace android diff --git a/keystore/include/keystore/ExportResult.h b/keystore/include/keystore/ExportResult.h deleted file mode 100644 index b5489427..00000000 --- a/keystore/include/keystore/ExportResult.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2017 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_EXPORTRESULT_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_EXPORTRESULT_H_ - -#include <binder/Parcelable.h> - -#include "keystore_return_types.h" - -namespace android { -namespace security { -namespace keymaster { - -struct ExportResult : public ::android::Parcelable { - ExportResult(); - ~ExportResult(); - status_t readFromParcel(const Parcel* in) override; - status_t writeToParcel(Parcel* out) const override; - - ::keystore::KeyStoreServiceReturnCode resultCode; - hardware::hidl_vec<uint8_t> exportData; -}; - -} // namespace keymaster -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_EXPORTRESULT_H_ diff --git a/keystore/include/keystore/KeyAttestationApplicationId.h b/keystore/include/keystore/KeyAttestationApplicationId.h deleted file mode 100644 index 0bf1aad0..00000000 --- a/keystore/include/keystore/KeyAttestationApplicationId.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2016 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYATTESTATIONAPPLICATIONID_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYATTESTATIONAPPLICATIONID_H_ - -#include <memory> -#include <optional> -#include <vector> - -#include <binder/Parcelable.h> - -#include "KeyAttestationPackageInfo.h" - -namespace android { -namespace security { -namespace keymaster { - -class KeyAttestationApplicationId : public Parcelable { - public: - typedef SharedNullableIterator<const KeyAttestationPackageInfo, std::vector> - ConstKeyAttestationPackageInfoIterator; - typedef std::vector<std::optional<KeyAttestationPackageInfo>> PackageInfoVector; - KeyAttestationApplicationId(); - // Following c'tors are for initializing instances containing test data. - explicit KeyAttestationApplicationId(std::optional<KeyAttestationPackageInfo> package); - explicit KeyAttestationApplicationId(PackageInfoVector packages); - - status_t writeToParcel(Parcel*) const override; - status_t readFromParcel(const Parcel* parcel) override; - - ConstKeyAttestationPackageInfoIterator pinfos_begin() const { - return ConstKeyAttestationPackageInfoIterator(packageInfos_); - } - ConstKeyAttestationPackageInfoIterator pinfos_end() const { - return ConstKeyAttestationPackageInfoIterator(); - } - - private: - std::shared_ptr<PackageInfoVector> packageInfos_; -}; - -} // namespace keymaster -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYATTESTATIONAPPLICATIONID_H_ diff --git a/keystore/include/keystore/KeyAttestationPackageInfo.h b/keystore/include/keystore/KeyAttestationPackageInfo.h deleted file mode 100644 index fa638f9a..00000000 --- a/keystore/include/keystore/KeyAttestationPackageInfo.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2016 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYATTESTATIONPACKAGEINFO_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYATTESTATIONPACKAGEINFO_H_ - -#include <stdint.h> - -#include <memory> -#include <optional> -#include <vector> - -#include <binder/Parcelable.h> - -#include "Signature.h" -#include "utils.h" - -namespace android { -namespace security { -namespace keymaster { - -class KeyAttestationPackageInfo : public Parcelable { - public: - typedef SharedNullableIterator<const content::pm::Signature, std::vector> - ConstSignatureIterator; - typedef std::vector<std::optional<content::pm::Signature>> SignaturesVector; - typedef std::shared_ptr<SignaturesVector> SharedSignaturesVector; - - KeyAttestationPackageInfo(const String16& packageName, int64_t versionCode, - SharedSignaturesVector signatures); - KeyAttestationPackageInfo(); - - status_t writeToParcel(Parcel*) const override; - status_t readFromParcel(const Parcel* parcel) override; - - const std::optional<String16>& package_name() const { return packageName_; } - int64_t version_code() const { return versionCode_; } - - ConstSignatureIterator sigs_begin() const { return ConstSignatureIterator(signatures_); } - ConstSignatureIterator sigs_end() const { return ConstSignatureIterator(); } - - private: - std::optional<String16> packageName_; - int64_t versionCode_; - SharedSignaturesVector signatures_; -}; - -} // namespace keymaster -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYATTESTATIONPACKAGEINFO_H_ diff --git a/keystore/include/keystore/KeyCharacteristics.h b/keystore/include/keystore/KeyCharacteristics.h deleted file mode 100644 index 9c90b8a1..00000000 --- a/keystore/include/keystore/KeyCharacteristics.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2017 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYCHARACTERISTICS_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYCHARACTERISTICS_H_ - -#include <binder/Parcelable.h> - -#include "KeymasterArguments.h" -#include "keymaster_types.h" - -namespace android { -namespace security { -namespace keymaster { - -// Parcelable version of keystore::KeyCharacteristics -struct KeyCharacteristics : public ::android::Parcelable { - KeyCharacteristics(){}; - explicit KeyCharacteristics(::keystore::KeyCharacteristics&& other) { - softwareEnforced = std::move(other.softwareEnforced); - hardwareEnforced = std::move(other.hardwareEnforced); - } - explicit KeyCharacteristics(const ::keystore::KeyCharacteristics& other) { - softwareEnforced = KeymasterArguments(other.softwareEnforced); - hardwareEnforced = KeymasterArguments(other.hardwareEnforced); - } - status_t readFromParcel(const Parcel* in) override; - status_t writeToParcel(Parcel* out) const override; - - KeymasterArguments softwareEnforced; - KeymasterArguments hardwareEnforced; -}; - -} // namespace keymaster -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYCHARACTERISTICS_H_ diff --git a/keystore/include/keystore/KeymasterArguments.h b/keystore/include/keystore/KeymasterArguments.h deleted file mode 100644 index 3d22f5f1..00000000 --- a/keystore/include/keystore/KeymasterArguments.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERARGUMENTS_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERARGUMENTS_H_ - -#include <binder/Parcelable.h> - -#include <keystore/keymaster_types.h> - -namespace android { -namespace security { -namespace keymaster { - -// struct for serializing/deserializing a list of KeyParameters -struct KeymasterArguments : public Parcelable { - KeymasterArguments(){}; - // NOLINTNEXTLINE(google-explicit-constructor) - KeymasterArguments(hardware::hidl_vec<::keystore::KeyParameter>&& other); - explicit KeymasterArguments(const hardware::hidl_vec<::keystore::KeyParameter>& other); - - status_t readFromParcel(const Parcel* in) override; - status_t writeToParcel(Parcel* out) const override; - - const inline hardware::hidl_vec<::keystore::KeyParameter>& getParameters() const { - return data_; - } - - private: - hardware::hidl_vec<::keystore::KeyParameter> data_; -}; - -} // namespace keymaster -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERARGUMENTS_H_ diff --git a/keystore/include/keystore/KeymasterBlob.h b/keystore/include/keystore/KeymasterBlob.h deleted file mode 100644 index fc849bd0..00000000 --- a/keystore/include/keystore/KeymasterBlob.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2017 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERBLOB_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERBLOB_H_ - -#include <binder/Parcelable.h> - -namespace android { -namespace security { -namespace keymaster { - -// Parcelable which wraps hardware::hidl_vec<uint8_t> -struct KeymasterBlob : public ::android::Parcelable { - KeymasterBlob(){}; - explicit KeymasterBlob(hardware::hidl_vec<uint8_t> data) : data_(data) {} - status_t readFromParcel(const Parcel* in) override; - status_t writeToParcel(Parcel* out) const override; - const hardware::hidl_vec<uint8_t>& getData() const { return data_; } - - private: - hardware::hidl_vec<uint8_t> data_; -}; - -} // namespace keymaster -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERBLOB_H_ diff --git a/keystore/include/keystore/KeymasterCertificateChain.h b/keystore/include/keystore/KeymasterCertificateChain.h deleted file mode 100644 index f251d084..00000000 --- a/keystore/include/keystore/KeymasterCertificateChain.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2017 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERCERTIFICATECHAIN_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERCERTIFICATECHAIN_H_ - -#include <binder/Parcelable.h> -#include <keystore/keymaster_types.h> - -namespace android { -namespace security { -namespace keymaster { - -// struct for serializing keymaster_cert_chain_t's -struct KeymasterCertificateChain : public ::android::Parcelable { - KeymasterCertificateChain(){}; - explicit KeymasterCertificateChain(hardware::hidl_vec<hardware::hidl_vec<uint8_t>> other) - : chain(std::move(other)) {} - - status_t readFromParcel(const Parcel* in) override; - status_t writeToParcel(Parcel* out) const override; - - private: - // The structure is only used as output and doesn't have getter. - hardware::hidl_vec<hardware::hidl_vec<uint8_t>> chain; -}; - -} // namespace keymaster -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYMASTERCERTIFICATECHAIN_H_ diff --git a/keystore/include/keystore/KeystoreResponse.h b/keystore/include/keystore/KeystoreResponse.h deleted file mode 100644 index 4a7ef0df..00000000 --- a/keystore/include/keystore/KeystoreResponse.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_RESPONSE_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_RESPONSE_H_ - -#include <binder/Parcel.h> -#include <binder/Parcelable.h> -#include <utils/String8.h> - -#include "keystore_return_types.h" - -namespace android { -namespace security { -namespace keystore { - -// struct for holding response code and optionally an error message for keystore -// AIDL callbacks -struct KeystoreResponse : public ::android::Parcelable { - public: - KeystoreResponse() = default; - explicit KeystoreResponse(const int response_code, const String16& error_msg) - : response_code_(response_code), error_msg_(error_msg) {} - explicit KeystoreResponse(const int response_code) - : response_code_(response_code), error_msg_() {} - // NOLINTNEXTLINE(google-explicit-constructor) - KeystoreResponse(const ::keystore::KeyStoreServiceReturnCode& rc) - : response_code_(rc.getErrorCode()), error_msg_() {} - KeystoreResponse(const KeystoreResponse& other) = default; - KeystoreResponse(KeystoreResponse&& other) = default; - - status_t readFromParcel(const Parcel* in) override; - status_t writeToParcel(Parcel* out) const override; - - int response_code() const { return response_code_; } - const std::optional<String16>& error_msg() const { return error_msg_; } - - private: - int response_code_; - std::optional<String16> error_msg_; -}; - -} // namespace keystore -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_RESPONSE_H_ diff --git a/keystore/include/keystore/OperationResult.h b/keystore/include/keystore/OperationResult.h deleted file mode 100644 index caa7cdbf..00000000 --- a/keystore/include/keystore/OperationResult.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_OPERATIONRESULT_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_OPERATIONRESULT_H_ - -#include <binder/Parcel.h> -#include <binder/Parcelable.h> - -#include "keymaster_types.h" -#include "keystore_return_types.h" - -namespace android { -namespace security { -namespace keymaster { - -struct OperationResult : public ::android::Parcelable { - OperationResult(); - status_t readFromParcel(const Parcel* in) override; - status_t writeToParcel(Parcel* out) const override; - - // Native code may need to use KeyStoreNativeReturnCode - ::keystore::KeyStoreServiceReturnCode resultCode; - sp<IBinder> token; - uint64_t handle; - int inputConsumed; - ::keystore::hidl_vec<uint8_t> data; - ::keystore::hidl_vec<::keystore::KeyParameter> outParams; -}; - -OperationResult operationFailed(const ::keystore::KeyStoreServiceReturnCode& error); - -} // namespace keymaster -} // namespace security -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_OPERATIONRESULT_H_ diff --git a/keystore/include/keystore/Signature.h b/keystore/include/keystore/Signature.h deleted file mode 100644 index f39acecf..00000000 --- a/keystore/include/keystore/Signature.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2016 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. - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_SIGNATURE_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_SIGNATURE_H_ - -#include <vector> - -#include <binder/Parcelable.h> - -namespace android { -namespace content { -namespace pm { - -class Signature : public Parcelable { - public: - Signature() = default; - // Intended for initializing instances containing test data. - explicit Signature(std::vector<uint8_t> signature_data); - - status_t writeToParcel(Parcel*) const override; - status_t readFromParcel(const Parcel* parcel) override; - - const std::vector<uint8_t>& data() const & { return sig_data_; } - std::vector<uint8_t>& data() & { return sig_data_; } - std::vector<uint8_t>&& data() && { return std::move(sig_data_); } - - private: - std::vector<uint8_t> sig_data_; -}; - -} // namespace pm -} // namespace content -} // namespace android - -#endif // KEYSTORE_INCLUDE_KEYSTORE_SIGNATURE_H_ diff --git a/keystore/include/keystore/keymaster_types.h b/keystore/include/keystore/keymaster_types.h deleted file mode 100644 index 8da9682a..00000000 --- a/keystore/include/keystore/keymaster_types.h +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2017 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. - -#ifndef SECURITY_KEYSTORE_INCLUDE_KEYSTORE_KEYMASTER_TYPES_H_ -#define SECURITY_KEYSTORE_INCLUDE_KEYSTORE_KEYMASTER_TYPES_H_ - -#include <android/hardware/keymaster/3.0/types.h> -#include <android/hardware/keymaster/4.1/IKeymasterDevice.h> -#include <android/hardware/keymaster/4.1/types.h> - -#include <keymasterV4_1/authorization_set.h> -#include <keymasterV4_1/keymaster_tags.h> - -/** - * This header lifts the types from the current Keymaster version into the keystore namespace. - */ - -namespace keystore { - -// Changing this namespace alias will change the keymaster version. -namespace keymaster = ::android::hardware::keymaster::V4_1; - -using android::hardware::hidl_vec; -using android::hardware::Return; - -using keymaster::IKeymasterDevice; -using keymaster::SecurityLevel; - -using keymaster::AuthorizationSet; -using keymaster::AuthorizationSetBuilder; - -// It's more convenient to use the V4.0 error and tag types by default. -using ::android::hardware::keymaster::V4_0::ErrorCode; -using ::android::hardware::keymaster::V4_0::Tag; - -using V4_1_ErrorCode = ::android::hardware::keymaster::V4_1::ErrorCode; -using V4_1_Tag = ::android::hardware::keymaster::V4_1::Tag; - -using keymaster::Algorithm; -using keymaster::BlockMode; -using keymaster::Digest; -using keymaster::EcCurve; -using keymaster::HardwareAuthenticatorType; -using keymaster::HardwareAuthToken; -using keymaster::HmacSharingParameters; -using keymaster::KeyCharacteristics; -using keymaster::KeyFormat; -using keymaster::KeyParameter; -using keymaster::KeyPurpose; -using keymaster::OperationHandle; -using keymaster::PaddingMode; -using keymaster::SecurityLevel; -using keymaster::TagType; -using keymaster::VerificationToken; - -using keymaster::TAG_ACTIVE_DATETIME; -using keymaster::TAG_ALGORITHM; -using keymaster::TAG_ALLOW_WHILE_ON_BODY; -using keymaster::TAG_APPLICATION_DATA; -using keymaster::TAG_APPLICATION_ID; -using keymaster::TAG_ATTESTATION_APPLICATION_ID; -using keymaster::TAG_AUTH_TIMEOUT; -using keymaster::TAG_BLOB_USAGE_REQUIREMENTS; -using keymaster::TAG_BLOCK_MODE; -using keymaster::TAG_DIGEST; -using keymaster::TAG_EC_CURVE; -using keymaster::TAG_KEY_SIZE; -using keymaster::TAG_MAC_LENGTH; -using keymaster::TAG_MAX_USES_PER_BOOT; -using keymaster::TAG_MIN_MAC_LENGTH; -using keymaster::TAG_MIN_SECONDS_BETWEEN_OPS; -using keymaster::TAG_NO_AUTH_REQUIRED; -using keymaster::TAG_NONCE; -using keymaster::TAG_ORIGIN; -using keymaster::TAG_ORIGINATION_EXPIRE_DATETIME; -using keymaster::TAG_PADDING; -using keymaster::TAG_PURPOSE; -using keymaster::TAG_RESET_SINCE_ID_ROTATION; -using keymaster::TAG_RSA_PUBLIC_EXPONENT; -using keymaster::TAG_USAGE_EXPIRE_DATETIME; -using keymaster::TAG_USER_AUTH_TYPE; -using keymaster::TAG_USER_ID; -using keymaster::TAG_USER_SECURE_ID; - -using keymaster::NullOr; - -using Km3HardwareAuthToken = ::android::hardware::keymaster::V3_0::HardwareAuthToken; -using Km3HardwareAuthenticatorType = - ::android::hardware::keymaster::V3_0::HardwareAuthenticatorType; - -// The following create the numeric values that KM_TAG_PADDING and KM_TAG_DIGEST used to have. We -// need these old values to be able to support old keys that use them. -constexpr int32_t KM_TAG_DIGEST_OLD = static_cast<int32_t>(TagType::ENUM) | 5; -constexpr int32_t KM_TAG_PADDING_OLD = static_cast<int32_t>(TagType::ENUM) | 7; - -} // namespace keystore - -#endif // SYSTEM_SECURITY_KEYSTORE_KM4_AUTHORIZATION_SET_H_ diff --git a/keystore/include/keystore/keystore.h b/keystore/include/keystore/keystore.h deleted file mode 100644 index ab6c6829..00000000 --- a/keystore/include/keystore/keystore.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2009 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. - */ - -#ifndef __KEYSTORE_H__ -#define __KEYSTORE_H__ - -#include <stdint.h> - -// note state values overlap with ResponseCode for the purposes of the state() API -enum State { - STATE_NO_ERROR = 1, - STATE_LOCKED = 2, - STATE_UNINITIALIZED = 3, -}; - -// must be in sync with KeyStore.java, -enum class ResponseCode : int32_t { - NO_ERROR = STATE_NO_ERROR, // 1 - LOCKED = STATE_LOCKED, // 2 - UNINITIALIZED = STATE_UNINITIALIZED, // 3 - SYSTEM_ERROR = 4, - PROTOCOL_ERROR = 5, - PERMISSION_DENIED = 6, - KEY_NOT_FOUND = 7, - VALUE_CORRUPTED = 8, - UNDEFINED_ACTION = 9, - WRONG_PASSWORD_0 = 10, - WRONG_PASSWORD_1 = 11, - WRONG_PASSWORD_2 = 12, - WRONG_PASSWORD_3 = 13, // MAX_RETRY = 4 - SIGNATURE_INVALID = 14, - OP_AUTH_NEEDED = 15, // Auth is needed for this operation before it can be used. - KEY_ALREADY_EXISTS = 16, - KEY_PERMANENTLY_INVALIDATED = 17, - - /** - * Following three response codes are for logging purposes only. - * The operations are logged at the end of the life cycle of an operation handle, - * along with the reason for the end of the operation handle. For the operations - * that fail in update and finish, the reason for failure is available with - * the above response codes. - * For the operations that are aborted in three different ways, the reason - * for aborting is not available. The following enum values define the - * three ways an operation can get aborted. - */ - ABORT_CALLED = 18, - PRUNED = 19, - BINDER_DIED = 20, -}; - -/* - * All the flags for import and insert calls. - */ -enum KeyStoreFlag : uint8_t { - KEYSTORE_FLAG_NONE = 0, - KEYSTORE_FLAG_ENCRYPTED = 1 << 0, - KEYSTORE_FLAG_FALLBACK = 1 << 1, - // KEYSTORE_FLAG_SUPER_ENCRYPTED is for blobs that are already encrypted by keymaster but have - // an additional layer of password-based encryption applied. The same encryption scheme is used - // as KEYSTORE_FLAG_ENCRYPTED, but it's safe to remove super-encryption when the password is - // cleared, rather than deleting blobs, and the error returned when attempting to use a - // super-encrypted blob while keystore is locked is different. - KEYSTORE_FLAG_SUPER_ENCRYPTED = 1 << 2, - // KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION is for blobs that are part of device encryption - // flow so it receives special treatment from keystore. For example this blob will not be super - // encrypted, and it will be stored separately under an unique UID instead. This flag should - // only be available to system uid. - KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION = 1 << 3, - KEYSTORE_FLAG_STRONGBOX = 1 << 4, -}; - -#endif diff --git a/keystore/include/keystore/keystore_attestation_id.h b/keystore/include/keystore/keystore_attestation_id.h index 238f4b12..a0d43ad8 100644 --- a/keystore/include/keystore/keystore_attestation_id.h +++ b/keystore/include/keystore/keystore_attestation_id.h @@ -25,11 +25,11 @@ namespace security { constexpr size_t KEY_ATTESTATION_APPLICATION_ID_MAX_SIZE = 1024; -namespace keymaster { +namespace keystore { class KeyAttestationApplicationId; -} // namespace keymaster +} // namespace keystore template <typename T> class StatusOr { public: @@ -77,7 +77,7 @@ StatusOr<std::vector<uint8_t>> gather_attestation_application_id(uid_t uid); */ StatusOr<std::vector<uint8_t>> build_attestation_application_id( - const ::android::security::keymaster::KeyAttestationApplicationId& key_attestation_id); + const ::android::security::keystore::KeyAttestationApplicationId& key_attestation_id); } // namespace security } // namespace android diff --git a/keystore/include/keystore/keystore_client.h b/keystore/include/keystore/keystore_client.h deleted file mode 100644 index cb27268e..00000000 --- a/keystore/include/keystore/keystore_client.h +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2015 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. - -#ifndef KEYSTORE_KEYSTORE_CLIENT_H_ -#define KEYSTORE_KEYSTORE_CLIENT_H_ - -#include <memory> -#include <optional> -#include <set> -#include <string> -#include <vector> - -#include <android-base/macros.h> - -#include "keymaster_types.h" -#include "keystore.h" -#include "keystore_return_types.h" - -namespace keystore { - -// An abstract class providing a convenient interface to keystore services. This -// interface is designed to: -// - hide details of the IPC mechanism (e.g. binder) -// - use std data types -// - encourage the use of keystore::AuthorizationSet[Builder] -// - be convenient for native services integrating with keystore -// - be safely mocked for unit testing (e.g. pure virtual methods) -// -// Example usage: -// KeystoreClient* keystore = new KeyStoreClientImpl(); -// keystore->AddRandomNumberGeneratorEntropy("unpredictable"); -// -// Notes on error codes: -// Keystore binder methods return a variety of values including ResponseCode -// values defined in keystore.h, keymaster_error_t values defined in -// keymaster_defs.h, or just 0 or -1 (both of which conflict with -// keymaster_error_t). The methods in this class converge on a single success -// indicator for convenience. KM_ERROR_OK was chosen over ::NO_ERROR for two -// reasons: -// 1) KM_ERROR_OK is 0, which is a common convention for success, is the gmock -// default, and allows error checks like 'if (error) {...'. -// 2) Although both pollute the global namespace, KM_ERROR_OK has a prefix per -// C convention and hopefully clients can use this interface without -// needing to include 'keystore.h' directly. -class KeystoreClient { - public: - KeystoreClient() = default; - virtual ~KeystoreClient() = default; - - // Encrypts and authenticates |data| with minimal configuration for local - // decryption. If a key identified by |key_name| does not already exist it - // will be generated. On success returns true and populates |encrypted_data|. - // Note: implementations may generate more than one key but they will always - // have |key_name| as a prefix. - virtual bool encryptWithAuthentication(const std::string& key_name, const std::string& data, - int32_t flags, std::string* encrypted_data) = 0; - - // Decrypts and authenticates |encrypted_data| as output by - // EncryptWithAuthentication using the key(s) identified by |key_name|. On - // success returns true and populates |data|. - virtual bool decryptWithAuthentication(const std::string& key_name, - const std::string& encrypted_data, - std::string* data) = 0; - - // Performs a Begin/Update/Finish sequence for an operation. The |purpose|, - // |key_name|, |input_parameters|, and |output_parameters| are as in - // BeginOperation. The |input_data| is as in UpdateOperation. The - // |signature_to_verify| and |output_data| are as in FinishOperation. On - // success returns true. - virtual bool oneShotOperation(KeyPurpose purpose, const std::string& key_name, - const keystore::AuthorizationSet& input_parameters, - const std::string& input_data, - const std::string& signature_to_verify, - keystore::AuthorizationSet* output_parameters, - std::string* output_data) = 0; - - // Adds |entropy| to the random number generator. Returns KM_ERROR_OK on - // success and a Keystore ResponseCode or keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode addRandomNumberGeneratorEntropy(const std::string& entropy, - int32_t flags) = 0; - - // Generates a key according to the given |key_parameters| and stores it with - // the given |key_name|. The [hardware|software]_enforced_characteristics of - // the key are provided on success. Returns KM_ERROR_OK on success. Returns - // KM_ERROR_OK on success and a Keystore ResponseCode or keymaster_error_t on - // failure. - virtual KeyStoreNativeReturnCode - generateKey(const std::string& key_name, const keystore::AuthorizationSet& key_parameters, - int32_t flags, keystore::AuthorizationSet* hardware_enforced_characteristics, - keystore::AuthorizationSet* software_enforced_characteristics) = 0; - - // Provides the [hardware|software]_enforced_characteristics of a key - // identified by |key_name|. Returns KM_ERROR_OK on success and a Keystore - // ResponseCode or keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode - getKeyCharacteristics(const std::string& key_name, - keystore::AuthorizationSet* hardware_enforced_characteristics, - keystore::AuthorizationSet* software_enforced_characteristics) = 0; - - // Imports |key_data| in the given |key_format|, applies the given - // |key_parameters|, and stores it with the given |key_name|. The - // [hardware|software]_enforced_characteristics of the key are provided on - // success. Returns KM_ERROR_OK on success and a Keystore ResponseCode or - // keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode - importKey(const std::string& key_name, const keystore::AuthorizationSet& key_parameters, - KeyFormat key_format, const std::string& key_data, - keystore::AuthorizationSet* hardware_enforced_characteristics, - keystore::AuthorizationSet* software_enforced_characteristics) = 0; - - // Exports the public key identified by |key_name| to |export_data| using - // |export_format|. Returns KM_ERROR_OK on success and a Keystore ResponseCode - // or keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode exportKey(KeyFormat export_format, const std::string& key_name, - std::string* export_data) = 0; - - // Deletes the key identified by |key_name|. Returns KM_ERROR_OK on success - // and a Keystore ResponseCode or keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode deleteKey(const std::string& key_name) = 0; - - // Deletes all keys owned by the caller. Returns KM_ERROR_OK on success and a - // Keystore ResponseCode or keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode deleteAllKeys() = 0; - - // Begins a cryptographic operation (e.g. encrypt, sign) identified by - // |purpose| using the key identified by |key_name| and the given - // |input_parameters|. On success, any |output_parameters| and an operation - // |handle| are populated. Returns KM_ERROR_OK on success and a Keystore - // ResponseCode or keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode - beginOperation(KeyPurpose purpose, const std::string& key_name, - const keystore::AuthorizationSet& input_parameters, - keystore::AuthorizationSet* output_parameters, uint64_t* handle) = 0; - - // Continues the operation associated with |handle| using the given - // |input_parameters| and |input_data|. On success, the - // |num_input_bytes_consumed| and any |output_parameters| are populated. Any - // |output_data| will be appended. Returns KM_ERROR_OK on success and a - // Keystore ResponseCode or keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode - updateOperation(uint64_t handle, const keystore::AuthorizationSet& input_parameters, - const std::string& input_data, size_t* num_input_bytes_consumed, - keystore::AuthorizationSet* output_parameters, std::string* output_data) = 0; - - // Finishes the operation associated with |handle| using the given - // |input_parameters| and, if necessary, a |signature_to_verify|. On success, - // any |output_parameters| are populated and |output_data| is appended. - // Returns KM_ERROR_OK on success and a Keystore ResponseCode or - // keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode - finishOperation(uint64_t handle, const keystore::AuthorizationSet& input_parameters, - const std::string& input_data, const std::string& signature_to_verify, - keystore::AuthorizationSet* output_parameters, std::string* output_data) = 0; - - // Aborts the operation associated with |handle|. Returns KM_ERROR_OK on - // success and a Keystore ResponseCode or keymaster_error_t on failure. - virtual KeyStoreNativeReturnCode abortOperation(uint64_t handle) = 0; - - // Returns true if a key identified by |key_name| exists in the caller's - // key store. Returns false if an error occurs. - virtual bool doesKeyExist(const std::string& key_name) = 0; - - // Provides a |key_name_list| containing all existing key names in the - // caller's key store starting with |prefix|. Returns true on success. - virtual bool listKeys(const std::string& prefix, std::vector<std::string>* key_name_list) = 0; - - // Provides a |key_name_list| containing all existing key names in the - // caller's key store starting with |prefix|. Returns true on success. - virtual bool listKeysOfUid(const std::string& prefix, int uid, - std::vector<std::string>* key_name_list) = 0; - - virtual std::optional<std::vector<uint8_t>> getKey(const std::string& alias, int uid) = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(KeystoreClient); -}; - -} // namespace keystore - -#endif // KEYSTORE_KEYSTORE_CLIENT_H_ diff --git a/keystore/include/keystore/keystore_client_impl.h b/keystore/include/keystore/keystore_client_impl.h deleted file mode 100644 index ed8ac446..00000000 --- a/keystore/include/keystore/keystore_client_impl.h +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2015 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. - -#ifndef KEYSTORE_KEYSTORE_CLIENT_IMPL_H_ -#define KEYSTORE_KEYSTORE_CLIENT_IMPL_H_ - -#include "keystore_client.h" - -#include <future> -#include <map> -#include <optional> -#include <string> -#include <vector> - -#include <android/security/keystore/IKeystoreService.h> -#include <binder/IBinder.h> -#include <binder/IServiceManager.h> -#include <utils/StrongPointer.h> - -namespace keystore { - -class KeystoreClientImpl : public KeystoreClient { - public: - KeystoreClientImpl(); - ~KeystoreClientImpl() override = default; - - // KeystoreClient methods. - bool encryptWithAuthentication(const std::string& key_name, const std::string& data, - int32_t flags, std::string* encrypted_data) override; - bool decryptWithAuthentication(const std::string& key_name, const std::string& encrypted_data, - std::string* data) override; - bool oneShotOperation(KeyPurpose purpose, const std::string& key_name, - const keystore::AuthorizationSet& input_parameters, - const std::string& input_data, const std::string& signature_to_verify, - keystore::AuthorizationSet* output_parameters, - std::string* output_data) override; - KeyStoreNativeReturnCode addRandomNumberGeneratorEntropy(const std::string& entropy, - int32_t flags) override; - KeyStoreNativeReturnCode - generateKey(const std::string& key_name, const keystore::AuthorizationSet& key_parameters, - int32_t flags, keystore::AuthorizationSet* hardware_enforced_characteristics, - keystore::AuthorizationSet* software_enforced_characteristics) override; - KeyStoreNativeReturnCode - getKeyCharacteristics(const std::string& key_name, - keystore::AuthorizationSet* hardware_enforced_characteristics, - keystore::AuthorizationSet* software_enforced_characteristics) override; - KeyStoreNativeReturnCode - importKey(const std::string& key_name, const keystore::AuthorizationSet& key_parameters, - KeyFormat key_format, const std::string& key_data, - keystore::AuthorizationSet* hardware_enforced_characteristics, - keystore::AuthorizationSet* software_enforced_characteristics) override; - KeyStoreNativeReturnCode exportKey(KeyFormat export_format, const std::string& key_name, - std::string* export_data) override; - KeyStoreNativeReturnCode deleteKey(const std::string& key_name) override; - KeyStoreNativeReturnCode deleteAllKeys() override; - KeyStoreNativeReturnCode beginOperation(KeyPurpose purpose, const std::string& key_name, - const keystore::AuthorizationSet& input_parameters, - keystore::AuthorizationSet* output_parameters, - uint64_t* handle) override; - KeyStoreNativeReturnCode updateOperation(uint64_t handle, - const keystore::AuthorizationSet& input_parameters, - const std::string& input_data, - size_t* num_input_bytes_consumed, - keystore::AuthorizationSet* output_parameters, - std::string* output_data) override; - KeyStoreNativeReturnCode finishOperation(uint64_t handle, - const keystore::AuthorizationSet& input_parameters, - const std::string& input_data, - const std::string& signature_to_verify, - keystore::AuthorizationSet* output_parameters, - std::string* output_data) override; - KeyStoreNativeReturnCode abortOperation(uint64_t handle) override; - bool doesKeyExist(const std::string& key_name) override; - bool listKeys(const std::string& prefix, std::vector<std::string>* key_name_list) override; - bool listKeysOfUid(const std::string& prefix, int uid, - std::vector<std::string>* key_name_list) override; - std::optional<std::vector<uint8_t>> getKey(const std::string& alias, int uid) override; - - private: - // Returns an available virtual operation handle. - uint64_t getNextVirtualHandle(); - - // Maps a keystore error code to a code where all success cases use - // KM_ERROR_OK (not keystore's NO_ERROR). - // int32_t mapKeystoreError(int32_t keystore_error); - - // Creates an encryption key suitable for EncryptWithAuthentication or - // verifies attributes if the key already exists. Returns true on success. - bool createOrVerifyEncryptionKey(const std::string& key_name, int32_t flags); - - // Creates an authentication key suitable for EncryptWithAuthentication or - // verifies attributes if the key already exists. Returns true on success. - bool createOrVerifyAuthenticationKey(const std::string& key_name, int32_t flags); - - // Verifies attributes of an encryption key suitable for - // EncryptWithAuthentication. Returns true on success and populates |verified| - // with the result of the verification. - bool verifyEncryptionKeyAttributes(const std::string& key_name, bool* verified); - - // Verifies attributes of an authentication key suitable for - // EncryptWithAuthentication. Returns true on success and populates |verified| - // with the result of the verification. - bool verifyAuthenticationKeyAttributes(const std::string& key_name, bool* verified); - - android::sp<android::IServiceManager> service_manager_; - android::sp<android::IBinder> keystore_binder_; - android::sp<android::security::keystore::IKeystoreService> keystore_; - uint64_t next_virtual_handle_ = 1; - std::map<uint64_t, android::sp<android::IBinder>> active_operations_; - - DISALLOW_COPY_AND_ASSIGN(KeystoreClientImpl); -}; - -} // namespace keystore - -#endif // KEYSTORE_KEYSTORE_CLIENT_IMPL_H_ diff --git a/keystore/include/keystore/keystore_client_mock.h b/keystore/include/keystore/keystore_client_mock.h deleted file mode 100644 index b16367fb..00000000 --- a/keystore/include/keystore/keystore_client_mock.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2015 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. - -#ifndef KEYSTORE_KEYSTORE_CLIENT_MOCK_H_ -#define KEYSTORE_KEYSTORE_CLIENT_MOCK_H_ - -#include "keystore/keystore_client.h" -#include "gmock/gmock.h" - -using testing::_; - -namespace keystore { - -// A mock implementation of KeystoreClient. By default all methods do nothing -// and return KM_ERROR_OK (or false). -class KeystoreClientMock : public KeystoreClient { - public: - KeystoreClientMock() = default; - ~KeystoreClientMock() = default; - - MOCK_METHOD3(encryptWithAuthentication, - bool(const std::string& key_name, const std::string& data, - std::string* encrypted_data)); - MOCK_METHOD3(decryptWithAuthentication, - bool(const std::string& key_name, const std::string& encrypted_data, - std::string* data)); - MOCK_METHOD7(oneShotOperation, - bool(keymaster_purpose_t purpose, const std::string& key_name, - const keymaster::AuthorizationSet& input_parameters, - const std::string& input_data, const std::string& signature_to_verify, - keymaster::AuthorizationSet* output_parameters, std::string* output_data)); - MOCK_METHOD1(addRandomNumberGeneratorEntropy, int32_t(const std::string& entropy)); - MOCK_METHOD4(generateKey, - int32_t(const std::string& key_name, - const keymaster::AuthorizationSet& key_parameters, - keymaster::AuthorizationSet* hardware_enforced_characteristics, - keymaster::AuthorizationSet* software_enforced_characteristics)); - MOCK_METHOD3(getKeyCharacteristics, - int32_t(const std::string& key_name, - keymaster::AuthorizationSet* hardware_enforced_characteristics, - keymaster::AuthorizationSet* software_enforced_characteristics)); - MOCK_METHOD6(importKey, - int32_t(const std::string& key_name, - const keymaster::AuthorizationSet& key_parameters, - keymaster_key_format_t key_format, const std::string& key_data, - keymaster::AuthorizationSet* hardware_enforced_characteristics, - keymaster::AuthorizationSet* software_enforced_characteristics)); - MOCK_METHOD3(exportKey, int32_t(keymaster_key_format_t export_format, - const std::string& key_name, std::string* export_data)); - MOCK_METHOD1(deleteKey, int32_t(const std::string& key_name)); - MOCK_METHOD0(deleteAllKeys, int32_t()); - MOCK_METHOD5(beginOperation, int32_t(keymaster_purpose_t purpose, const std::string& key_name, - const keymaster::AuthorizationSet& input_parameters, - keymaster::AuthorizationSet* output_parameters, - keymaster_operation_handle_t* handle)); - MOCK_METHOD6(updateOperation, - int32_t(keymaster_operation_handle_t handle, - const keymaster::AuthorizationSet& input_parameters, - const std::string& input_data, size_t* num_input_bytes_consumed, - keymaster::AuthorizationSet* output_parameters, std::string* output_data)); - MOCK_METHOD5(finishOperation, - int32_t(keymaster_operation_handle_t handle, - const keymaster::AuthorizationSet& input_parameters, - const std::string& signature_to_verify, - keymaster::AuthorizationSet* output_parameters, std::string* output_data)); - MOCK_METHOD1(abortOperation, int32_t(keymaster_operation_handle_t handle)); - MOCK_METHOD1(doesKeyExist, bool(const std::string& key_name)); - MOCK_METHOD2(listKeys, - bool(const std::string& prefix, std::vector<std::string>* key_name_list)); - - private: - DISALLOW_COPY_AND_ASSIGN(KeystoreClientMock); -}; - -} // namespace keystore - -#endif // KEYSTORE_KEYSTORE_CLIENT_MOCK_H_ diff --git a/keystore/include/keystore/keystore_concurrency.h b/keystore/include/keystore/keystore_concurrency.h deleted file mode 100644 index 039ca313..00000000 --- a/keystore/include/keystore/keystore_concurrency.h +++ /dev/null @@ -1,114 +0,0 @@ -/* -** -** Copyright 2018, 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. -*/ - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_CONCURRENCY_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_CONCURRENCY_H_ - -#include <type_traits> - -namespace keystore { - -template <typename LockedType> class UnlockProxyLockHelper { - private: - std::function<void(LockedType*)> unlock_; - LockedType* value_; - - public: - using lockedType = LockedType; - UnlockProxyLockHelper() : value_(nullptr) {} - UnlockProxyLockHelper(LockedType* value, std::function<void(LockedType*)>&& unlock) - : unlock_(std::move(unlock)), value_(value) {} - ~UnlockProxyLockHelper() { - if (unlock_) unlock_(value_); - } - UnlockProxyLockHelper(UnlockProxyLockHelper&& rhs) - : unlock_(std::move(rhs.unlock_)), value_(rhs.value_) { - rhs.value_ = nullptr; - rhs.unlock_ = {}; - } - UnlockProxyLockHelper& operator=(UnlockProxyLockHelper&& rhs) { - if (this != &rhs) { - UnlockProxyLockHelper dummy(std::move(*this)); - unlock_ = std::move(rhs.unlock_); - value_ = std::move(rhs.value_); - rhs.value_ = nullptr; - rhs.unlock_ = {}; - } - return *this; - } - UnlockProxyLockHelper(const UnlockProxyLockHelper& rhs) = delete; - UnlockProxyLockHelper& operator=(const UnlockProxyLockHelper& rhs) = delete; - - template <typename T = LockedType> - std::enable_if_t<!std::is_const<LockedType>::value, T*> value() { - return value_; - } - const LockedType* value() const { return value_; } -}; - -template <typename LockedType, typename MutexType, template <typename> class GuardType> -class MutexProxyLockHelper { - private: - GuardType<MutexType> lock_; - LockedType* value_; - - public: - using lockedType = LockedType; - MutexProxyLockHelper() : value_(nullptr) {} - MutexProxyLockHelper(LockedType* value, GuardType<MutexType>&& lock) - : lock_(std::move(lock)), value_(value) {} - - template <typename T = LockedType> - std::enable_if_t<!std::is_const<LockedType>::value, T*> value() { - return value_; - } - const LockedType* value() const { return value_; } -}; - -template <typename Implementation> class ProxyLock { - private: - Implementation impl_; - - public: - ProxyLock() : impl_() {} - // NOLINTNEXTLINE(google-explicit-constructor) - template <typename... Args> ProxyLock(Args&&... args) : impl_{std::forward<Args>(args)...} {} - explicit ProxyLock(Implementation&& impl) : impl_(std::move(impl)) {} - explicit operator bool() const { return impl_.value() != nullptr; } - - template <typename T = typename Implementation::lockedType> - std::enable_if_t<!std::is_const<typename Implementation::lockedType>::value, T*> operator->() { - return impl_.value(); - } - - template <typename T = typename Implementation::lockedType> - std::enable_if_t<!std::is_const<typename Implementation::lockedType>::value, T&> operator*() { - return *impl_.value(); - } - - const std::remove_const_t<typename Implementation::lockedType>* operator->() const { - return impl_.value(); - } - - const std::remove_const_t<typename Implementation::lockedType>& operator*() const { - return *impl_.value(); - } -}; - -} // namespace keystore - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_CONCURRENCY_H_ diff --git a/keystore/include/keystore/keystore_hidl_support.h b/keystore/include/keystore/keystore_hidl_support.h deleted file mode 100644 index d1d7f165..00000000 --- a/keystore/include/keystore/keystore_hidl_support.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - ** - ** Copyright 2016, 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. - */ - -#ifndef KEYSTORE_KEYSTORE_HIDL_SUPPORT_H_ -#define KEYSTORE_KEYSTORE_HIDL_SUPPORT_H_ - -#include <ostream> -#include <sstream> -#include <string> - -#include <android-base/logging.h> -#include <android/hardware/keymaster/3.0/IKeymasterDevice.h> -#include <hardware/hw_auth_token.h> -#include <hidl/Status.h> -#include <keymasterV4_0/keymaster_utils.h> - -#include <keystore/keymaster_types.h> - -namespace keystore { - -using android::hardware::keymaster::V4_0::support::blob2hidlVec; -using android::hardware::keymaster::V4_0::support::hidlVec2AuthToken; -using android::hardware::keymaster::V4_0::support::authToken2HidlVec; - -inline static std::ostream& formatArgs(std::ostream& out) { - return out; -} - -template <typename First, typename... Args> -inline static std::ostream& formatArgs(std::ostream& out, First&& first, Args&&... args) { - out << first; - return formatArgs(out, args...); -} - -template <typename... Args> inline static std::string argsToString(Args&&... args) { - std::stringstream s; - formatArgs(s, args...); - return s.str(); -} - -template <typename KMDevice, typename... Msgs> -inline static ErrorCode ksHandleHidlError(KMDevice dev, const Return<ErrorCode>& error, - Msgs&&... msgs) { - if (!error.isOk()) { - LOG(ERROR) << "HIDL call failed with " << error.description().c_str() << " @ " - << argsToString(msgs...); - return ErrorCode::UNKNOWN_ERROR; - } - auto ec = ErrorCode(error); - dev->logIfKeymasterVendorError(ec); - return ec; -} -template <typename KMDevice, typename... Msgs> -inline static ErrorCode ksHandleHidlError(KMDevice, const Return<void>& error, Msgs&&... msgs) { - if (!error.isOk()) { - ALOGE("HIDL call failed with %s @ %s", error.description().c_str(), - argsToString(msgs...).c_str()); - return ErrorCode::UNKNOWN_ERROR; - } - return ErrorCode::OK; -} - -#define KS_HANDLE_HIDL_ERROR(dev, rc) \ - ::keystore::ksHandleHidlError(dev, rc, __FILE__, ":", __LINE__, ":", __PRETTY_FUNCTION__) - -template <typename T, typename OutIter> -inline static OutIter copy_bytes_to_iterator(const T& value, OutIter dest) { - const uint8_t* value_ptr = reinterpret_cast<const uint8_t*>(&value); - return std::copy(value_ptr, value_ptr + sizeof(value), dest); -} - -constexpr size_t kHmacSize = 32; - -inline static hidl_vec<uint8_t> authToken2HidlVec(const Km3HardwareAuthToken& token) { - static_assert(std::is_same<decltype(token.hmac), - ::android::hardware::hidl_array<uint8_t, kHmacSize>>::value, - "This function assumes token HMAC is 32 bytes, but it might not be."); - static_assert(1 /* version size */ + sizeof(token.challenge) + sizeof(token.userId) + - sizeof(token.authenticatorId) + sizeof(token.authenticatorType) + - sizeof(token.timestamp) + kHmacSize == - sizeof(hw_auth_token_t), - "HardwareAuthToken content size does not match hw_auth_token_t size"); - - hidl_vec<uint8_t> result; - result.resize(sizeof(hw_auth_token_t)); - auto pos = result.begin(); - *pos++ = 0; // Version byte - pos = copy_bytes_to_iterator(token.challenge, pos); - pos = copy_bytes_to_iterator(token.userId, pos); - pos = copy_bytes_to_iterator(token.authenticatorId, pos); - pos = copy_bytes_to_iterator(token.authenticatorType, pos); - pos = copy_bytes_to_iterator(token.timestamp, pos); - pos = std::copy(token.hmac.data(), token.hmac.data() + token.hmac.size(), pos); - - return result; -} - -template <typename T, typename InIter> -inline static InIter copy_bytes_from_iterator(T* value, InIter src) { - uint8_t* value_ptr = reinterpret_cast<uint8_t*>(value); - std::copy(src, src + sizeof(T), value_ptr); - return src + sizeof(T); -} - -inline static Km3HardwareAuthToken hidlVec2Km3AuthToken(const hidl_vec<uint8_t>& buffer) { - Km3HardwareAuthToken token; - static_assert(std::is_same<decltype(token.hmac), - ::android::hardware::hidl_array<uint8_t, kHmacSize>>::value, - "This function assumes token HMAC is 32 bytes, but it might not be."); - static_assert(1 /* version size */ + sizeof(token.challenge) + sizeof(token.userId) + - sizeof(token.authenticatorId) + sizeof(token.authenticatorType) + - sizeof(token.timestamp) + kHmacSize == - sizeof(hw_auth_token_t), - "HardwareAuthToken content size does not match hw_auth_token_t size"); - - if (buffer.size() != sizeof(hw_auth_token_t)) return {}; - - auto pos = buffer.begin(); - ++pos; // skip first byte - pos = copy_bytes_from_iterator(&token.challenge, pos); - pos = copy_bytes_from_iterator(&token.userId, pos); - pos = copy_bytes_from_iterator(&token.authenticatorId, pos); - pos = copy_bytes_from_iterator(&token.authenticatorType, pos); - pos = copy_bytes_from_iterator(&token.timestamp, pos); - pos = std::copy(pos, pos + token.hmac.size(), &token.hmac[0]); - - return token; -} - -inline std::string hidlVec2String(const hidl_vec<uint8_t>& value) { - return std::string(reinterpret_cast<const std::string::value_type*>(&value[0]), value.size()); -} - -} // namespace keystore - -#endif // KEYSTORE_KEYSTORE_HIDL_SUPPORT_H_ diff --git a/keystore/include/keystore/keystore_promises.h b/keystore/include/keystore/keystore_promises.h deleted file mode 100644 index 3d45016e..00000000 --- a/keystore/include/keystore/keystore_promises.h +++ /dev/null @@ -1,72 +0,0 @@ -/* -** -** Copyright 2018, 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. -*/ - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_PROMISES_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_PROMISES_H_ - -#include <android/security/keystore/BnKeystoreCertificateChainCallback.h> -#include <android/security/keystore/BnKeystoreExportKeyCallback.h> -#include <android/security/keystore/BnKeystoreKeyCharacteristicsCallback.h> -#include <android/security/keystore/BnKeystoreOperationResultCallback.h> -#include <android/security/keystore/BnKeystoreResponseCallback.h> -#include <future> - -namespace keystore { - -template <typename BnInterface, typename Result> -class CallbackPromise : public BnInterface, public std::promise<Result> { - public: - ::android::binder::Status onFinished(const Result& result) override { - this->set_value(result); - return ::android::binder::Status::ok(); - } -}; - -template <typename BnInterface, typename... Results> -class CallbackPromise<BnInterface, std::tuple<Results...>> - : public BnInterface, public std::promise<std::tuple<Results...>> { - public: - ::android::binder::Status onFinished(const Results&... results) override { - this->set_value({results...}); - return ::android::binder::Status::ok(); - } -}; - -using OperationResultPromise = - CallbackPromise<::android::security::keystore::BnKeystoreOperationResultCallback, - ::android::security::keymaster::OperationResult>; - -using KeystoreResponsePromise = - CallbackPromise<::android::security::keystore::BnKeystoreResponseCallback, - ::android::security::keystore::KeystoreResponse>; - -using KeyCharacteristicsPromise = - CallbackPromise<::android::security::keystore::BnKeystoreKeyCharacteristicsCallback, - std::tuple<::android::security::keystore::KeystoreResponse, - ::android::security::keymaster::KeyCharacteristics>>; -using KeystoreExportPromise = - CallbackPromise<::android::security::keystore::BnKeystoreExportKeyCallback, - ::android::security::keymaster::ExportResult>; - -using KeyCertChainPromise = - CallbackPromise<::android::security::keystore::BnKeystoreCertificateChainCallback, - std::tuple<::android::security::keystore::KeystoreResponse, - ::android::security::keymaster::KeymasterCertificateChain>>; - -} // namespace keystore - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_PROMISES_H_ diff --git a/keystore/include/keystore/keystore_return_types.h b/keystore/include/keystore/keystore_return_types.h deleted file mode 100644 index 2762f8d1..00000000 --- a/keystore/include/keystore/keystore_return_types.h +++ /dev/null @@ -1,193 +0,0 @@ -/* -** -** Copyright 2016, 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. -*/ - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_RETURN_TYPES_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_RETURN_TYPES_H_ - -#include "keymaster_types.h" -#include "keystore.h" - -namespace keystore { - -class KeyStoreServiceReturnCode; -class KeyStoreNativeReturnCode; - -/** - * The keystore service return code is a bit tricky. It can return error codes from two name spaces: - * ErrorCode, which has negative error codes and use 0 for ERROR_OK; - * ResponseCode, which has positive error codes and uses 1 for NO_ERROR. - * This class can be initialized by both. And when accessed through the operator int32_t () it - * always returns ResponseCode::NO_ERROR (1) on success, even if it was initialized with - * ErrorCode::OK (0), because this is what (java) clients expect. - * - * !!! Do not confuse this with KeyStoreNativeReturnCode which always converts to 0 on success. !!! - */ -class KeyStoreServiceReturnCode { - public: - KeyStoreServiceReturnCode() : errorCode_(0) {} - // NOLINTNEXTLINE(google-explicit-constructor) - KeyStoreServiceReturnCode(const ErrorCode& errorCode) : errorCode_(int32_t(errorCode)) {} - // NOLINTNEXTLINE(google-explicit-constructor) - KeyStoreServiceReturnCode(const ResponseCode& errorCode) : errorCode_(int32_t(errorCode)) {} - KeyStoreServiceReturnCode(const KeyStoreServiceReturnCode& errorCode) - : errorCode_(errorCode.errorCode_) {} - // NOLINTNEXTLINE(google-explicit-constructor) - KeyStoreServiceReturnCode(const KeyStoreNativeReturnCode& errorCode); - explicit inline KeyStoreServiceReturnCode(const int32_t& errorCode) : errorCode_(errorCode) {} - inline KeyStoreServiceReturnCode& operator=(const ErrorCode& errorCode) { - errorCode_ = int32_t(errorCode); - return *this; - } - inline KeyStoreServiceReturnCode& operator=(const ResponseCode& errorCode) { - errorCode_ = int32_t(errorCode); - return *this; - } - inline KeyStoreServiceReturnCode& operator=(const KeyStoreServiceReturnCode& errorCode) { - errorCode_ = errorCode.errorCode_; - return *this; - } - inline bool isOk() const { - return errorCode_ == static_cast<int32_t>(ResponseCode::NO_ERROR) || - errorCode_ == static_cast<int32_t>(ErrorCode::OK); - } - - inline int32_t getErrorCode() const { - if (!errorCode_) return static_cast<int32_t>(ResponseCode::NO_ERROR /* 1 */); - return errorCode_; - } - inline bool operator==(const ResponseCode& rhs) const { - return (rhs == ResponseCode::NO_ERROR && - errorCode_ == static_cast<int32_t>(ErrorCode::OK)) || - errorCode_ == int32_t(rhs); - } - inline bool operator==(const ErrorCode& rhs) const { - return (rhs == ErrorCode::OK && - errorCode_ == static_cast<int32_t>(ResponseCode::NO_ERROR)) || - errorCode_ == int32_t(rhs); - } - inline bool operator!=(const ResponseCode& rhs) const { return !(*this == rhs); } - inline bool operator!=(const ErrorCode& rhs) const { return !(*this == rhs); } - - private: - int32_t errorCode_; -}; - -inline bool operator==(const ResponseCode& lhs, const KeyStoreServiceReturnCode& rhs) { - return rhs == lhs; -} -inline bool operator==(const ErrorCode& lhs, const KeyStoreServiceReturnCode& rhs) { - return rhs == lhs; -} -inline bool operator!=(const ResponseCode& lhs, const KeyStoreServiceReturnCode& rhs) { - return rhs != lhs; -} -inline bool operator!=(const ErrorCode& lhs, const KeyStoreServiceReturnCode& rhs) { - return rhs != lhs; -} - -inline std::ostream& operator<<(std::ostream& out, const KeyStoreServiceReturnCode& error) { - return out << error.getErrorCode(); -} - -/** - * The keystore native return code is a bit tricky. It can return error codes from two name spaces: - * ErrorCode, which has negative error codes and use 0 for ERROR_OK; - * ResponseCode, which has positive error codes and uses 1 for NO_ERROR. - * This class can be initialized by both. And when accessed through the operator int32_t () it - * always returns ErrorCode::OK (0) on success, even if it was initialized with - * ResponseCode::NO_ERROR (1), because this is what (native) clients expect. - * - * !!! Do not this confuse with KeyStoreServiceReturnCode which always converts to 1 on success. !!! - */ -class KeyStoreNativeReturnCode { - public: - KeyStoreNativeReturnCode() : errorCode_(0) {} - // NOLINTNEXTLINE(google-explicit-constructor) - KeyStoreNativeReturnCode(const ErrorCode& errorCode) : errorCode_(int32_t(errorCode)) {} - // NOLINTNEXTLINE(google-explicit-constructor) - KeyStoreNativeReturnCode(const ResponseCode& errorCode) : errorCode_(int32_t(errorCode)) {} - KeyStoreNativeReturnCode(const KeyStoreNativeReturnCode& errorCode) - : errorCode_(errorCode.errorCode_) {} - explicit inline KeyStoreNativeReturnCode(const int32_t& errorCode) : errorCode_(errorCode) {} - // NOLINTNEXTLINE(google-explicit-constructor) - KeyStoreNativeReturnCode(const KeyStoreServiceReturnCode& errorcode); - inline KeyStoreNativeReturnCode& operator=(const ErrorCode& errorCode) { - errorCode_ = int32_t(errorCode); - return *this; - } - inline KeyStoreNativeReturnCode& operator=(const ResponseCode& errorCode) { - errorCode_ = int32_t(errorCode); - return *this; - } - inline KeyStoreNativeReturnCode& operator=(const KeyStoreNativeReturnCode& errorCode) { - errorCode_ = errorCode.errorCode_; - return *this; - } - inline bool isOk() const { - return errorCode_ == static_cast<int32_t>(ResponseCode::NO_ERROR) || - errorCode_ == static_cast<int32_t>(ErrorCode::OK); - } - inline int32_t getErrorCode() const { - if (errorCode_ == static_cast<int32_t>(ResponseCode::NO_ERROR) /* 1 */) { - return static_cast<int32_t>(ErrorCode::OK) /* 0 */; - } - return errorCode_; - } - inline bool operator==(const ResponseCode& rhs) const { - return (rhs == ResponseCode::NO_ERROR && - errorCode_ == static_cast<int32_t>(ErrorCode::OK)) || - errorCode_ == int32_t(rhs); - } - inline bool operator==(const ErrorCode& rhs) const { - return (rhs == ErrorCode::OK && - errorCode_ == static_cast<int32_t>(ResponseCode::NO_ERROR)) || - errorCode_ == int32_t(rhs); - } - inline bool operator!=(const ResponseCode& rhs) const { return !(*this == rhs); } - inline bool operator!=(const ErrorCode& rhs) const { return !(*this == rhs); } - - private: - int32_t errorCode_; -}; - -inline bool operator==(const ResponseCode& lhs, const KeyStoreNativeReturnCode& rhs) { - return rhs == lhs; -} -inline bool operator==(const ErrorCode& lhs, const KeyStoreNativeReturnCode& rhs) { - return rhs == lhs; -} -inline bool operator!=(const ResponseCode& lhs, const KeyStoreNativeReturnCode& rhs) { - return rhs != lhs; -} -inline bool operator!=(const ErrorCode& lhs, const KeyStoreNativeReturnCode& rhs) { - return rhs != lhs; -} - -inline KeyStoreNativeReturnCode::KeyStoreNativeReturnCode( - const KeyStoreServiceReturnCode& errorCode) - : errorCode_(errorCode.getErrorCode()) {} -inline KeyStoreServiceReturnCode::KeyStoreServiceReturnCode( - const KeyStoreNativeReturnCode& errorCode) - : errorCode_(errorCode.getErrorCode()) {} - -inline std::ostream& operator<<(std::ostream& out, const KeyStoreNativeReturnCode& error) { - return out << error.getErrorCode(); -} - -} // namespace keystore - -#endif // KEYSTORE_INCLUDE_KEYSTORE_KEYSTORE_RETURN_TYPES_H_ diff --git a/keystore/include/keystore/utils.h b/keystore/include/keystore/utils.h deleted file mode 100644 index 2143d3af..00000000 --- a/keystore/include/keystore/utils.h +++ /dev/null @@ -1,101 +0,0 @@ -// TODO: Insert description here. (generated by jdanis) - -#ifndef KEYSTORE_INCLUDE_KEYSTORE_UTILS_H_ -#define KEYSTORE_INCLUDE_KEYSTORE_UTILS_H_ - -#include <iterator> -#include <memory> -#include <optional> -#include <vector> - -namespace android { -namespace security { - -/* - * This iterator abstracts from a collection of the form - * std::shared_ptr<COLLECTION_TYPE<std::optional<T>>> - * such that it is defined both for nulled outer pointer and - * nulled entries. If shared_ptr(nullptr) is passed in, the iterator behaves - * like the end iterator yielding an empty collection. Nulled - * entries are skipped so that the iterator is always dereferencable unless - * it is equal to end. - * The default constructor always yields an iterator equal to end. - * The same iterator invalidation rules apply as they do for the iterators - * of the corresponding collection. - */ -template <typename T, template <typename...> class Coll = std::vector> -class SharedNullableIterator { - public: - typedef Coll<std::optional<typename std::remove_const<T>::type>> CollectionType; - typedef std::shared_ptr<CollectionType> CollectionPtr; - - SharedNullableIterator() {} - explicit SharedNullableIterator(const std::shared_ptr<CollectionType>& coll) : coll_(coll) { - init(); - } - explicit SharedNullableIterator(std::shared_ptr<CollectionType>&& coll) : coll_(coll) { - init(); - } - - SharedNullableIterator(const SharedNullableIterator& other) - : coll_(other.coll_), cur_(other.cur_) {} - SharedNullableIterator(SharedNullableIterator&& other) noexcept - : coll_(std::move(other.coll_)), cur_(std::move(other.cur_)) {} - - SharedNullableIterator& operator++() { - inc(); - return *this; - } - SharedNullableIterator operator++(int) { - SharedNullableIterator retval(*this); - ++(*this); - return retval; - } - T& operator*() const { return **cur_; } - - T* operator->() const { return &**cur_; } - - bool operator==(const SharedNullableIterator& other) const { - return cur_ == other.cur_ || (is_end() && other.is_end()); - } - bool operator!=(const SharedNullableIterator& other) const { return !(*this == other); } - - SharedNullableIterator& operator=(const SharedNullableIterator&) = default; - SharedNullableIterator& operator=(SharedNullableIterator&&) noexcept = default; - - private: - inline bool is_end() const { return !coll_ || cur_ == coll_->end(); } - inline void inc() { - if (!is_end()) { - do { - ++cur_; - // move forward to the next non null member or stay at end - } while (cur_ != coll_->end() && !(*cur_)); - } - } - void init() { - if (coll_) { - // move forward to the first non null member - for (cur_ = coll_->begin(); cur_ != coll_->end() && !(*cur_); ++cur_) { - } - } - } - - CollectionPtr coll_; - typename CollectionType::iterator cur_; -}; - -} // namespace security -} // namespace android - -namespace std { -template <typename T, template <typename...> class COLL> -struct iterator_traits<android::security::SharedNullableIterator<T, COLL>> { - typedef T& reference; - typedef T value_type; - typedef T* pointer; - typedef forward_iterator_tag iterator_category; -}; -} - -#endif // KEYSTORE_INCLUDE_KEYSTORE_UTILS_H_ diff --git a/keystore/keystore_attestation_id.cpp b/keystore/keystore_attestation_id.cpp index ccd38085..1534be16 100644 --- a/keystore/keystore_attestation_id.cpp +++ b/keystore/keystore_attestation_id.cpp @@ -29,11 +29,11 @@ #include <binder/Parcelable.h> #include <binder/PersistableBundle.h> -#include <android/security/keymaster/BpKeyAttestationApplicationIdProvider.h> -#include <android/security/keymaster/IKeyAttestationApplicationIdProvider.h> -#include <keystore/KeyAttestationApplicationId.h> -#include <keystore/KeyAttestationPackageInfo.h> -#include <keystore/Signature.h> +#include <android/security/keystore/BpKeyAttestationApplicationIdProvider.h> +#include <android/security/keystore/IKeyAttestationApplicationIdProvider.h> +#include <android/security/keystore/KeyAttestationApplicationId.h> +#include <android/security/keystore/KeyAttestationPackageInfo.h> +#include <android/security/keystore/Signature.h> #include <private/android_filesystem_config.h> /* for AID_SYSTEM */ @@ -50,13 +50,13 @@ namespace { constexpr const char* kAttestationSystemPackageName = "AndroidSystem"; constexpr const char* kUnknownPackageName = "UnknownPackage"; -std::vector<uint8_t> signature2SHA256(const content::pm::Signature& sig) { +std::vector<uint8_t> signature2SHA256(const security::keystore::Signature& sig) { std::vector<uint8_t> digest_buffer(SHA256_DIGEST_LENGTH); - SHA256(sig.data().data(), sig.data().size(), digest_buffer.data()); + SHA256(sig.data.data(), sig.data.size(), digest_buffer.data()); return digest_buffer; } -using ::android::security::keymaster::BpKeyAttestationApplicationIdProvider; +using ::android::security::keystore::BpKeyAttestationApplicationIdProvider; class KeyAttestationApplicationIdProvider : public BpKeyAttestationApplicationIdProvider { public: @@ -74,8 +74,8 @@ KeyAttestationApplicationIdProvider& KeyAttestationApplicationIdProvider::get() } KeyAttestationApplicationIdProvider::KeyAttestationApplicationIdProvider() - : BpKeyAttestationApplicationIdProvider( - android::defaultServiceManager()->getService(String16("sec_key_att_app_id_provider"))) {} + : BpKeyAttestationApplicationIdProvider(android::defaultServiceManager()->waitForService( + String16("sec_key_att_app_id_provider"))) {} DECLARE_STACK_OF(ASN1_OCTET_STRING); @@ -141,8 +141,8 @@ namespace android { namespace security { namespace { -using ::android::security::keymaster::KeyAttestationApplicationId; -using ::android::security::keymaster::KeyAttestationPackageInfo; +using ::android::security::keystore::KeyAttestationApplicationId; +using ::android::security::keystore::KeyAttestationPackageInfo; status_t build_attestation_package_info(const KeyAttestationPackageInfo& pinfo, std::unique_ptr<KM_ATTESTATION_PACKAGE_INFO>* attestation_package_info_ptr) { @@ -153,12 +153,12 @@ status_t build_attestation_package_info(const KeyAttestationPackageInfo& pinfo, attestation_package_info.reset(KM_ATTESTATION_PACKAGE_INFO_new()); if (!attestation_package_info.get()) return NO_MEMORY; - if (!pinfo.package_name()) { + if (!pinfo.packageName) { ALOGE("Key attestation package info lacks package name"); return BAD_VALUE; } - std::string pkg_name(String8(*pinfo.package_name()).string()); + std::string pkg_name(String8(pinfo.packageName).c_str()); if (!ASN1_OCTET_STRING_set(attestation_package_info->package_name, reinterpret_cast<const unsigned char*>(pkg_name.data()), pkg_name.size())) { @@ -169,7 +169,7 @@ status_t build_attestation_package_info(const KeyAttestationPackageInfo& pinfo, if (bn_version == nullptr) { return NO_MEMORY; } - if (BN_set_u64(bn_version, static_cast<uint64_t>(pinfo.version_code())) != 1) { + if (BN_set_u64(bn_version, static_cast<uint64_t>(pinfo.versionCode)) != 1) { BN_free(bn_version); return UNKNOWN_ERROR; } @@ -201,15 +201,16 @@ build_attestation_application_id(const KeyAttestationApplicationId& key_attestat auto attestation_pinfo_stack = reinterpret_cast<_STACK*>(attestation_id->package_infos); - if (key_attestation_id.pinfos_begin() == key_attestation_id.pinfos_end()) return BAD_VALUE; + if (key_attestation_id.packageInfos.begin() == key_attestation_id.packageInfos.end()) + return BAD_VALUE; - for (auto pinfo = key_attestation_id.pinfos_begin(); pinfo != key_attestation_id.pinfos_end(); - ++pinfo) { - if (!pinfo->package_name()) { + for (auto pinfo = key_attestation_id.packageInfos.begin(); + pinfo != key_attestation_id.packageInfos.end(); ++pinfo) { + if (!pinfo->packageName) { ALOGE("Key attestation package info lacks package name"); return BAD_VALUE; } - std::string package_name(String8(*pinfo->package_name()).string()); + std::string package_name(String8(pinfo->packageName).c_str()); std::unique_ptr<KM_ATTESTATION_PACKAGE_INFO> attestation_package_info; auto rc = build_attestation_package_info(*pinfo, &attestation_package_info); if (rc != NO_ERROR) { @@ -231,10 +232,10 @@ build_attestation_application_id(const KeyAttestationApplicationId& key_attestat * signature field actually holds the signing certificate, rather than a signature, we can * simply use the set of signature digests of the first package info. */ - const auto& pinfo = *key_attestation_id.pinfos_begin(); + const auto& pinfo = *key_attestation_id.packageInfos.begin(); std::vector<std::vector<uint8_t>> signature_digests; - for (auto sig = pinfo.sigs_begin(); sig != pinfo.sigs_end(); ++sig) { + for (auto sig = pinfo.signatures.begin(); sig != pinfo.signatures.end(); ++sig) { signature_digests.push_back(signature2SHA256(*sig)); } @@ -271,10 +272,10 @@ StatusOr<std::vector<uint8_t>> gather_attestation_application_id(uid_t uid) { if (uid == AID_SYSTEM) { /* Use a fixed ID for system callers */ - auto pinfo = std::make_optional<KeyAttestationPackageInfo>( - String16(kAttestationSystemPackageName), 1 /* version code */, - std::make_shared<KeyAttestationPackageInfo::SignaturesVector>()); - key_attestation_id = KeyAttestationApplicationId(std::move(pinfo)); + auto pinfo = KeyAttestationPackageInfo(); + pinfo.packageName = String16(kAttestationSystemPackageName); + pinfo.versionCode = 1; + key_attestation_id.packageInfos.push_back(std::move(pinfo)); } else { /* Get the attestation application ID from package manager */ auto& pm = KeyAttestationApplicationIdProvider::get(); @@ -283,11 +284,12 @@ StatusOr<std::vector<uint8_t>> gather_attestation_application_id(uid_t uid) { // caller is unknown. if (!status.isOk()) { ALOGW("package manager request for key attestation ID failed with: %s %d", - status.exceptionMessage().string(), status.exceptionCode()); - auto pinfo = std::make_optional<KeyAttestationPackageInfo>( - String16(kUnknownPackageName), 1 /* version code */, - std::make_shared<KeyAttestationPackageInfo::SignaturesVector>()); - key_attestation_id = KeyAttestationApplicationId(std::move(pinfo)); + status.exceptionMessage().c_str(), status.exceptionCode()); + + auto pinfo = KeyAttestationPackageInfo(); + pinfo.packageName = String16(kUnknownPackageName); + pinfo.versionCode = 1; + key_attestation_id.packageInfos.push_back(std::move(pinfo)); } } diff --git a/keystore/keystore_get.cpp b/keystore/keystore_get.cpp deleted file mode 100644 index a6f87557..00000000 --- a/keystore/keystore_get.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2012 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 <android/security/keystore/IKeystoreService.h> -#include <binder/IServiceManager.h> - -#include <keystore/keystore_get.h> -#include <vector> - -using namespace android; -using namespace keystore; - -ssize_t keystore_get(const char* key, size_t keyLength, uint8_t** value) { - sp<IServiceManager> sm = defaultServiceManager(); - sp<IBinder> binder = sm->getService(String16("android.security.keystore")); - sp<android::security::keystore::IKeystoreService> service = - interface_cast<android::security::keystore::IKeystoreService>(binder); - - if (service == nullptr) { - return -1; - } - - ::std::vector<uint8_t> result; - auto ret = service->get(String16(key, keyLength), -1, &result); - if (!ret.isOk()) return -1; - - if (value) { - *value = reinterpret_cast<uint8_t*>(malloc(result.size())); - if (!*value) return -1; - memcpy(*value, &result[0], result.size()); - } - return result.size(); -} diff --git a/keystore/keystore_keymaster_enforcement.h b/keystore/keystore_keymaster_enforcement.h deleted file mode 100644 index b0dae48e..00000000 --- a/keystore/keystore_keymaster_enforcement.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2015 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. - */ - -#ifndef KEYSTORE_KEYSTORE_KEYMASTER_ENFORCEMENT_H_ -#define KEYSTORE_KEYSTORE_KEYMASTER_ENFORCEMENT_H_ - -#include <time.h> - -#include "keymaster_enforcement.h" - -namespace keystore { -/** - * This is a specialization of the KeymasterEnforcement class to be used by Keystore to enforce - * keymaster requirements on all key operation. - */ -class KeystoreKeymasterEnforcement : public KeymasterEnforcement { - public: - KeystoreKeymasterEnforcement() : KeymasterEnforcement(64, 64) {} - - uint32_t get_current_time() const override { - struct timespec tp; - int err = clock_gettime(CLOCK_MONOTONIC, &tp); - if (err || tp.tv_sec < 0) - return 0; - return static_cast<uint32_t>(tp.tv_sec); - } - - bool activation_date_valid(uint64_t activation_date) const override { - time_t now = time(nullptr); - if (now == static_cast<time_t>(-1)) { - // Failed to obtain current time -- fail safe: activation_date hasn't yet occurred. - return false; - } else if (now < 0) { - // Current time is prior to start of the epoch -- activation_date hasn't yet occurred. - return false; - } - - // time(NULL) returns seconds since epoch and "loses" milliseconds information. We thus add - // 999 ms to now_date to avoid a situation where an activation_date of up to 999ms in the - // past may still be considered to still be in the future. This can be removed once - // time(NULL) is replaced by a millisecond-precise source of time. - uint64_t now_date = static_cast<uint64_t>(now) * 1000 + 999; - return now_date >= activation_date; - } - - bool expiration_date_passed(uint64_t expiration_date) const override { - time_t now = time(nullptr); - if (now == static_cast<time_t>(-1)) { - // Failed to obtain current time -- fail safe: expiration_date has passed. - return true; - } else if (now < 0) { - // Current time is prior to start of the epoch: expiration_date hasn't yet occurred. - return false; - } - - // time(NULL) returns seconds since epoch and "loses" milliseconds information. As a result, - // expiration_date of up to 999 ms in the past may still be considered in the future. This - // is OK. - uint64_t now_date = static_cast<uint64_t>(now) * 1000; - return now_date > expiration_date; - } - - bool auth_token_timed_out(const HardwareAuthToken&, uint32_t) const { - // Assume the token has not timed out, because AuthTokenTable would not have returned it if - // the timeout were past. Secure hardware will also check timeouts if it supports them. - return false; - } - - bool ValidateTokenSignature(const HardwareAuthToken&) const override { - // Non-secure world cannot validate token signatures because it doesn't have access to the - // signing key. Assume the token is good. - return true; - } - - bool is_device_locked(int32_t userId) const override { - std::lock_guard<std::mutex> lock(is_device_locked_for_user_map_lock_); - // If we haven't had a set call for this user yet, assume the device is locked. - if (mIsDeviceLockedForUser.count(userId) == 0) return true; - return mIsDeviceLockedForUser.find(userId)->second; - } - - void set_device_locked(bool isLocked, int32_t userId) { - std::lock_guard<std::mutex> lock(is_device_locked_for_user_map_lock_); - mIsDeviceLockedForUser[userId] = isLocked; - } - - private: - mutable std::mutex is_device_locked_for_user_map_lock_; - std::map<int32_t, bool> mIsDeviceLockedForUser; -}; - -} // namespace keystore - -#endif // KEYSTORE_KEYSTORE_KEYMASTER_ENFORCEMENT_H_ diff --git a/keystore/keystore_utils.cpp b/keystore/keystore_utils.cpp deleted file mode 100644 index f0f60982..00000000 --- a/keystore/keystore_utils.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#define LOG_TAG "keystore" - -#include "keystore_utils.h" - -#include <errno.h> -#include <string.h> -#include <unistd.h> - -#include <log/log.h> -#include <private/android_filesystem_config.h> -#include <private/android_logger.h> - -#include <log/log_event_list.h> - -#include <keystore/keymaster_types.h> -#include <keystore/keystore_client.h> - -#include <android-base/logging.h> -#include <android-base/unique_fd.h> - -#include "blob.h" - -size_t readFully(int fd, uint8_t* data, size_t size) { - size_t remaining = size; - while (remaining > 0) { - ssize_t n = TEMP_FAILURE_RETRY(read(fd, data, remaining)); - if (n <= 0) { - return size - remaining; - } - data += n; - remaining -= n; - } - return size; -} - -size_t writeFully(int fd, uint8_t* data, size_t size) { - size_t remaining = size; - while (remaining > 0) { - ssize_t n = TEMP_FAILURE_RETRY(write(fd, data, remaining)); - if (n < 0) { - ALOGW("write failed: %s", strerror(errno)); - return size - remaining; - } - data += n; - remaining -= n; - } - if (TEMP_FAILURE_RETRY(fsync(fd)) == -1) { - ALOGW("fsync failed: %s", strerror(errno)); - return -1; - } - return size; -} - -std::string getContainingDirectory(const std::string& filename) { - std::string containing_dir; - size_t last_pos; - size_t pos = std::string::npos; - - __builtin_add_overflow(filename.size(), -1, &last_pos); - - // strip all trailing '/' - while ((pos = filename.find_last_of('/', last_pos)) == last_pos && pos != 0) { - --last_pos; - } - - if (pos == 0) { - containing_dir = "/"; - } else if (pos == std::string::npos) { - containing_dir = "."; - } else { - containing_dir = filename.substr(0, pos); - } - - return containing_dir; -} - -void fsyncDirectory(const std::string& path) { - android::base::unique_fd dir_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_DIRECTORY | O_RDONLY))); - - if (dir_fd < 0) { - LOG(WARNING) << "Could not open dir: " << path << " error: " << strerror(errno); - return; - } - - if (TEMP_FAILURE_RETRY(fsync(dir_fd)) == -1) { - LOG(WARNING) << "Failed to fsync the directory " << path << " error: " << strerror(errno); - } - - return; -} - -void add_legacy_key_authorizations(int keyType, keystore::AuthorizationSet* params) { - using namespace keystore; - params->push_back(TAG_PURPOSE, KeyPurpose::SIGN); - params->push_back(TAG_PURPOSE, KeyPurpose::VERIFY); - params->push_back(TAG_PURPOSE, KeyPurpose::ENCRYPT); - params->push_back(TAG_PURPOSE, KeyPurpose::DECRYPT); - params->push_back(TAG_PADDING, PaddingMode::NONE); - if (keyType == EVP_PKEY_RSA) { - params->push_back(TAG_PADDING, PaddingMode::RSA_PKCS1_1_5_SIGN); - params->push_back(TAG_PADDING, PaddingMode::RSA_PKCS1_1_5_ENCRYPT); - params->push_back(TAG_PADDING, PaddingMode::RSA_PSS); - params->push_back(TAG_PADDING, PaddingMode::RSA_OAEP); - } - params->push_back(TAG_DIGEST, Digest::NONE); - params->push_back(TAG_DIGEST, Digest::MD5); - params->push_back(TAG_DIGEST, Digest::SHA1); - params->push_back(TAG_DIGEST, Digest::SHA_2_224); - params->push_back(TAG_DIGEST, Digest::SHA_2_256); - params->push_back(TAG_DIGEST, Digest::SHA_2_384); - params->push_back(TAG_DIGEST, Digest::SHA_2_512); - params->push_back(TAG_NO_AUTH_REQUIRED); - params->push_back(TAG_ORIGINATION_EXPIRE_DATETIME, LLONG_MAX); - params->push_back(TAG_USAGE_EXPIRE_DATETIME, LLONG_MAX); - params->push_back(TAG_ACTIVE_DATETIME, 0); -} - -uid_t get_app_id(uid_t uid) { - return uid % AID_USER; -} - -uid_t get_user_id(uid_t uid) { - return uid / AID_USER; -} - -void log_key_integrity_violation(const char* name, uid_t uid) { - if (!__android_log_security()) return; - android_log_event_list(SEC_TAG_KEY_INTEGRITY_VIOLATION) - << name << int32_t(uid) << LOG_ID_SECURITY; -} - -namespace keystore { - -hidl_vec<uint8_t> blob2hidlVec(const Blob& blob) { - hidl_vec<uint8_t> result(blob.getValue(), blob.getValue() + blob.getLength()); - return result; -} - -SecurityLevel flagsToSecurityLevel(int32_t flags) { - switch (flags & (KEYSTORE_FLAG_FALLBACK | KEYSTORE_FLAG_STRONGBOX)) { - case KEYSTORE_FLAG_FALLBACK: - // treating Strongbox flag as "don't care" if Fallback is set - case (KEYSTORE_FLAG_FALLBACK | KEYSTORE_FLAG_STRONGBOX): - return SecurityLevel::SOFTWARE; - case KEYSTORE_FLAG_STRONGBOX: - return SecurityLevel::STRONGBOX; - default: - return SecurityLevel::TRUSTED_ENVIRONMENT; - } -} - -uint32_t securityLevelToFlags(SecurityLevel secLevel) { - switch (secLevel) { - case SecurityLevel::SOFTWARE: - return KEYSTORE_FLAG_FALLBACK; - case SecurityLevel::STRONGBOX: - return KEYSTORE_FLAG_STRONGBOX; - default: - return 0; - } -} - -} // namespace keystore diff --git a/keystore/keystore_utils.h b/keystore/keystore_utils.h deleted file mode 100644 index ce64d423..00000000 --- a/keystore/keystore_utils.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#ifndef KEYSTORE_KEYSTORE_UTILS_H_ -#define KEYSTORE_KEYSTORE_UTILS_H_ - -#include <cstdint> -#include <string> -#include <vector> - -#include <openssl/evp.h> -#include <openssl/pem.h> - -#include <memory> - -#include <keystore/keymaster_types.h> - -size_t readFully(int fd, uint8_t* data, size_t size); -size_t writeFully(int fd, uint8_t* data, size_t size); -std::string getContainingDirectory(const std::string& filename); -void fsyncDirectory(const std::string& path); - -void add_legacy_key_authorizations(int keyType, keystore::AuthorizationSet* params); - -/** - * Returns the app ID (in the Android multi-user sense) for the current - * UNIX UID. - */ -uid_t get_app_id(uid_t uid); - -/** - * Returns the user ID (in the Android multi-user sense) for the current - * UNIX UID. - */ -uid_t get_user_id(uid_t uid); - -class Blob; - -// Tags for audit logging. Be careful and don't log sensitive data. -// Should be in sync with frameworks/base/core/java/android/app/admin/SecurityLogTags.logtags -constexpr int SEC_TAG_KEY_DESTROYED = 210026; -constexpr int SEC_TAG_KEY_INTEGRITY_VIOLATION = 210032; -constexpr int SEC_TAG_AUTH_KEY_GENERATED = 210024; -constexpr int SEC_TAG_KEY_IMPORTED = 210025; - -void log_key_integrity_violation(const char* name, uid_t uid); - -namespace keystore { - -hidl_vec<uint8_t> blob2hidlVec(const Blob& blob); - -SecurityLevel flagsToSecurityLevel(int32_t flags); -uint32_t securityLevelToFlags(SecurityLevel secLevel); - -} // namespace keystore - -#endif // KEYSTORE_KEYSTORE_UTILS_H_ diff --git a/keystore/test-keystore b/keystore/test-keystore deleted file mode 100755 index 3be51b3e..00000000 --- a/keystore/test-keystore +++ /dev/null @@ -1,273 +0,0 @@ -#!/bin/bash -# -# Copyright 2011, 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. - -set -e - -prefix=$0 -log_file=$prefix.log -baseline_file=$prefix.baseline - -function cleanup_output() { - rm -f $log_file - rm -f $baseline_file -} - -function log() { - echo "$@" - append $log_file \# "$@" - append $baseline_file \# "$@" -} - -function expect() { - append $baseline_file "$@" -} - -function append() { - declare -r file=$1 - shift - echo "$@" >> $file -} - -function run() { - # strip out carriage returns from adb - # strip out date/time from ls -l - "$@" | tr --delete '\r' | sed -E 's/[0-9]{4}-[0-9]{2}-[0-9]{2} +[0-9]{1,2}:[0-9]{2} //' >> $log_file -} - -function keystore() { - declare -r user=$1 - shift - run adb shell su $user keystore_cli "$@" -} - -function list_keystore_directory() { - run adb shell ls -al /data/misc/keystore -} - -function compare() { - log "comparing $baseline_file and $log_file" - diff $baseline_file $log_file || (log $tag FAILED && exit 1) -} - -function test_basic() { - - # - # reset - # - log "reset keystore as system user" - keystore system r - expect "1 No error" - list_keystore_directory - - # - # basic tests as system/root - # - log "root does not have permission to run test" - keystore root t - expect "6 Permission denied" - - log "but system user does" - keystore system t - expect "3 Uninitialized" - list_keystore_directory - - log "password is now bar" - keystore system p bar - expect "1 No error" - list_keystore_directory - expect "-rw------- keystore keystore 84 .masterkey" - - log "no error implies initialized and unlocked" - keystore system t - expect "1 No error" - - log "saw with no argument" - keystore system s - expect "5 Protocol error" - - log "saw nothing" - keystore system s "" - expect "1 No error" - - log "add key baz" - keystore system i baz quux - expect "1 No error" - - log "1000 is uid of system" - list_keystore_directory - expect "-rw------- keystore keystore 84 .masterkey" - expect "-rw------- keystore keystore 52 1000_baz" - - log "saw baz" - keystore system s "" - expect "1 No error" - expect "baz" - - log "get baz" - keystore system g baz - expect "1 No error" - expect "quux" - - log "root can read system user keys (as can wifi or vpn users)" - keystore root g baz - expect "1 No error" - expect "quux" - - # - # app user tests - # - - # app_0 has uid 10000, as seen below - log "other uses cannot see the system keys" - keystore app_0 g baz - expect "7 Key not found" - - log "app user cannot use reset, password, lock, unlock" - keystore app_0 r - expect "6 Permission denied" - keystore app_0 p - expect "6 Permission denied" - keystore app_0 l - expect "6 Permission denied" - keystore app_0 u - expect "6 Permission denied" - - log "install app_0 key" - keystore app_0 i 0x deadbeef - expect 1 No error - list_keystore_directory - expect "-rw------- keystore keystore 84 .masterkey" - expect "-rw------- keystore keystore 52 10000_0x" - expect "-rw------- keystore keystore 52 1000_baz" - - log "get with no argument" - keystore app_0 g - expect "5 Protocol error" - - keystore app_0 g 0x - expect "1 No error" - expect "deadbeef" - - keystore app_0 i fred barney - expect "1 No error" - - keystore app_0 s "" - expect "1 No error" - expect "0x" - expect "fred" - - log "note that saw returns the suffix of prefix matches" - keystore app_0 s fr # fred - expect "1 No error" - expect "ed" # fred - - # - # lock tests - # - log "lock the store as system" - keystore system l - expect "1 No error" - keystore system t - expect "2 Locked" - - log "saw works while locked" - keystore app_0 s "" - expect "1 No error" - expect "0x" - expect "fred" - - log "...but cannot read keys..." - keystore app_0 g 0x - expect "2 Locked" - - log "...but they can be deleted." - keystore app_0 e 0x - expect "1 No error" - keystore app_0 d 0x - expect "1 No error" - keystore app_0 e 0x - expect "7 Key not found" - - # - # password - # - log "wrong password" - keystore system u foo - expect "13 Wrong password (4 tries left)" - log "right password" - keystore system u bar - expect "1 No error" - - log "make the password foo" - keystore system p foo - expect "1 No error" - - # - # final reset - # - log "reset wipes everything for all users" - keystore system r - expect "1 No error" - list_keystore_directory - - keystore system t - expect "3 Uninitialized" - -} - -function test_4599735() { - # http://b/4599735 - log "start regression test for b/4599735" - keystore system r - expect "1 No error" - - keystore system p foo - expect "1 No error" - - keystore system i baz quux - expect "1 No error" - - keystore root g baz - expect "1 No error" - expect "quux" - - keystore system l - expect "1 No error" - - keystore system p foo - expect "1 No error" - - log "after unlock, regression led to result of '8 Value corrupted'" - keystore root g baz - expect "1 No error" - expect "quux" - - keystore system r - expect "1 No error" - log "end regression test for b/4599735" -} - -function main() { - cleanup_output - log $tag START - test_basic - test_4599735 - compare - log $tag PASSED - cleanup_output -} - -main diff --git a/keystore/tests/Android.bp b/keystore/tests/Android.bp index f51cc2f5..c3a9e660 100644 --- a/keystore/tests/Android.bp +++ b/keystore/tests/Android.bp @@ -1,6 +1,7 @@ // Unit test for AuthTokenTable package { + default_team: "trendy_team_android_hardware_backed_security", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "system_security_license" @@ -35,14 +36,15 @@ cc_test { "libutils", ], shared_libs: [ + "android.security.aaid_aidl-cpp", "libbinder", "libkeymaster_messages", "libkeystore-attestation-application-id", "libvndksupport", ], - sanitize: { - cfi: false, - } + sanitize: { + cfi: false, + }, } cc_test { @@ -67,7 +69,7 @@ cc_test { shared_libs: [ "libbinder_ndk", ], - sanitize: { - cfi: false, - } + sanitize: { + cfi: false, + }, } diff --git a/keystore/tests/Makefile b/keystore/tests/Makefile deleted file mode 100644 index b50b94a4..00000000 --- a/keystore/tests/Makefile +++ /dev/null @@ -1,125 +0,0 @@ -########## -# This makefile builds local unit tests that run locally on the development machine. Note -# that it may be necessary to install some libraries on the dev maching to make the tests -# build. -# -# The same unit tests are also built by Android.mk to run on the target device. The tests -# should always build and pass in both places. The on-device test is what really matters, -# of course, but debugging is often somewhat easier on the dev platform. -########## - -BASE=../../../.. -SUBS=system/core \ - system/keymaster\ - hardware/libhardware \ - external/gtest -GTEST=$(BASE)/external/gtest -KEYMASTER=$(BASE)/system/keymaster - -INCLUDES=$(foreach dir,$(SUBS),-I $(BASE)/$(dir)/include) \ - -I $(GTEST) -Iinclude - -# Add USE_CLANG=1 to the make command line to build with clang, which has better error -# reporting and diagnoses some conditions that GCC doesn't. -ifdef USE_CLANG -CC=/usr/bin/clang -CXX=/usr/bin/clang -CLANG_TEST_DEFINE=-DKEYMASTER_CLANG_TEST_BUILD -COMPILER_SPECIFIC_ARGS=-std=c++11 $(CLANG_TEST_DEFINE) -else -COMPILER_SPECIFIC_ARGS=-std=c++0x -fprofile-arcs -endif - -CPPFLAGS=$(INCLUDES) -g -O0 -MD -DHOST_BUILD -CXXFLAGS=-Wall -Werror -Wno-unused -Winit-self -Wpointer-arith -Wunused-parameter \ - -Werror=sign-compare -Wmissing-declarations -ftest-coverage -fno-permissive \ - -Wno-deprecated-declarations -fno-exceptions -DKEYMASTER_NAME_TAGS \ - $(COMPILER_SPECIFIC_ARGS) - -# Uncomment to enable debug logging. -# CXXFLAGS += -DDEBUG - -LDLIBS=-lpthread -lstdc++ -lgcov - -# This list of sources is used for dependency generation and cleanup. Add each new source -# file here (not headers). -CPPSRCS=\ - ../auth_token_table.cpp \ - auth_token_table_test.cpp \ - gtest_main.cpp \ - $(KEYMASTER)/authorization_set.cpp \ - $(KEYMASTER)/keymaster_tags.cpp \ - $(KEYMASTER)/logger.cpp \ - $(KEYMASTER)/serializable.cpp - -CCSRCS=$(GTEST)/src/gtest-all.cc - -# This list of binaries determes what gets built and run. Add each new test binary here. -BINARIES=\ - auth_token_table_test - -.PHONY: coverage memcheck massif clean run - -%.run: % - ./$< - touch $@ - -run: $(BINARIES:=.run) - -GTEST_OBJS = $(GTEST)/src/gtest-all.o gtest_main.o - -auth_token_table_test: auth_token_table_test.o \ - ../auth_token_table.o \ - $(GTEST_OBJS) \ - $(KEYMASTER)/authorization_set.o \ - $(KEYMASTER)/keymaster_tags.o \ - $(KEYMASTER)/logger.o \ - $(KEYMASTER)/serializable.o - -coverage: coverage.info - genhtml coverage.info --output-directory coverage - -coverage.info: run - lcov --capture --directory=. --directory=.. -b . --output-file coverage.info - -%.coverage : % - $(MAKE) clean && $(MAKE) $< - ./$< - lcov --capture --directory=. --output-file coverage.info - genhtml coverage.info --output-directory coverage -#UNINIT_OPTS=--track-origins=yes -UNINIT_OPTS=--undef-value-errors=no - -MEMCHECK_OPTS=--leak-check=full \ - --show-reachable=yes \ - --vgdb=full \ - $(UNINIT_OPTS) \ - --error-exitcode=1 - -MASSIF_OPTS=--tool=massif \ - --stacks=yes - -%.memcheck : % - valgrind $(MEMCHECK_OPTS) ./$< && \ - touch $@ - -%.massif : % - valgrind $(MASSIF_OPTS) --massif-out-file=$@ ./$< - -memcheck: $(BINARIES:=.memcheck) - -massif: $(BINARIES:=.massif) - -OBJS=$(CPPSRCS:.cpp=.o) -DEPS=$(CPPSRCS:.cpp=.d) -GCOV=$(CPPSRCS:.cpp=.gcov) $(CPPSRCS:.cpp=.gcda) $(CPPSRCS:.cpp=.gcno) - -clean: - rm -f $(OBJS) $(DEPS) $(BINARIES) $(GCOV) \ - $(BINARIES:=.run) $(BINARIES:=.memcheck) $(BINARIES:=.massif) \ - *gcov *gcno *gcda coverage.info - rm -rf coverage - --include $(CPPSRCS:.cpp=.d) --include $(CCSRCS:.cc=.d) - diff --git a/keystore/tests/aaid_truncation_test.cpp b/keystore/tests/aaid_truncation_test.cpp index fa4d769a..3a94ec1c 100644 --- a/keystore/tests/aaid_truncation_test.cpp +++ b/keystore/tests/aaid_truncation_test.cpp @@ -22,14 +22,14 @@ #include <keymaster/logger.h> #include <keystore/keystore_attestation_id.h> -#include <keystore/KeyAttestationApplicationId.h> -#include <keystore/KeyAttestationPackageInfo.h> -#include <keystore/Signature.h> +#include <android/security/keystore/KeyAttestationApplicationId.h> +#include <android/security/keystore/KeyAttestationPackageInfo.h> +#include <android/security/keystore/Signature.h> using ::android::String16; using ::android::security::KEY_ATTESTATION_APPLICATION_ID_MAX_SIZE; -using ::android::security::keymaster::KeyAttestationApplicationId; -using ::android::security::keymaster::KeyAttestationPackageInfo; +using ::android::security::keystore::KeyAttestationApplicationId; +using ::android::security::keystore::KeyAttestationPackageInfo; using std::vector; namespace keystore { @@ -72,24 +72,27 @@ constexpr const size_t kTooManySignatures = 35; } // namespace -using ::android::content::pm::Signature; using ::android::security::build_attestation_application_id; +using ::android::security::keystore::Signature; -std::optional<KeyAttestationPackageInfo> -make_package_info_with_signatures(const char* package_name, - KeyAttestationPackageInfo::SignaturesVector signatures) { - return std::make_optional<KeyAttestationPackageInfo>( - String16(package_name), 1 /* version code */, - std::make_shared<KeyAttestationPackageInfo::SignaturesVector>(std::move(signatures))); +KeyAttestationPackageInfo make_package_info_with_signatures(const char* package_name, + std::vector<Signature> signatures) { + auto pInfo = KeyAttestationPackageInfo(); + pInfo.packageName = String16(package_name); + pInfo.versionCode = 1; + std::move(signatures.begin(), signatures.end(), std::back_inserter(pInfo.signatures)); + + return pInfo; } -std::optional<KeyAttestationPackageInfo> make_package_info(const char* package_name) { - return make_package_info_with_signatures(package_name, - KeyAttestationPackageInfo::SignaturesVector()); +KeyAttestationPackageInfo make_package_info(const char* package_name) { + return make_package_info_with_signatures(package_name, std::vector<Signature>()); } TEST(AaidTruncationTest, shortPackageInfoTest) { - KeyAttestationApplicationId app_id(make_package_info(kDummyPackageName)); + KeyAttestationApplicationId app_id; + auto pInfo = make_package_info(kDummyPackageName); + app_id.packageInfos.push_back(std::move(pInfo)); auto result = build_attestation_application_id(app_id); ASSERT_TRUE(result.isOk()); @@ -98,7 +101,9 @@ TEST(AaidTruncationTest, shortPackageInfoTest) { } TEST(AaidTruncationTest, tooLongPackageNameTest) { - KeyAttestationApplicationId app_id(make_package_info(kLongPackageName)); + KeyAttestationApplicationId app_id; + auto pInfo = make_package_info(kLongPackageName); + app_id.packageInfos.push_back(std::move(pInfo)); auto result = build_attestation_application_id(app_id); ASSERT_TRUE(result.isOk()); @@ -108,14 +113,17 @@ TEST(AaidTruncationTest, tooLongPackageNameTest) { TEST(AaidTruncationTest, tooManySignaturesTest) { std::vector<uint8_t> dummy_sig_data(kDummySignature, kDummySignature + 32); - KeyAttestationPackageInfo::SignaturesVector signatures; + std::vector<Signature> signatures; // Add 35 signatures which will surely exceed the 1K limit. for (size_t i = 0; i < kTooManySignatures; ++i) { - signatures.push_back(std::make_optional<Signature>(dummy_sig_data)); + auto sign = Signature(); + sign.data = dummy_sig_data; + signatures.push_back(std::move(sign)); } - KeyAttestationApplicationId app_id( - make_package_info_with_signatures(kDummyPackageName, std::move(signatures))); + auto pInfo = make_package_info_with_signatures(kDummyPackageName, std::move(signatures)); + KeyAttestationApplicationId app_id; + app_id.packageInfos.push_back(std::move(pInfo)); auto result = build_attestation_application_id(app_id); ASSERT_TRUE(result.isOk()); @@ -125,19 +133,22 @@ TEST(AaidTruncationTest, tooManySignaturesTest) { TEST(AaidTruncationTest, combinedPackagesAndSignaturesTest) { std::vector<uint8_t> dummy_sig_data(kDummySignature, kDummySignature + 32); - KeyAttestationApplicationId::PackageInfoVector packages; + ::std::vector<KeyAttestationPackageInfo> packages; for (size_t i = 0; i < kTooManyPackages; ++i) { - KeyAttestationPackageInfo::SignaturesVector signatures; + std::vector<Signature> signatures; // Add a few signatures for each package for (int j = 0; j < 3; ++j) { - signatures.push_back(std::make_optional<Signature>(dummy_sig_data)); + auto sign = Signature(); + sign.data = dummy_sig_data; + signatures.push_back(std::move(sign)); } - packages.push_back( - make_package_info_with_signatures(kReasonablePackageName, std::move(signatures))); + packages.push_back(std::move( + make_package_info_with_signatures(kReasonablePackageName, std::move(signatures)))); } + KeyAttestationApplicationId app_id; + std::move(packages.begin(), packages.end(), std::back_inserter(app_id.packageInfos)); - KeyAttestationApplicationId app_id(std::move(packages)); auto result = build_attestation_application_id(app_id); ASSERT_TRUE(result.isOk()); std::vector<uint8_t>& encoded_app_id = result; diff --git a/keystore/tests/auth_token_formatting_test.cpp b/keystore/tests/auth_token_formatting_test.cpp deleted file mode 100644 index 0ecc4cca..00000000 --- a/keystore/tests/auth_token_formatting_test.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2017 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 <gtest/gtest.h> - -#include <endian.h> -#include <hidl/HidlSupport.h> -#include <keymaster/logger.h> -#include <keymasterV4_0/keymaster_utils.h> - -#include <keystore/keymaster_types.h> -#include <keystore/keystore_hidl_support.h> - -#include "../auth_token_table.h" - -using std::vector; - -namespace keystore { - -using android::hardware::hidl_array; -using android::hardware::hidl_vec; - -namespace test { - -namespace { - -class StdoutLogger : public ::keymaster::Logger { - public: - StdoutLogger() { set_instance(this); } - - int log_msg(LogLevel level, const char* fmt, va_list args) const { - int output_len = 0; - switch (level) { - case DEBUG_LVL: - output_len = printf("DEBUG: "); - break; - case INFO_LVL: - output_len = printf("INFO: "); - break; - case WARNING_LVL: - output_len = printf("WARNING: "); - break; - case ERROR_LVL: - output_len = printf("ERROR: "); - break; - case SEVERE_LVL: - output_len = printf("SEVERE: "); - break; - } - - output_len += vprintf(fmt, args); - output_len += printf("\n"); - return output_len; - } -}; - -StdoutLogger logger; - -} // namespace - -constexpr const uint8_t test_token[69] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, - 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, - 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44}; - -constexpr const uint8_t test_hmac_data[] = { - 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, - 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44}; - -static const Km3HardwareAuthToken km3_hidl_test_token_little_endian = { - UINT64_C(0x0807060504030201), UINT64_C(0x100f0e0d0c0b0a09), - UINT64_C(0x1817161514131211), UINT32_C(0x1c1b1a19), - UINT64_C(0x24232221201f1e1d), hidl_array<uint8_t, 32>(test_hmac_data)}; - -static const HardwareAuthToken km4_hidl_test_token = { - UINT64_C(0x0807060504030201), UINT64_C(0x100f0e0d0c0b0a09), - UINT64_C(0x1817161514131211), static_cast<HardwareAuthenticatorType>(UINT32_C(0x191a1b1c)), - UINT64_C(0x1d1e1f2021222324), hidl_vec<uint8_t>(test_hmac_data, test_hmac_data + 32)}; - -TEST(AuthenticationTokenFormattingTest, hidlVec2Km3AuthToken) { - static_assert(sizeof(hw_auth_token_t) == sizeof(test_token), "test_token has wrong size"); - hidl_vec<uint8_t> hidl_test_token; - hidl_test_token.setToExternal(const_cast<unsigned char*>(test_token), sizeof(test_token)); - ASSERT_EQ(km3_hidl_test_token_little_endian, hidlVec2Km3AuthToken(hidl_test_token)); -} - -TEST(AuthenticationTokenFormattingTest, hidlVec2Km4AuthToken) { - static_assert(sizeof(hw_auth_token_t) == sizeof(test_token), "test_token has wrong size"); - hidl_vec<uint8_t> hidl_test_token; - hidl_test_token.setToExternal(const_cast<unsigned char*>(test_token), sizeof(test_token)); - ASSERT_EQ(km4_hidl_test_token, hidlVec2AuthToken(hidl_test_token)); -} - -TEST(AuthenticationTokenFormattingTest, km3AuthToken2HidlVec) { - static_assert(sizeof(hw_auth_token_t) == sizeof(test_token), "test_token has wrong size"); - hidl_vec<uint8_t> hidl_test_token; - hidl_test_token.setToExternal(const_cast<unsigned char*>(test_token), sizeof(test_token)); - ASSERT_EQ(hidl_test_token, authToken2HidlVec(km3_hidl_test_token_little_endian)); -} - -TEST(AuthenticationTokenFormattingTest, km4AuthToken2HidlVec) { - static_assert(sizeof(hw_auth_token_t) == sizeof(test_token), "test_token has wrong size"); - hidl_vec<uint8_t> hidl_test_token; - hidl_test_token.setToExternal(const_cast<unsigned char*>(test_token), sizeof(test_token)); - ASSERT_EQ(hidl_test_token, authToken2HidlVec(km4_hidl_test_token)); -} - -TEST(AuthenticationTokenFormattingTest, backAndForth) { - static_assert(sizeof(hw_auth_token_t) == sizeof(test_token), "test_token has wrong size"); - hidl_vec<uint8_t> hidl_test_token; - hidl_test_token.setToExternal(const_cast<unsigned char*>(test_token), sizeof(test_token)); - ASSERT_EQ(km3_hidl_test_token_little_endian, - hidlVec2Km3AuthToken(authToken2HidlVec(km3_hidl_test_token_little_endian))); - ASSERT_EQ(km4_hidl_test_token, hidlVec2AuthToken(authToken2HidlVec(km4_hidl_test_token))); -} - -TEST(AuthenticationTokenFormattingTest, forthAndBack) { - static_assert(sizeof(hw_auth_token_t) == sizeof(test_token), "test_token has wrong size"); - hidl_vec<uint8_t> hidl_test_token; - hidl_test_token.setToExternal(const_cast<unsigned char*>(test_token), sizeof(test_token)); - ASSERT_EQ(hidl_test_token, authToken2HidlVec(hidlVec2Km3AuthToken(hidl_test_token))); - ASSERT_EQ(hidl_test_token, authToken2HidlVec(hidlVec2Km3AuthToken(hidl_test_token))); -} - -TEST(AuthenticationTokenFormattingTest, roundAndRound) { - static_assert(sizeof(hw_auth_token_t) == sizeof(test_token), "test_token has wrong size"); - hidl_vec<uint8_t> hidl_test_token; - hidl_test_token.setToExternal(const_cast<unsigned char*>(test_token), sizeof(test_token)); - HardwareAuthToken km4_from_hidl = hidlVec2AuthToken(hidl_test_token); - hidl_vec<uint8_t> hidl_from_km4 = authToken2HidlVec(km4_from_hidl); - Km3HardwareAuthToken km3_from_hidl = hidlVec2Km3AuthToken(hidl_from_km4); - hidl_vec<uint8_t> hidl_from_km3 = authToken2HidlVec(km3_from_hidl); - - ASSERT_EQ(hidl_from_km4, hidl_test_token); - ASSERT_EQ(hidl_from_km3, hidl_test_token); - ASSERT_NE(km4_from_hidl.timestamp, km3_from_hidl.timestamp); - ASSERT_NE(static_cast<uint32_t>(km4_from_hidl.authenticatorType), - km3_from_hidl.authenticatorType); -} - -} // namespace test -} // namespace keystore diff --git a/keystore/tests/auth_token_table_test.cpp b/keystore/tests/auth_token_table_test.cpp deleted file mode 100644 index f6ce10ed..00000000 --- a/keystore/tests/auth_token_table_test.cpp +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Copyright (C) 2015 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 <gtest/gtest.h> - -#include <endian.h> -#include <keymaster/logger.h> - -#include "../auth_token_table.h" - -using std::vector; - -namespace keystore { -namespace test { - -class StdoutLogger : public ::keymaster::Logger { - public: - StdoutLogger() { set_instance(this); } - - int log_msg(LogLevel level, const char* fmt, va_list args) const { - int output_len = 0; - switch (level) { - case DEBUG_LVL: - output_len = printf("DEBUG: "); - break; - case INFO_LVL: - output_len = printf("INFO: "); - break; - case WARNING_LVL: - output_len = printf("WARNING: "); - break; - case ERROR_LVL: - output_len = printf("ERROR: "); - break; - case SEVERE_LVL: - output_len = printf("SEVERE: "); - break; - } - - output_len += vprintf(fmt, args); - output_len += printf("\n"); - return output_len; - } -}; - -StdoutLogger logger; - -TEST(AuthTokenTableTest, Create) { - AuthTokenTable table; -} - -static HardwareAuthToken make_token(uint64_t rsid, uint64_t ssid = 0, uint64_t challenge = 0, - uint64_t timestamp = 0) { - HardwareAuthToken token; - token.userId = rsid; - token.authenticatorId = ssid; - token.authenticatorType = HardwareAuthenticatorType::PASSWORD; - token.challenge = challenge; - token.timestamp = timestamp; - return token; -} - -static AuthorizationSet make_set(uint64_t rsid, uint32_t timeout = 10000) { - AuthorizationSetBuilder builder; - builder.Authorization(TAG_USER_AUTH_TYPE, HardwareAuthenticatorType::PASSWORD) - .Authorization(TAG_USER_SECURE_ID, rsid); - // Use timeout == 0 to indicate tags that require auth per operation. - if (timeout != 0) builder.Authorization(TAG_AUTH_TIMEOUT, timeout); - return std::move(builder); -} - -// Tests obviously run so fast that a real-time clock with a one-second granularity rarely changes -// output during a test run. This test clock "ticks" one second every time it's called. -static time_t monotonic_clock() { - static time_t time = 0; - return time++; -} - -TEST(AuthTokenTableTest, SimpleAddAndFindTokens) { - AuthTokenTable table; - - table.AddAuthenticationToken(make_token(1, 2)); - table.AddAuthenticationToken(make_token(3, 4)); - EXPECT_EQ(2U, table.size()); - - AuthTokenTable::Error rc; - HardwareAuthToken found; - - ASSERT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ(1U, found.userId); - EXPECT_EQ(2U, found.authenticatorId); - - ASSERT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(2), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ(1U, found.userId); - EXPECT_EQ(2U, found.authenticatorId); - - ASSERT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(3), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ(3U, found.userId); - EXPECT_EQ(4U, found.authenticatorId); - - ASSERT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(4), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ(3U, found.userId); - EXPECT_EQ(4U, found.authenticatorId); - - ASSERT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(5), KeyPurpose::SIGN, 0), rc)); -} - -TEST(AuthTokenTableTest, FlushTable) { - AuthTokenTable table(3, monotonic_clock); - - table.AddAuthenticationToken(make_token(1)); - table.AddAuthenticationToken(make_token(2)); - table.AddAuthenticationToken(make_token(3)); - - AuthTokenTable::Error rc; - HardwareAuthToken found; - - // All three should be in the table. - EXPECT_EQ(3U, table.size()); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(2), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(3), KeyPurpose::SIGN, 0), rc)); - - table.Clear(); - EXPECT_EQ(0U, table.size()); -} - -TEST(AuthTokenTableTest, TableOverflow) { - AuthTokenTable table(3, monotonic_clock); - - table.AddAuthenticationToken(make_token(1)); - table.AddAuthenticationToken(make_token(2)); - table.AddAuthenticationToken(make_token(3)); - - AuthTokenTable::Error rc; - HardwareAuthToken found; - - // All three should be in the table. - EXPECT_EQ(3U, table.size()); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(2), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(3), KeyPurpose::SIGN, 0), rc)); - - table.AddAuthenticationToken(make_token(4)); - - // Oldest should be gone. - EXPECT_EQ(3U, table.size()); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(1), KeyPurpose::SIGN, 0), rc)); - - // Others should be there, including the new one (4). Search for it first, then the others, so - // 4 becomes the least recently used. - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(4), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(2), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(3), KeyPurpose::SIGN, 0), rc)); - - table.AddAuthenticationToken(make_token(5)); - - // 5 should have replaced 4. - EXPECT_EQ(3U, table.size()); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(4), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(2), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(5), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(3), KeyPurpose::SIGN, 0), rc)); - - table.AddAuthenticationToken(make_token(6)); - table.AddAuthenticationToken(make_token(7)); - - // 2 and 5 should be gone - EXPECT_EQ(3U, table.size()); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(2), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(5), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(6), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(7), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(3), KeyPurpose::SIGN, 0), rc)); - - table.AddAuthenticationToken(make_token(8)); - table.AddAuthenticationToken(make_token(9)); - table.AddAuthenticationToken(make_token(10)); - - // Only the three most recent should be there. - EXPECT_EQ(3U, table.size()); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(1), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(2), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(3), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(4), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(5), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(6), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(7), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(8), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(9), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(10), KeyPurpose::SIGN, 0), rc)); -} - -TEST(AuthTokenTableTest, AuthenticationNotRequired) { - AuthTokenTable table; - AuthTokenTable::Error rc; - HardwareAuthToken found; - - EXPECT_EQ(AuthTokenTable::AUTH_NOT_REQUIRED, - (std::tie(rc, found) = table.FindAuthorization( - AuthorizationSetBuilder().Authorization(TAG_NO_AUTH_REQUIRED), KeyPurpose::SIGN, - 0 /* no challenge */), - rc)); -} - -TEST(AuthTokenTableTest, OperationHandleNotFound) { - AuthTokenTable table; - AuthTokenTable::Error rc; - HardwareAuthToken found; - - table.AddAuthenticationToken(make_token(1, 0, 1, 5)); - EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = - table.FindAuthorization(make_set(1, 0 /* no timeout */), KeyPurpose::SIGN, - 2 /* non-matching challenge */), - rc)); - EXPECT_EQ(AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization( - make_set(1, 0 /* no timeout */), KeyPurpose::SIGN, 1 /* matching challenge */), - rc)); - table.MarkCompleted(1); - EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization( - make_set(1, 0 /* no timeout */), KeyPurpose::SIGN, 1 /* used challenge */), - rc)); -} - -TEST(AuthTokenTableTest, OperationHandleRequired) { - AuthTokenTable table; - AuthTokenTable::Error rc; - HardwareAuthToken found; - - table.AddAuthenticationToken(make_token(1)); - EXPECT_EQ(AuthTokenTable::OP_HANDLE_REQUIRED, - (std::tie(rc, found) = table.FindAuthorization( - make_set(1, 0 /* no timeout */), KeyPurpose::SIGN, 0 /* no op handle */), - rc)); -} - -TEST(AuthTokenTableTest, AuthSidChanged) { - AuthTokenTable table; - AuthTokenTable::Error rc; - HardwareAuthToken found; - - table.AddAuthenticationToken(make_token(1, 3, /* op handle */ 1)); - EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_WRONG_SID, - (std::tie(rc, found) = table.FindAuthorization(make_set(2, 0 /* no timeout */), - KeyPurpose::SIGN, 1 /* op handle */), - rc)); -} - -TEST(AuthTokenTableTest, TokenExpired) { - AuthTokenTable table(5, monotonic_clock); - AuthTokenTable::Error rc; - HardwareAuthToken found; - - auto key_info = make_set(1, 5 /* five second timeout */); - - // monotonic_clock "ticks" one second each time it's called, which is once per request, so the - // sixth request should fail, since key_info says the key is good for five seconds. - // - // Note that this tests the decision of the AuthTokenTable to reject a request it knows is - // expired. An additional check of the secure timestamp (in the token) will be made by - // keymaster when the found token is passed to it. - table.AddAuthenticationToken(make_token(1, 0)); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - key_info, KeyPurpose::SIGN, 0 /* no op handle */), - rc)); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - key_info, KeyPurpose::SIGN, 0 /* no op handle */), - rc)); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - key_info, KeyPurpose::SIGN, 0 /* no op handle */), - rc)); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - key_info, KeyPurpose::SIGN, 0 /* no op handle */), - rc)); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - key_info, KeyPurpose::SIGN, 0 /* no op handle */), - rc)); - EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_EXPIRED, - (std::tie(rc, found) = - table.FindAuthorization(key_info, KeyPurpose::SIGN, 0 /* no op handle */), - rc)); -} - -TEST(AuthTokenTableTest, MarkNonexistentEntryCompleted) { - AuthTokenTable table; - // Marking a nonexistent entry completed is ignored. This test is mainly for code coverage. - table.MarkCompleted(1); -} - -TEST(AuthTokenTableTest, SupersededEntries) { - AuthTokenTable table; - AuthTokenTable::Error rc; - HardwareAuthToken found; - - // Add two identical tokens, without challenges. The second should supersede the first, based - // on timestamp (fourth arg to make_token). - table.AddAuthenticationToken(make_token(1, 0, 0, 0)); - table.AddAuthenticationToken(make_token(1, 0, 0, 1)); - EXPECT_EQ(1U, table.size()); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ(1U, found.timestamp); - - // Add a third token, this with a different RSID. It should not be superseded. - table.AddAuthenticationToken(make_token(2, 0, 0, 2)); - EXPECT_EQ(2U, table.size()); - - // Add two more, superseding each of the two in the table. - table.AddAuthenticationToken(make_token(1, 0, 0, 3)); - table.AddAuthenticationToken(make_token(2, 0, 0, 4)); - EXPECT_EQ(2U, table.size()); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ(3U, found.timestamp); - EXPECT_EQ( - AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(2), KeyPurpose::SIGN, 0), rc)); - EXPECT_EQ(4U, found.timestamp); - - // Add another, this one with a challenge value. It should supersede the old one since it is - // newer, and matches other than the challenge. - table.AddAuthenticationToken(make_token(1, 0, 1, 5)); - EXPECT_EQ(2U, table.size()); - - // And another, also with a challenge. Because of the challenge values, the one just added - // cannot be superseded. - table.AddAuthenticationToken(make_token(1, 0, 2, 6)); - EXPECT_EQ(3U, table.size()); - - // Should be able to find each of them, by specifying their challenge, with a key that is not - // timed (timed keys don't care about challenges). - EXPECT_EQ(AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1, 0 /* no timeout*/), - KeyPurpose::SIGN, 1 /* challenge */), - rc)); - EXPECT_EQ(5U, found.timestamp); - EXPECT_EQ(AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1, 0 /* no timeout */), - KeyPurpose::SIGN, 2 /* challenge */), - rc)); - EXPECT_EQ(6U, found.timestamp); - - // Add another, without a challenge, and the same timestamp as the last one. This new one - // actually could be considered already-superseded, but the table doesn't handle that case, - // since it seems unlikely to occur in practice. - table.AddAuthenticationToken(make_token(1, 0, 0, 6)); - EXPECT_EQ(4U, table.size()); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - make_set(1), KeyPurpose::SIGN, 0 /* challenge */), - rc)); - EXPECT_EQ(6U, found.timestamp); - - // Add another without a challenge but an increased timestamp. This should supersede the - // previous challenge-free entry. - table.AddAuthenticationToken(make_token(1, 0, 0, 7)); - EXPECT_EQ(4U, table.size()); - EXPECT_EQ(AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1, 0 /* no timeout */), - KeyPurpose::SIGN, 2 /* challenge */), - rc)); - EXPECT_EQ(6U, found.timestamp); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - make_set(1), KeyPurpose::SIGN, 0 /* challenge */), - rc)); - EXPECT_EQ(7U, found.timestamp); - - // Mark the entry with challenge 2 as complete. Since there's a newer challenge-free entry, the - // challenge entry will be superseded. - table.MarkCompleted(2); - EXPECT_EQ(3U, table.size()); - EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(1, 0 /* no timeout */), - KeyPurpose::SIGN, 2 /* challenge */), - rc)); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - make_set(1), KeyPurpose::SIGN, 0 /* challenge */), - rc)); - EXPECT_EQ(7U, found.timestamp); - - // Add another SID 1 entry with a challenge. It supersedes the previous SID 1 entry with - // no challenge (timestamp 7), but not the one with challenge 1 (timestamp 5). - table.AddAuthenticationToken(make_token(1, 0, 3, 8)); - EXPECT_EQ(3U, table.size()); - - EXPECT_EQ(AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1, 0 /* no timeout */), - KeyPurpose::SIGN, 1 /* challenge */), - rc)); - EXPECT_EQ(5U, found.timestamp); - - EXPECT_EQ(AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1, 0 /* no timeout */), - KeyPurpose::SIGN, 3 /* challenge */), - rc)); - EXPECT_EQ(8U, found.timestamp); - - // SID 2 entry is still there. - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - make_set(2), KeyPurpose::SIGN, 0 /* challenge */), - rc)); - EXPECT_EQ(4U, found.timestamp); - - // Mark the entry with challenge 3 as complete. Since the older challenge 1 entry is - // incomplete, nothing is superseded. - table.MarkCompleted(3); - EXPECT_EQ(3U, table.size()); - - EXPECT_EQ(AuthTokenTable::OK, - (std::tie(rc, found) = table.FindAuthorization(make_set(1, 0 /* no timeout */), - KeyPurpose::SIGN, 1 /* challenge */), - rc)); - EXPECT_EQ(5U, found.timestamp); - - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - make_set(1), KeyPurpose::SIGN, 0 /* challenge */), - rc)); - EXPECT_EQ(8U, found.timestamp); - - // Mark the entry with challenge 1 as complete. Since there's a newer one (with challenge 3, - // completed), the challenge 1 entry is superseded and removed. - table.MarkCompleted(1); - EXPECT_EQ(2U, table.size()); - EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, - (std::tie(rc, found) = table.FindAuthorization(make_set(1, 0 /* no timeout */), - KeyPurpose::SIGN, 1 /* challenge */), - rc)); - EXPECT_EQ(AuthTokenTable::OK, (std::tie(rc, found) = table.FindAuthorization( - make_set(1), KeyPurpose::SIGN, 0 /* challenge */), - rc)); - EXPECT_EQ(8U, found.timestamp); -} - -} // namespace test -} // namespace keystore diff --git a/keystore/tests/confirmationui_rate_limiting_test.cpp b/keystore/tests/confirmationui_rate_limiting_test.cpp deleted file mode 100644 index f56b5099..00000000 --- a/keystore/tests/confirmationui_rate_limiting_test.cpp +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (C) 2018 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 <gtest/gtest.h> - -#include "../confirmationui_rate_limiting.h" -#include <keymaster/logger.h> - -using std::vector; - -namespace keystore { - -namespace test { - -namespace { - -class StdoutLogger : public ::keymaster::Logger { - public: - StdoutLogger() { set_instance(this); } - - int log_msg(LogLevel level, const char* fmt, va_list args) const { - int output_len = 0; - switch (level) { - case DEBUG_LVL: - output_len = printf("DEBUG: "); - break; - case INFO_LVL: - output_len = printf("INFO: "); - break; - case WARNING_LVL: - output_len = printf("WARNING: "); - break; - case ERROR_LVL: - output_len = printf("ERROR: "); - break; - case SEVERE_LVL: - output_len = printf("SEVERE: "); - break; - } - - output_len += vprintf(fmt, args); - output_len += printf("\n"); - return output_len; - } -}; - -StdoutLogger logger; - -class FakeClock : public std::chrono::steady_clock { - private: - static time_point sNow; - - public: - static void setNow(time_point newNow) { sNow = newNow; } - static time_point now() noexcept { return sNow; } -}; - -FakeClock::time_point FakeClock::sNow; - -} // namespace - -/* - * Test that there are no residual slots when various apps receive successful confirmations. - */ -TEST(ConfirmationUIRateLimitingTest, noPenaltyTest) { - auto now = std::chrono::steady_clock::now(); - RateLimiting<FakeClock> rateLimiting; - FakeClock::setNow(now); - - for (int i = 0; i < 10000; ++i) { - ASSERT_TRUE(rateLimiting.tryPrompt(rand())); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - ASSERT_EQ(0U, rateLimiting.usedSlots()); -} - -TEST(ConfirmationUIRateLimitingTest, policyTest) { - using namespace std::chrono_literals; - auto now = std::chrono::steady_clock::now(); - RateLimiting<FakeClock> rateLimiting; - FakeClock::setNow(now); - - // first three tries are free - for (int i = 0; i < 3; ++i) { - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - } - - // throw in a couple of successful confirmations by other apps to make sure there - // is not cross talk - for (int i = 0; i < 10000; ++i) { - uid_t id = rand(); - if (id == 20) continue; - ASSERT_TRUE(rateLimiting.tryPrompt(id)); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - // the next three tries get a 30s penalty - for (int i = 3; i < 6; ++i) { - FakeClock::setNow(FakeClock::now() + 29s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - } - - // throw in a couple of successful confirmations by other apps to make sure there - // is not cross talk - for (int i = 0; i < 10000; ++i) { - uid_t id = rand(); - if (id == 20) continue; - ASSERT_TRUE(rateLimiting.tryPrompt(id)); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - // there after the penalty doubles with each cancellation - for (int i = 6; i < 17; ++i) { - FakeClock::setNow((FakeClock::now() + 60s * (1ULL << (i - 6))) - 1s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - } - - // throw in a couple of successful confirmations by other apps to make sure there - // is not cross talk - for (int i = 0; i < 10000; ++i) { - uid_t id = rand(); - if (id == 20) continue; - ASSERT_TRUE(rateLimiting.tryPrompt(id)); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - ASSERT_EQ(1U, rateLimiting.usedSlots()); - - FakeClock::setNow(FakeClock::now() + 24h - 1s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - - // after 24h the counter is forgotten - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - - // throw in a couple of successful confirmations by other apps to make sure there - // is not cross talk - for (int i = 0; i < 10000; ++i) { - uid_t id = rand(); - if (id == 20) continue; - ASSERT_TRUE(rateLimiting.tryPrompt(id)); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - for (int i = 1; i < 3; ++i) { - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - } - - // throw in a couple of successful confirmations by other apps to make sure there - // is not cross talk - for (int i = 0; i < 10000; ++i) { - uid_t id = rand(); - if (id == 20) continue; - ASSERT_TRUE(rateLimiting.tryPrompt(id)); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - for (int i = 3; i < 6; ++i) { - FakeClock::setNow(FakeClock::now() + 29s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - } - - // throw in a couple of successful confirmations by other apps to make sure there - // is not cross talk - for (int i = 0; i < 10000; ++i) { - uid_t id = rand(); - if (id == 20) continue; - ASSERT_TRUE(rateLimiting.tryPrompt(id)); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - for (int i = 6; i < 17; ++i) { - FakeClock::setNow((FakeClock::now() + 60s * (1ULL << (i - 6))) - 1s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - } - - // throw in a couple of successful confirmations by other apps to make sure there - // is not cross talk - for (int i = 0; i < 10000; ++i) { - uid_t id = rand(); - if (id == 20) continue; - ASSERT_TRUE(rateLimiting.tryPrompt(id)); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - ASSERT_EQ(1U, rateLimiting.usedSlots()); -} - -TEST(ConfirmationUIRateLimitingTest, rewindTest) { - using namespace std::chrono_literals; - auto now = std::chrono::steady_clock::now(); - RateLimiting<FakeClock> rateLimiting; - - // first three tries are free - for (int i = 0; i < 3; ++i) { - FakeClock::setNow(now); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - } - - for (int i = 3; i < 6; ++i) { - FakeClock::setNow(FakeClock::now() + 29s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Canceled); - } - - FakeClock::setNow(FakeClock::now() + 59s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::Aborted); - - FakeClock::setNow(FakeClock::now() - 1s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::SystemError); - - // throw in a couple of successful confirmations by other apps to make sure there - // is not cross talk - for (int i = 0; i < 10000; ++i) { - uid_t id = rand(); - if (id == 20) continue; - ASSERT_TRUE(rateLimiting.tryPrompt(id)); - rateLimiting.processResult(ConfirmationResponseCode::OK); - } - - FakeClock::setNow(FakeClock::now() - 1s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - rateLimiting.processResult(ConfirmationResponseCode::UIError); - - FakeClock::setNow(FakeClock::now() - 1s); - ASSERT_FALSE(rateLimiting.tryPrompt(20)); - FakeClock::setNow(FakeClock::now() + 1s); - ASSERT_TRUE(rateLimiting.tryPrompt(20)); - - ASSERT_EQ(1U, rateLimiting.usedSlots()); -} - -} // namespace test -} // namespace keystore diff --git a/keystore/tests/fuzzer/Android.bp b/keystore/tests/fuzzer/Android.bp index 4116ae14..55d8f83e 100644 --- a/keystore/tests/fuzzer/Android.bp +++ b/keystore/tests/fuzzer/Android.bp @@ -15,6 +15,7 @@ */ package { + default_team: "trendy_team_android_hardware_backed_security", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "system_security_license" @@ -40,9 +41,17 @@ cc_fuzz { ], fuzz_config: { cc: [ - "android-media-fuzzing-reports@google.com", + "android-hardware-security@google.com", ], - componentid: 155276, + componentid: 1084732, + hotlists: [ + "4593311", + ], + description: "The fuzzer targets the APIs of libkeystore-wifi-hidl", + vector: "local_no_privileges_required", + service_privilege: "privileged", + users: "multi_user", + fuzzed_code_usage: "shipped", }, } @@ -55,15 +64,24 @@ cc_defaults { "libhidlbase", ], shared_libs: [ + "android.security.aaid_aidl-cpp", "libbinder", "libcrypto", "libutils", ], fuzz_config: { cc: [ - "android-media-fuzzing-reports@google.com", + "android-hardware-security@google.com", + ], + componentid: 1084732, + hotlists: [ + "4593311", ], - componentid: 155276, + description: "The fuzzer targets the APIs of libkeystore-attestation-application-id", + vector: "local_no_privileges_required", + service_privilege: "privileged", + users: "multi_user", + fuzzed_code_usage: "shipped", }, } diff --git a/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp b/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp index 0eddb9a8..9388001c 100644 --- a/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp +++ b/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp @@ -15,9 +15,9 @@ */ #include "keystoreCommon.h" -#include <keystore/KeyAttestationApplicationId.h> +#include <android/security/keystore/KeyAttestationApplicationId.h> -using ::security::keymaster::KeyAttestationApplicationId; +using ::android::security::keystore::KeyAttestationApplicationId; constexpr size_t kPackageVectorSizeMin = 1; constexpr size_t kPackageVectorSizeMax = 10; @@ -33,26 +33,37 @@ class KeystoreApplicationId { }; void KeystoreApplicationId::invokeApplicationId() { - std::optional<KeyAttestationApplicationId> applicationId; + KeyAttestationApplicationId applicationId; bool shouldUsePackageInfoVector = mFdp->ConsumeBool(); if (shouldUsePackageInfoVector) { - KeyAttestationApplicationId::PackageInfoVector packageInfoVector; + ::std::vector<KeyAttestationPackageInfo> packageInfoVector; int32_t packageVectorSize = mFdp->ConsumeIntegralInRange<int32_t>(kPackageVectorSizeMin, kPackageVectorSizeMax); for (int32_t packageSize = 0; packageSize < packageVectorSize; ++packageSize) { auto packageInfoData = initPackageInfoData(mFdp.get()); - packageInfoVector.push_back(make_optional<KeyAttestationPackageInfo>( - String16((packageInfoData.packageName).c_str()), packageInfoData.versionCode, - packageInfoData.sharedSignaturesVector)); + auto pInfo = KeyAttestationPackageInfo(); + pInfo.packageName = String16((packageInfoData.packageName).c_str()); + pInfo.versionCode = packageInfoData.versionCode; + std::move(packageInfoData.sharedSignaturesVector->begin(), + packageInfoData.sharedSignaturesVector->end(), + std::back_inserter(pInfo.signatures)); + + packageInfoVector.push_back(std::move(pInfo)); } - applicationId = KeyAttestationApplicationId(std::move(packageInfoVector)); + + std::move(packageInfoVector.begin(), packageInfoVector.end(), + std::back_inserter(applicationId.packageInfos)); } else { auto packageInfoData = initPackageInfoData(mFdp.get()); - applicationId = KeyAttestationApplicationId(make_optional<KeyAttestationPackageInfo>( - String16((packageInfoData.packageName).c_str()), packageInfoData.versionCode, - packageInfoData.sharedSignaturesVector)); + auto pInfo = KeyAttestationPackageInfo(); + pInfo.packageName = String16((packageInfoData.packageName).c_str()); + pInfo.versionCode = packageInfoData.versionCode; + std::move(packageInfoData.sharedSignaturesVector->begin(), + packageInfoData.sharedSignaturesVector->end(), + std::back_inserter(pInfo.signatures)); + applicationId.packageInfos.push_back(std::move(pInfo)); } - invokeReadWriteParcel(&applicationId.value()); + invokeReadWriteParcel(&applicationId); } void KeystoreApplicationId::process(const uint8_t* data, size_t size) { diff --git a/keystore/tests/fuzzer/keystoreCommon.h b/keystore/tests/fuzzer/keystoreCommon.h index 7af3ba8c..77d39e0a 100644 --- a/keystore/tests/fuzzer/keystoreCommon.h +++ b/keystore/tests/fuzzer/keystoreCommon.h @@ -16,18 +16,18 @@ #ifndef KEYSTORECOMMON_H #define KEYSTORECOMMON_H +#include <android/security/keystore/KeyAttestationPackageInfo.h> +#include <android/security/keystore/Signature.h> #include <binder/Parcel.h> #include <binder/Parcelable.h> -#include <keystore/KeyAttestationPackageInfo.h> -#include <keystore/Signature.h> #include <vector> #include "fuzzer/FuzzedDataProvider.h" using namespace android; using namespace std; -using ::content::pm::Signature; -using ::security::keymaster::KeyAttestationPackageInfo; +using ::android::security::keystore::KeyAttestationPackageInfo; +using ::android::security::keystore::Signature; constexpr size_t kSignatureSizeMin = 1; constexpr size_t kSignatureSizeMax = 1000; @@ -38,7 +38,7 @@ constexpr size_t kSignatureVectorSizeMax = 1000; struct PackageInfoData { string packageName; int64_t versionCode; - KeyAttestationPackageInfo::SharedSignaturesVector sharedSignaturesVector; + std::shared_ptr<std::vector<Signature>> sharedSignaturesVector; }; inline void invokeReadWriteParcel(Parcelable* obj) { @@ -60,18 +60,20 @@ inline PackageInfoData initPackageInfoData(FuzzedDataProvider* fdp) { packageInfoData.versionCode = fdp->ConsumeIntegral<int64_t>(); size_t signatureVectorSize = fdp->ConsumeIntegralInRange(kSignatureVectorSizeMin, kSignatureVectorSizeMax); - KeyAttestationPackageInfo::SignaturesVector signatureVector; + std::vector<Signature> signatureVector; for (size_t size = 0; size < signatureVectorSize; ++size) { bool shouldUseParameterizedConstructor = fdp->ConsumeBool(); if (shouldUseParameterizedConstructor) { vector<uint8_t> signatureData = initSignatureData(fdp); - signatureVector.push_back(make_optional<Signature>(signatureData)); + auto sign = Signature(); + sign.data = signatureData; + signatureVector.push_back(std::move(sign)); } else { - signatureVector.push_back(std::nullopt); + signatureVector.push_back(Signature()); } } packageInfoData.sharedSignaturesVector = - make_shared<KeyAttestationPackageInfo::SignaturesVector>(move(signatureVector)); + make_shared<std::vector<Signature>>(std::move(signatureVector)); return packageInfoData; } #endif // KEYSTORECOMMON_H diff --git a/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp b/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp index 63899ff8..f1e42041 100644 --- a/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp +++ b/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp @@ -28,9 +28,12 @@ class KeystorePackageInfoFuzzer { void KeystorePackageInfoFuzzer::invokePackageInfo() { auto packageInfoData = initPackageInfoData(mFdp.get()); - KeyAttestationPackageInfo packageInfo(String16((packageInfoData.packageName).c_str()), - packageInfoData.versionCode, - packageInfoData.sharedSignaturesVector); + auto packageInfo = KeyAttestationPackageInfo(); + packageInfo.packageName = String16((packageInfoData.packageName).c_str()); + packageInfo.versionCode = packageInfoData.versionCode; + std::move(packageInfoData.sharedSignaturesVector->begin(), + packageInfoData.sharedSignaturesVector->end(), + std::back_inserter(packageInfo.signatures)); invokeReadWriteParcel(&packageInfo); } diff --git a/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp b/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp index b8f8a73e..aab1f251 100644 --- a/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp +++ b/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp @@ -14,7 +14,9 @@ * limitations under the License. */ #include "keystoreCommon.h" -#include <keystore/Signature.h> +#include <android/security/keystore/Signature.h> + +using ::android::security::keystore::Signature; class KeystoreSignatureFuzzer { public: @@ -27,15 +29,15 @@ class KeystoreSignatureFuzzer { }; void KeystoreSignatureFuzzer::invokeSignature() { - std::optional<Signature> signature; + Signature signature; bool shouldUseParameterizedConstructor = mFdp->ConsumeBool(); if (shouldUseParameterizedConstructor) { std::vector<uint8_t> signatureData = initSignatureData(mFdp.get()); - signature = Signature(signatureData); + signature.data = signatureData; } else { signature = Signature(); } - invokeReadWriteParcel(&signature.value()); + invokeReadWriteParcel(&signature); } void KeystoreSignatureFuzzer::process(const uint8_t* data, size_t size) { diff --git a/keystore/tests/list_auth_bound_keys_test.sh b/keystore/tests/list_auth_bound_keys_test.sh deleted file mode 100755 index f609b346..00000000 --- a/keystore/tests/list_auth_bound_keys_test.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash - -# -# Copyright (C) 2018 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. -# -# -# Simple adb based test for keystore method list_auth_bound_keys -# Depends on keystore_cli_v2 tool and root -# - -set -e - -ROOT_ID=0 -USER1_ID=10901 -USER2_ID=10902 -SYSTEM_ID=1000 - -function cli { - adb shell "su $1 keystore_cli_v2 $2" -} - -#start as root -adb root - -# generate keys as user -echo "generating keys" -cli $USER1_ID "delete --name=no_auth_key" || true -cli $USER1_ID "generate --name=no_auth_key" -cli $USER2_ID "delete --name=auth_key" || true -if ! cli $USER2_ID "generate --name=auth_key --auth_bound"; then - echo "Unable to generate auth bound key, make sure device/emulator has a pin/password set." - echo "$ adb shell locksettings set-pin 1234" - exit 1 -fi - -# try to list keys as user -if cli $USER2_ID list-apps-with-keys; then - echo "Error: list-apps-with-keys succeeded as user, this is not expected!" - exit 1 -fi - -# try to list keys as root -if cli $ROOT_ID "list-apps-with-keys"; then - echo "Error: list-apps-with-keys succeeded as root, this is not expected!" - exit 1 -fi - -# try to list keys as system -success=false -while read -r line; do - echo $line - if [ "$line" == "$USER2_ID" ]; then - success=true - fi - if [ "$line" == "$USER1_ID" ]; then - echo "Error: User1 id not expected in list" - exit 1 - fi -done <<< $(cli $SYSTEM_ID "list-apps-with-keys") -if [ $success = true ]; then - echo "Success!" -else - echo "Error: User2 id not in list" - exit 1 -fi
\ No newline at end of file diff --git a/keystore/user_state.cpp b/keystore/user_state.cpp deleted file mode 100644 index 30dfe3c3..00000000 --- a/keystore/user_state.cpp +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#define LOG_TAG "keystore" - -#include "user_state.h" - -#include <dirent.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/stat.h> - -#include <openssl/digest.h> -#include <openssl/evp.h> -#include <openssl/rand.h> - -#include <log/log.h> - -#include "blob.h" -#include "keystore_utils.h" - -namespace keystore { - -UserState::UserState(uid_t userId) - : mMasterKeyEntry(".masterkey", "user_" + std::to_string(userId), userId, /* masterkey */ true), - mUserId(userId), mState(STATE_UNINITIALIZED) {} - -bool UserState::operator<(const UserState& rhs) const { - return getUserId() < rhs.getUserId(); -} - -bool UserState::operator<(uid_t userId) const { - return getUserId() < userId; -} - -bool operator<(uid_t userId, const UserState& rhs) { - return userId < rhs.getUserId(); -} - -bool UserState::initialize() { - if ((mkdir(mMasterKeyEntry.user_dir().c_str(), S_IRUSR | S_IWUSR | S_IXUSR) < 0) && - (errno != EEXIST)) { - ALOGE("Could not create directory '%s'", mMasterKeyEntry.user_dir().c_str()); - return false; - } - - if (mMasterKeyEntry.hasKeyBlob()) { - setState(STATE_LOCKED); - } else { - setState(STATE_UNINITIALIZED); - } - - return true; -} - -void UserState::setState(State state) { - mState = state; -} - -void UserState::zeroizeMasterKeysInMemory() { - memset(mMasterKey.data(), 0, mMasterKey.size()); - memset(mSalt, 0, sizeof(mSalt)); -} - -bool UserState::deleteMasterKey() { - setState(STATE_UNINITIALIZED); - zeroizeMasterKeysInMemory(); - return unlink(mMasterKeyEntry.getKeyBlobPath().c_str()) == 0 || errno == ENOENT; -} - -ResponseCode UserState::initialize(const android::String8& pw) { - if (!generateMasterKey()) { - return ResponseCode::SYSTEM_ERROR; - } - ResponseCode response = writeMasterKey(pw); - if (response != ResponseCode::NO_ERROR) { - return response; - } - setupMasterKeys(); - return ResponseCode::NO_ERROR; -} - -ResponseCode UserState::copyMasterKey(LockedUserState<UserState>* src) { - if (mState != STATE_UNINITIALIZED) { - return ResponseCode::SYSTEM_ERROR; - } - if ((*src)->getState() != STATE_NO_ERROR) { - return ResponseCode::SYSTEM_ERROR; - } - mMasterKey = (*src)->mMasterKey; - setupMasterKeys(); - return copyMasterKeyFile(src); -} - -ResponseCode UserState::copyMasterKeyFile(LockedUserState<UserState>* src) { - /* Copy the master key file to the new user. Unfortunately we don't have the src user's - * password so we cannot generate a new file with a new salt. - */ - int in = TEMP_FAILURE_RETRY(open((*src)->getMasterKeyFileName().c_str(), O_RDONLY)); - if (in < 0) { - return ResponseCode::SYSTEM_ERROR; - } - blobv3 rawBlob; - size_t length = readFully(in, (uint8_t*)&rawBlob, sizeof(rawBlob)); - if (close(in) != 0) { - return ResponseCode::SYSTEM_ERROR; - } - int out = TEMP_FAILURE_RETRY(open(mMasterKeyEntry.getKeyBlobPath().c_str(), - O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)); - if (out < 0) { - return ResponseCode::SYSTEM_ERROR; - } - size_t outLength = writeFully(out, (uint8_t*)&rawBlob, length); - if (close(out) != 0) { - return ResponseCode::SYSTEM_ERROR; - } - if (outLength != length) { - ALOGW("blob not fully written %zu != %zu", outLength, length); - unlink(mMasterKeyEntry.getKeyBlobPath().c_str()); - return ResponseCode::SYSTEM_ERROR; - } - return ResponseCode::NO_ERROR; -} - -ResponseCode UserState::writeMasterKey(const android::String8& pw) { - std::vector<uint8_t> passwordKey(mMasterKey.size()); - generateKeyFromPassword(passwordKey, pw, mSalt); - auto blobType = TYPE_MASTER_KEY_AES256; - if (mMasterKey.size() == kAes128KeySizeBytes) { - blobType = TYPE_MASTER_KEY; - } - Blob masterKeyBlob(mMasterKey.data(), mMasterKey.size(), mSalt, sizeof(mSalt), blobType); - auto lockedEntry = LockedKeyBlobEntry::get(mMasterKeyEntry); - return lockedEntry.writeBlobs(masterKeyBlob, {}, passwordKey, STATE_NO_ERROR); -} - -ResponseCode UserState::readMasterKey(const android::String8& pw) { - - auto lockedEntry = LockedKeyBlobEntry::get(mMasterKeyEntry); - - int in = TEMP_FAILURE_RETRY(open(mMasterKeyEntry.getKeyBlobPath().c_str(), O_RDONLY)); - if (in < 0) { - return ResponseCode::SYSTEM_ERROR; - } - - // We read the raw blob to just to get the salt to generate the AES key, then we create the Blob - // to use with decryptBlob - blobv3 rawBlob; - size_t length = readFully(in, (uint8_t*)&rawBlob, sizeof(rawBlob)); - if (close(in) != 0) { - return ResponseCode::SYSTEM_ERROR; - } - // find salt at EOF if present, otherwise we have an old file - uint8_t* salt; - if (length > SALT_SIZE && rawBlob.info == SALT_SIZE) { - salt = (uint8_t*)&rawBlob + length - SALT_SIZE; - } else { - salt = nullptr; - } - - size_t masterKeySize = MASTER_KEY_SIZE_BYTES; - if (rawBlob.type == TYPE_MASTER_KEY) { - masterKeySize = kAes128KeySizeBytes; - } - - std::vector<uint8_t> passwordKey(masterKeySize); - generateKeyFromPassword(passwordKey, pw, salt); - Blob masterKeyBlob, dummyBlob; - ResponseCode response; - std::tie(response, masterKeyBlob, dummyBlob) = - lockedEntry.readBlobs(passwordKey, STATE_NO_ERROR); - if (response == ResponseCode::SYSTEM_ERROR) { - return response; - } - - size_t masterKeyBlobLength = static_cast<size_t>(masterKeyBlob.getLength()); - - if (response == ResponseCode::NO_ERROR && masterKeyBlobLength == masterKeySize) { - // If salt was missing, generate one and write a new master key file with the salt. - if (salt == nullptr) { - if (!generateSalt()) { - return ResponseCode::SYSTEM_ERROR; - } - response = writeMasterKey(pw); - } - if (response == ResponseCode::NO_ERROR) { - mMasterKey = std::vector<uint8_t>(masterKeyBlob.getValue(), - masterKeyBlob.getValue() + masterKeyBlob.getLength()); - - setupMasterKeys(); - } - return response; - } - - LOG(ERROR) << "Invalid password presented"; - return ResponseCode::WRONG_PASSWORD_0; -} - -bool UserState::reset() { - DIR* dir = opendir(mMasterKeyEntry.user_dir().c_str()); - if (!dir) { - // If the directory doesn't exist then nothing to do. - if (errno == ENOENT) { - return true; - } - ALOGW("couldn't open user directory: %s", strerror(errno)); - return false; - } - - struct dirent* file; - while ((file = readdir(dir)) != nullptr) { - // skip . and .. - if (!strcmp(".", file->d_name) || !strcmp("..", file->d_name)) { - continue; - } - - unlinkat(dirfd(dir), file->d_name, 0); - } - closedir(dir); - return true; -} - -void UserState::generateKeyFromPassword(std::vector<uint8_t>& key, const android::String8& pw, - uint8_t* salt) { - size_t saltSize; - if (salt != nullptr) { - saltSize = SALT_SIZE; - } else { - // Pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found - salt = (uint8_t*)"keystore"; - // sizeof = 9, not strlen = 8 - saltSize = sizeof("keystore"); - } - - const EVP_MD* digest = EVP_sha256(); - - // SHA1 was used prior to increasing the key size - if (key.size() == kAes128KeySizeBytes) { - digest = EVP_sha1(); - } - - PKCS5_PBKDF2_HMAC(reinterpret_cast<const char*>(pw.string()), pw.length(), salt, saltSize, 8192, - digest, key.size(), key.data()); -} - -bool UserState::generateSalt() { - return RAND_bytes(mSalt, sizeof(mSalt)); -} - -bool UserState::generateMasterKey() { - mMasterKey.resize(MASTER_KEY_SIZE_BYTES); - if (!RAND_bytes(mMasterKey.data(), mMasterKey.size())) { - return false; - } - if (!generateSalt()) { - return false; - } - return true; -} - -void UserState::setupMasterKeys() { - setState(STATE_NO_ERROR); -} - -LockedUserState<UserState> UserStateDB::getUserState(uid_t userId) { - std::unique_lock<std::mutex> lock(locked_state_mutex_); - decltype(mMasterKeys.begin()) it; - bool inserted; - std::tie(it, inserted) = mMasterKeys.emplace(userId, userId); - if (inserted) { - if (!it->second.initialize()) { - /* There's not much we can do if initialization fails. Trying to - * unlock the keystore for that user will fail as well, so any - * subsequent request for this user will just return SYSTEM_ERROR. - */ - ALOGE("User initialization failed for %u; subsequent operations will fail", userId); - } - } - return get(std::move(lock), &it->second); -} - -LockedUserState<UserState> UserStateDB::getUserStateByUid(uid_t uid) { - return getUserState(get_user_id(uid)); -} - -LockedUserState<const UserState> UserStateDB::getUserState(uid_t userId) const { - std::unique_lock<std::mutex> lock(locked_state_mutex_); - auto it = mMasterKeys.find(userId); - if (it == mMasterKeys.end()) return {}; - return get(std::move(lock), &it->second); -} - -LockedUserState<const UserState> UserStateDB::getUserStateByUid(uid_t uid) const { - return getUserState(get_user_id(uid)); -} - -} // namespace keystore diff --git a/keystore/user_state.h b/keystore/user_state.h deleted file mode 100644 index 75d99d91..00000000 --- a/keystore/user_state.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#ifndef KEYSTORE_USER_STATE_H_ -#define KEYSTORE_USER_STATE_H_ - -#include <sys/types.h> - -#include <openssl/aes.h> - -#include <utils/String8.h> - -#include <keystore/keystore.h> - -#include "blob.h" -#include "keystore_utils.h" - -#include <android-base/logging.h> -#include <condition_variable> -#include <keystore/keystore_concurrency.h> -#include <mutex> -#include <set> -#include <vector> - -namespace keystore { - -class UserState; - -template <typename UserState> using LockedUserState = ProxyLock<UnlockProxyLockHelper<UserState>>; - -class UserState { - public: - explicit UserState(uid_t userId); - - bool initialize(); - - uid_t getUserId() const { return mUserId; } - const std::string& getUserDirName() const { return mMasterKeyEntry.user_dir(); } - - std::string getMasterKeyFileName() const { return mMasterKeyEntry.getKeyBlobPath(); } - - void setState(State state); - State getState() const { return mState; } - - void zeroizeMasterKeysInMemory(); - bool deleteMasterKey(); - - ResponseCode initialize(const android::String8& pw); - - ResponseCode copyMasterKey(LockedUserState<UserState>* src); - ResponseCode copyMasterKeyFile(LockedUserState<UserState>* src); - ResponseCode writeMasterKey(const android::String8& pw); - ResponseCode readMasterKey(const android::String8& pw); - - const std::vector<uint8_t>& getEncryptionKey() const { return mMasterKey; } - - bool reset(); - - bool operator<(const UserState& rhs) const; - bool operator<(uid_t userId) const; - - private: - static constexpr int SHA1_DIGEST_SIZE_BYTES = 16; - static constexpr int SHA256_DIGEST_SIZE_BYTES = 32; - - static constexpr int MASTER_KEY_SIZE_BYTES = kAes256KeySizeBytes; - static constexpr int MASTER_KEY_SIZE_BITS = MASTER_KEY_SIZE_BYTES * 8; - - static constexpr size_t SALT_SIZE = 16; - - void generateKeyFromPassword(std::vector<uint8_t>& key, const android::String8& pw, - uint8_t* salt); - bool generateSalt(); - bool generateMasterKey(); - void setupMasterKeys(); - - KeyBlobEntry mMasterKeyEntry; - - uid_t mUserId; - State mState; - - std::vector<uint8_t> mMasterKey; - uint8_t mSalt[SALT_SIZE]; -}; - -bool operator<(uid_t userId, const UserState& rhs); - -class UserStateDB { - public: - LockedUserState<UserState> getUserState(uid_t userId); - LockedUserState<UserState> getUserStateByUid(uid_t uid); - LockedUserState<const UserState> getUserState(uid_t userId) const; - LockedUserState<const UserState> getUserStateByUid(uid_t uid) const; - - private: - mutable std::set<const UserState*> locked_state_; - mutable std::mutex locked_state_mutex_; - mutable std::condition_variable locked_state_mutex_cond_var_; - - template <typename UserState> - LockedUserState<UserState> get(std::unique_lock<std::mutex> lock, UserState* entry) const { - locked_state_mutex_cond_var_.wait( - lock, [&] { return locked_state_.find(entry) == locked_state_.end(); }); - locked_state_.insert(entry); - return {entry, [&](UserState* entry) { - std::unique_lock<std::mutex> lock(locked_state_mutex_); - locked_state_.erase(entry); - lock.unlock(); - locked_state_mutex_cond_var_.notify_all(); - }}; - } - - std::map<uid_t, UserState> mMasterKeys; -}; - -} // namespace keystore - -#endif // KEYSTORE_USER_STATE_H_ diff --git a/keystore2/Android.bp b/keystore2/Android.bp index d8c00814..ed9cd880 100644 --- a/keystore2/Android.bp +++ b/keystore2/Android.bp @@ -28,6 +28,7 @@ rust_defaults { defaults: [ "keymint_use_latest_hal_aidl_rust", "keystore2_use_latest_aidl_rust", + "structured_log_rust_defaults", ], rustlibs: [ @@ -40,26 +41,30 @@ rust_defaults { "android.security.compat-rust", "android.security.maintenance-rust", "android.security.metrics-rust", - "android.security.remoteprovisioning-rust", "android.security.rkp_aidl-rust", + "libaconfig_android_hardware_biometrics_rust", + "libandroid_security_flags_rust", "libanyhow", "libbinder_rs", "libkeystore2_aaid-rust", "libkeystore2_apc_compat-rust", "libkeystore2_crypto_rust", + "libkeystore2_flags_rust", + "libkeystore2_hal_names_rust", "libkeystore2_km_compat", "libkeystore2_selinux", - "libkeystore2_vintf_rust", "liblazy_static", "liblibc", - "liblog_event_list", "liblog_rust", + "libmessage_macro", "librand", + "librkpd_client", "librustutils", "libserde", "libserde_cbor", "libthiserror", "libtokio", + "libwatchdog_rs", ], shared_libs: [ "libcutils", @@ -79,62 +84,15 @@ rust_library { } rust_library { - name: "libkeystore2_test_utils", - crate_name: "keystore2_test_utils", - srcs: ["test_utils/lib.rs"], - defaults: [ - "keymint_use_latest_hal_aidl_rust", - "keystore2_use_latest_aidl_rust", - ], - rustlibs: [ - "android.hardware.security.rkp-V3-rust", - "libbinder_rs", - "libkeystore2_selinux", - "liblog_rust", - "libnix", - "librand", - "libserde", - "libserde_cbor", - "libthiserror", - "libanyhow", - ], -} - -rust_library { name: "libkeystore2_with_test_utils", defaults: ["libkeystore2_defaults"], features: [ "keystore2_blob_test_utils", ], rustlibs: [ + "libkeystore2_test_utils", "liblibsqlite3_sys", "librusqlite", - "libkeystore2_test_utils", - ], -} - -rust_test { - name: "keystore2_test_utils_test", - srcs: ["test_utils/lib.rs"], - defaults: [ - "keymint_use_latest_hal_aidl_rust", - "keystore2_use_latest_aidl_rust", - ], - test_suites: ["general-tests"], - require_root: true, - auto_gen_config: true, - compile_multilib: "first", - rustlibs: [ - "android.hardware.security.rkp-V3-rust", - "libbinder_rs", - "libkeystore2_selinux", - "liblog_rust", - "libnix", - "librand", - "libserde", - "libserde_cbor", - "libthiserror", - "libanyhow", ], } @@ -147,16 +105,17 @@ rust_test { defaults: ["libkeystore2_defaults"], rustlibs: [ "libandroid_logger", + "libhex", "libkeystore2_test_utils", + "libkeystore2_with_test_utils", "liblibsqlite3_sys", "libnix", "librusqlite", - "libkeystore2_with_test_utils", ], // The test should always include watchdog. features: [ - "watchdog", "keystore2_blob_test_utils", + "watchdog", ], require_root: true, } @@ -194,3 +153,23 @@ rust_binary { ], afdo: true, } + +// Keystore Flag definitions +aconfig_declarations { + name: "keystore2_flags", + package: "android.security.keystore2", + container: "system", + srcs: ["aconfig/flags.aconfig"], +} + +rust_aconfig_library { + name: "libkeystore2_flags_rust", + crate_name: "keystore2_flags", + aconfig_declarations: "keystore2_flags", +} + +rust_aconfig_library { + name: "libaconfig_android_hardware_biometrics_rust", + crate_name: "aconfig_android_hardware_biometrics_rust", + aconfig_declarations: "android.hardware.biometrics.flags-aconfig", +} diff --git a/keystore2/OWNERS b/keystore2/OWNERS new file mode 100644 index 00000000..6b1a95bd --- /dev/null +++ b/keystore2/OWNERS @@ -0,0 +1,9 @@ +set noparent +# Bug component: 1084732 +eranm@google.com +drysdale@google.com +hasinitg@google.com +jbires@google.com +sethmo@google.com +trong@google.com +swillden@google.com diff --git a/keystore2/TEST_MAPPING b/keystore2/TEST_MAPPING index 5d0a7dd3..57ce78cc 100644 --- a/keystore2/TEST_MAPPING +++ b/keystore2/TEST_MAPPING @@ -30,6 +30,15 @@ "postsubmit": [ { "name": "CtsKeystorePerformanceTestCases" + }, + { + "name": "keystore2_client_tests" + }, + { + "name": "librkpd_client.test" + }, + { + "name": "libwatchdog_rs.test" } ] } diff --git a/keystore2/aaid/Android.bp b/keystore2/aaid/Android.bp index 3417960d..3e90a926 100644 --- a/keystore2/aaid/Android.bp +++ b/keystore2/aaid/Android.bp @@ -27,7 +27,7 @@ cc_library { "aaid.cpp", ], shared_libs: [ - "libkeystore-attestation-application-id" + "libkeystore-attestation-application-id", ], } @@ -38,7 +38,6 @@ rust_bindgen { source_stem: "bindings", bindgen_flags: [ - "--size_t-is-usize", "--allowlist-function=aaid_keystore_attestation_id", "--allowlist-var=KEY_ATTESTATION_APPLICATION_ID_MAX_SIZE", ], diff --git a/keystore2/aconfig/flags.aconfig b/keystore2/aconfig/flags.aconfig new file mode 100644 index 00000000..b67bc6cb --- /dev/null +++ b/keystore2/aconfig/flags.aconfig @@ -0,0 +1,26 @@ +package: "android.security.keystore2" +container: "system" + +flag { + name: "wal_db_journalmode_v3" + namespace: "hardware_backed_security" + description: "This flag controls changing journalmode to wal" + bug: "191777960" + is_fixed_read_only: true +} + +flag { + name: "disable_legacy_keystore_put_v2" + namespace: "hardware_backed_security" + description: "This flag disables legacy keystore put and makes it so that command returns an error" + bug: "307460850" + is_fixed_read_only: true +} + +flag { + name: "import_previously_emulated_keys" + namespace: "hardware_backed_security" + description: "Include support for importing keys that were previously software-emulated into KeyMint" + bug: "283077822" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp index e3961da3..c297a158 100644 --- a/keystore2/aidl/Android.bp +++ b/keystore2/aidl/Android.bp @@ -23,8 +23,8 @@ package { aidl_interface { name: "android.security.attestationmanager", - srcs: [ "android/security/attestationmanager/*.aidl", ], - imports: [ "android.hardware.security.keymint-V3" ], + srcs: ["android/security/attestationmanager/*.aidl"], + imports: ["android.hardware.security.keymint-V3"], unstable: true, backend: { java: { @@ -36,13 +36,13 @@ aidl_interface { ndk: { enabled: true, apps_enabled: false, - } + }, }, } aidl_interface { name: "android.security.authorization", - srcs: [ "android/security/authorization/*.aidl" ], + srcs: ["android/security/authorization/*.aidl"], imports: [ "android.hardware.security.keymint-V3", "android.hardware.security.secureclock-V1", @@ -58,13 +58,13 @@ aidl_interface { ndk: { enabled: true, apps_enabled: false, - } + }, }, } aidl_interface { name: "android.security.apc", - srcs: [ "android/security/apc/*.aidl" ], + srcs: ["android/security/apc/*.aidl"], unstable: true, backend: { java: { @@ -75,13 +75,13 @@ aidl_interface { }, ndk: { enabled: true, - } + }, }, } aidl_interface { name: "android.security.compat", - srcs: [ "android/security/compat/*.aidl" ], + srcs: ["android/security/compat/*.aidl"], imports: [ "android.hardware.security.keymint-V3", "android.hardware.security.secureclock-V1", @@ -103,32 +103,10 @@ aidl_interface { } aidl_interface { - name: "android.security.remoteprovisioning", - srcs: [ "android/security/remoteprovisioning/*.aidl" ], - imports: [ - "android.hardware.security.keymint-V3", - "android.hardware.security.rkp-V3", - ], - unstable: true, - backend: { - java: { - platform_apis: true, - }, - ndk: { - enabled: true, - apps_enabled: false, - }, - rust: { - enabled: true, - }, - }, -} - -aidl_interface { name: "android.security.maintenance", - srcs: [ "android/security/maintenance/*.aidl" ], + srcs: ["android/security/maintenance/*.aidl"], imports: [ - "android.system.keystore2-V3", + "android.system.keystore2-V4", ], unstable: true, backend: { @@ -141,13 +119,13 @@ aidl_interface { ndk: { enabled: true, apps_enabled: false, - } + }, }, } aidl_interface { name: "android.security.legacykeystore", - srcs: [ "android/security/legacykeystore/*.aidl" ], + srcs: ["android/security/legacykeystore/*.aidl"], unstable: true, backend: { java: { @@ -159,15 +137,15 @@ aidl_interface { ndk: { enabled: true, apps_enabled: false, - } + }, }, } aidl_interface { name: "android.security.metrics", - srcs: [ "android/security/metrics/*.aidl" ], + srcs: ["android/security/metrics/*.aidl"], imports: [ - "android.system.keystore2-V3", + "android.system.keystore2-V4", ], unstable: true, backend: { @@ -180,7 +158,7 @@ aidl_interface { ndk: { enabled: true, apps_enabled: false, - } + }, }, } @@ -190,21 +168,21 @@ aidl_interface { java_defaults { name: "keystore2_use_latest_aidl_java_static", static_libs: [ - "android.system.keystore2-V3-java-source" + "android.system.keystore2-V4-java-source", ], } java_defaults { name: "keystore2_use_latest_aidl_java_shared", libs: [ - "android.system.keystore2-V3-java-source" + "android.system.keystore2-V4-java-source", ], } java_defaults { name: "keystore2_use_latest_aidl_java", libs: [ - "android.system.keystore2-V3-java" + "android.system.keystore2-V4-java", ], } @@ -214,38 +192,37 @@ java_defaults { cc_defaults { name: "keystore2_use_latest_aidl_ndk_static", static_libs: [ - "android.system.keystore2-V3-ndk", + "android.system.keystore2-V4-ndk", ], } cc_defaults { name: "keystore2_use_latest_aidl_ndk_shared", shared_libs: [ - "android.system.keystore2-V3-ndk", + "android.system.keystore2-V4-ndk", ], } cc_defaults { name: "keystore2_use_latest_aidl_cpp_shared", shared_libs: [ - "android.system.keystore2-V3-cpp", + "android.system.keystore2-V4-cpp", ], } cc_defaults { name: "keystore2_use_latest_aidl_cpp_static", static_libs: [ - "android.system.keystore2-V3-cpp", + "android.system.keystore2-V4-cpp", ], } - // A rust_defaults that includes the latest Keystore2 AIDL library. // Modules that depend on Keystore2 directly can include this rust_defaults to avoid // managing dependency versions explicitly. rust_defaults { name: "keystore2_use_latest_aidl_rust", rustlibs: [ - "android.system.keystore2-V3-rust", + "android.system.keystore2-V4-rust", ], } diff --git a/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl b/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl index 277b9dd8..5b22be01 100644 --- a/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl +++ b/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl @@ -27,6 +27,10 @@ interface IConfirmationCallback { /** * This callback gets called by the implementing service when a pending confirmation prompt * gets finalized. + * @deprecated Android Protected Confirmation had a low adoption rate among Android device + * makers and developers alike. Given the lack of devices supporting the feature, + * it is deprecated. Developers can use auth-bound Keystore keys as a partial + * replacement. * * @param result * - ResponseCode.OK On success. In this case dataConfirmed must be non null. diff --git a/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl b/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl index 3162224f..9f978479 100644 --- a/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl +++ b/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl @@ -35,6 +35,10 @@ interface IProtectedConfirmation { /** * Present the confirmation prompt. The caller must implement IConfirmationCallback and pass * it to this function as listener. + * @deprecated Android Protected Confirmation had a low adoption rate among Android device + * makers and developers alike. Given the lack of devices supporting the + * feature, it is deprecated. Developers can use auth-bound Keystore keys + * as a partial replacement. * * @param listener Must implement IConfirmationCallback. Doubles as session identifier when * passed to cancelPrompt. @@ -55,6 +59,11 @@ interface IProtectedConfirmation { /** * Cancel an ongoing prompt. + * @deprecated Android Protected Confirmation had a low adoption rate among Android device + * makers and developers alike. Given the lack of devices supporting the + * feature, it is deprecated. Developers can use auth-bound Keystore keys as + * a partial replacement. + * * * @param listener Must implement IConfirmationCallback, although in this context this binder * token is only used to identify the session that is to be cancelled. @@ -66,6 +75,10 @@ interface IProtectedConfirmation { /** * Returns true if the device supports Android Protected Confirmation. + * @deprecated Android Protected Confirmation had a low adoption rate among Android device + * makers and developers alike. Given the lack of devices supporting the + * feature, it is deprecated. Developers can use auth-bound Keystore keys + * as a partial replacement. */ boolean isSupported(); } diff --git a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl index e3b7d11d..fd532f62 100644 --- a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl +++ b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl @@ -15,11 +15,9 @@ package android.security.authorization; import android.hardware.security.keymint.HardwareAuthToken; -import android.security.authorization.LockScreenEvent; +import android.hardware.security.keymint.HardwareAuthenticatorType; import android.security.authorization.AuthorizationTokens; -// TODO: mark the interface with @SensitiveData when the annotation is ready (b/176110256). - /** * IKeystoreAuthorization interface exposes the methods for other system components to * provide keystore with the information required to enforce authorizations on key usage. @@ -40,41 +38,102 @@ interface IKeystoreAuthorization { void addAuthToken(in HardwareAuthToken authToken); /** - * Unlocks the keystore for the given user id. + * Tells Keystore that the device is now unlocked for a user. Requires the 'Unlock' permission. + * + * This method makes Keystore start allowing the use of the given user's keys that require an + * unlocked device, following the device boot or an earlier call to onDeviceLocked() which + * disabled the use of such keys. In addition, once per boot, this method must be called with a + * password before keys that require user authentication can be used. + * + * This method does two things to restore access to UnlockedDeviceRequired keys. First, it sets + * a flag that indicates the user is unlocked. This is always done, and it makes Keystore's + * logical enforcement of UnlockedDeviceRequired start passing. Second, it recovers and caches + * the user's UnlockedDeviceRequired super keys. This succeeds only in the following cases: + * + * - The (correct) password is provided, proving that the user has authenticated using LSKF or + * equivalent. This is the most powerful type of unlock. Keystore uses the password to + * decrypt the user's UnlockedDeviceRequired super keys from disk. It also uses the password + * to decrypt the user's AfterFirstUnlock super key from disk, if not already done. + * + * - The user's UnlockedDeviceRequired super keys are cached in biometric-encrypted form, and a + * matching valid HardwareAuthToken has been added to Keystore. I.e., class 3 biometric + * unlock is enabled and the user recently authenticated using a class 3 biometric. The keys + * are cached in biometric-encrypted form if onDeviceLocked() was called with a nonempty list + * of unlockingSids, and onNonLskfUnlockMethodsExpired() was not called later. + * + * - The user's UnlockedDeviceRequired super keys are already cached in plaintext. This is the + * case if onDeviceLocked() was called with weakUnlockEnabled=true, and + * onWeakUnlockMethodsExpired() was not called later. This case provides only + * Keystore-enforced logical security for UnlockedDeviceRequired. + * + * ## Error conditions: + * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'Unlock' permission. + * `ResponseCode::VALUE_CORRUPTED` - if a super key can not be decrypted. + * `ResponseCode::KEY_NOT_FOUND` - if a super key is not found. + * `ResponseCode::SYSTEM_ERROR` - if another error occurred. + * + * @param userId The Android user ID of the user for which the device is now unlocked + * @param password If available, a secret derived from the user's synthetic password + */ + void onDeviceUnlocked(in int userId, in @nullable byte[] password); + + /** + * Tells Keystore that the device is now locked for a user. Requires the 'Lock' permission. + * + * This method makes Keystore stop allowing the use of the given user's keys that require an + * unlocked device. This is enforced logically, and when possible it's also enforced + * cryptographically by wiping the UnlockedDeviceRequired super keys from memory. + * + * unlockingSids and weakUnlockEnabled specify the methods by which the device can become + * unlocked for the user, in addition to LSKF-equivalent authentication. + * + * unlockingSids is the list of SIDs of class 3 (strong) biometrics that can unlock. If + * unlockingSids is non-empty, then this method saves a copy of the UnlockedDeviceRequired super + * keys in memory encrypted by a new AES key that is imported into KeyMint and configured to be + * usable only when user authentication has occurred using any of the SIDs. This allows the + * keys to be recovered if the device is unlocked using a class 3 biometric. * - * Callers require 'Unlock' permission. + * weakUnlockEnabled is true if the unlock can happen using a method that does not have an + * associated SID, such as a class 1 (convenience) biometric, class 2 (weak) biometric, or trust + * agent. These methods don't count as "authentication" from Keystore's perspective. In this + * case, Keystore keeps a copy of the UnlockedDeviceRequired super keys in memory in plaintext, + * providing only logical security for UnlockedDeviceRequired. * - * Super-Encryption Key: - * When the device is unlocked (and password is non-null), Keystore stores in memory - * a super-encryption key derived from the password that protects UNLOCKED_DEVICE_REQUIRED - * keys; this key is wiped from memory when the device is locked. + * ## Error conditions: + * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'Lock' permission. * - * If unlockingSids is non-empty on lock, then before the super-encryption key is wiped from - * memory, a copy of it is stored in memory encrypted with a fresh AES key. This key is then - * imported into KM, tagged such that it can be used given a valid, recent auth token for any - * of the unlockingSids. + * @param userId The Android user ID of the user for which the device is now locked + * @param unlockingSids SIDs of class 3 biometrics that can unlock the device for the user + * @param weakUnlockEnabled Whether a weak unlock method can unlock the device for the user + */ + void onDeviceLocked(in int userId, in long[] unlockingSids, in boolean weakUnlockEnabled); + + /** + * Tells Keystore that weak unlock methods can no longer unlock the device for the given user. + * This is intended to be called after an earlier call to onDeviceLocked() with + * weakUnlockEnabled=true. It upgrades the security level of UnlockedDeviceRequired keys to + * that which would have resulted from calling onDeviceLocked() with weakUnlockEnabled=false. * - * Options for unlock: - * - If the password is non-null, the super-encryption key is re-derived as above. - * - If the password is null, then if a suitable auth token to access the encrypted - * Super-encryption key stored in KM has been sent to keystore (via addAuthToken), the - * encrypted super-encryption key is recovered so that UNLOCKED_DEVICE_REQUIRED keys can - * be used once again. - * - If neither of these are met, then the operation fails. + * ## Error conditions: + * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'Lock' permission. + * + * @param userId The Android user ID of the user for which weak unlock methods have expired + */ + void onWeakUnlockMethodsExpired(in int userId); + + /** + * Tells Keystore that non-LSKF-equivalent unlock methods can no longer unlock the device for + * the given user. This is intended to be called after an earlier call to onDeviceLocked() with + * nonempty unlockingSids. It upgrades the security level of UnlockedDeviceRequired keys to + * that which would have resulted from calling onDeviceLocked() with unlockingSids=[] and + * weakUnlockEnabled=false. * * ## Error conditions: - * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'Unlock' permission. - * `ResponseCode::SYSTEM_ERROR` - if failed to perform lock/unlock operations due to various - * `ResponseCode::VALUE_CORRUPTED` - if the super key can not be decrypted. - * `ResponseCode::KEY_NOT_FOUND` - if the super key is not found. - * - * @param lockScreenEvent whether the lock screen locked or unlocked - * @param userId android user id - * @param password synthetic password derived from the user's LSKF, must be null on lock - * @param unlockingSids list of biometric SIDs for this user, ignored on unlock + * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'Lock' permission. + * + * @param userId The Android user ID of the user for which non-LSKF unlock methods have expired */ - void onLockScreenEvent(in LockScreenEvent lockScreenEvent, in int userId, - in @nullable byte[] password, in @nullable long[] unlockingSids); + void onNonLskfUnlockMethodsExpired(in int userId); /** * Allows Credstore to retrieve a HardwareAuthToken and a TimestampToken. @@ -108,4 +167,13 @@ interface IKeystoreAuthorization { */ AuthorizationTokens getAuthTokensForCredStore(in long challenge, in long secureUserId, in long authTokenMaxAgeMillis); + + /** + * Returns the last successful authentication time since boot for the given user with any of the + * given authenticator types. This is determined by inspecting the cached auth tokens. + * + * ## Error conditions: + * `ResponseCode::NO_AUTH_TOKEN_FOUND` - if there is no matching authentication token found + */ + long getLastAuthTime(in long secureUserId, in HardwareAuthenticatorType[] authTypes); } diff --git a/keystore2/aidl/android/security/authorization/LockScreenEvent.aidl b/keystore2/aidl/android/security/authorization/LockScreenEvent.aidl deleted file mode 100644 index c7553a27..00000000 --- a/keystore2/aidl/android/security/authorization/LockScreenEvent.aidl +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2020, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package android.security.authorization; - -/** @hide */ -@Backing(type="int") -enum LockScreenEvent { - UNLOCK = 0, - LOCK = 1, -} diff --git a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl index 6a37c786..50e98286 100644 --- a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl +++ b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl @@ -16,7 +16,6 @@ package android.security.maintenance; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; -import android.security.maintenance.UserState; /** * IKeystoreMaintenance interface exposes the methods for adding/removing users and changing the @@ -28,10 +27,10 @@ interface IKeystoreMaintenance { /** * Allows LockSettingsService to inform keystore about adding a new user. - * Callers require 'AddUser' permission. + * Callers require 'ChangeUser' permission. * * ## Error conditions: - * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'AddUser' permission. + * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'ChangeUser' permission. * `ResponseCode::SYSTEM_ERROR` - if failed to delete the keys of an existing user with the same * user id. * @@ -40,11 +39,25 @@ interface IKeystoreMaintenance { void onUserAdded(in int userId); /** + * Allows LockSettingsService to tell Keystore to create a user's superencryption keys and store + * them encrypted by the given secret. Requires 'ChangeUser' permission. + * + * ## Error conditions: + * `ResponseCode::PERMISSION_DENIED` - if caller does not have the 'ChangeUser' permission + * `ResponseCode::SYSTEM_ERROR` - if failed to initialize the user's super keys + * + * @param userId - Android user id + * @param password - a secret derived from the synthetic password of the user + * @param allowExisting - if true, then the keys already existing is not considered an error + */ + void initUserSuperKeys(in int userId, in byte[] password, in boolean allowExisting); + + /** * Allows LockSettingsService to inform keystore about removing a user. - * Callers require 'RemoveUser' permission. + * Callers require 'ChangeUser' permission. * * ## Error conditions: - * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'RemoveUser' permission. + * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'ChangeUser' permission. * `ResponseCode::SYSTEM_ERROR` - if failed to delete the keys of the user being deleted. * * @param userId - Android user id @@ -52,6 +65,18 @@ interface IKeystoreMaintenance { void onUserRemoved(in int userId); /** + * Allows LockSettingsService to tell Keystore that a user's LSKF is being removed, ie the + * user's lock screen is changing to Swipe or None. Requires 'ChangePassword' permission. + * + * ## Error conditions: + * `ResponseCode::PERMISSION_DENIED` - if caller does not have the 'ChangePassword' permission + * `ResponseCode::SYSTEM_ERROR` - if failed to delete the user's auth-bound keys + * + * @param userId - Android user id + */ + void onUserLskfRemoved(in int userId); + + /** * Allows LockSettingsService to inform keystore about password change of a user. * Callers require 'ChangePassword' permission. * @@ -77,19 +102,6 @@ interface IKeystoreMaintenance { void clearNamespace(Domain domain, long nspace); /** - * Allows querying user state, given user id. - * Callers require 'GetState' permission. - * - * ## Error conditions: - * `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'GetState' - * permission. - * `ResponseCode::SYSTEM_ERROR` - if an error occurred when querying the user state. - * - * @param userId - Android user id - */ - UserState getState(in int userId); - - /** * This function notifies the Keymint device of the specified securityLevel that * early boot has ended, so that they no longer allow early boot keys to be used. * ## Error conditions: @@ -100,16 +112,6 @@ interface IKeystoreMaintenance { void earlyBootEnded(); /** - * Informs Keystore 2.0 that the an off body event was detected. - * - * ## Error conditions: - * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the `ReportOffBody` - * permission. - * `ResponseCode::SYSTEM_ERROR` - if an unexpected error occurred. - */ - void onDeviceOffBody(); - - /** * Migrate a key from one namespace to another. The caller must have use, grant, and delete * permissions on the source namespace and rebind permissions on the destination namespace. * The source may be specified by Domain::APP, Domain::SELINUX, or Domain::KEY_ID. The target @@ -131,4 +133,23 @@ interface IKeystoreMaintenance { * Tag::ROLLBACK_RESISTANCE may or may not be rendered unusable. */ void deleteAllKeys(); + + /** + * Returns a list of App UIDs that have keys associated with the given SID, under the + * given user ID. + * When a given user's LSKF is removed or biometric authentication methods are changed + * (addition of a fingerprint, for example), authentication-bound keys may be invalidated. + * This method allows the platform to find out which apps would be affected (for a given user) + * when a given user secure ID is removed. + * Callers require the `android.permission.MANAGE_USERS` Android permission + * (not SELinux policy). + * + * @param userId The affected user. + * @param sid The user secure ID - identifier of the authentication method. + * + * @return A list of APP UIDs, in the form of (AID + userId*AID_USER_OFFSET), that have + * keys auth-bound to the given SID. These values can be passed into the + * PackageManager for resolution. + */ + long[] getAppUidsAffectedBySid(in int userId, in long sid); } diff --git a/keystore2/aidl/android/security/maintenance/UserState.aidl b/keystore2/aidl/android/security/maintenance/UserState.aidl deleted file mode 100644 index 376f4fb0..00000000 --- a/keystore2/aidl/android/security/maintenance/UserState.aidl +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2021, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package android.security.maintenance; - -/** @hide */ -@Backing(type="int") -enum UserState { - UNINITIALIZED = 0, - LSKF_UNLOCKED = 1, - LSKF_LOCKED = 2, -}
\ No newline at end of file diff --git a/keystore2/aidl/android/security/metrics/AtomID.aidl b/keystore2/aidl/android/security/metrics/AtomID.aidl index 166e7538..3043ed3a 100644 --- a/keystore2/aidl/android/security/metrics/AtomID.aidl +++ b/keystore2/aidl/android/security/metrics/AtomID.aidl @@ -23,7 +23,7 @@ package android.security.metrics; @Backing(type="int") enum AtomID { STORAGE_STATS = 10103, - RKP_POOL_STATS = 10104, + // reserved 10104 KEY_CREATION_WITH_GENERAL_INFO = 10118, KEY_CREATION_WITH_AUTH_INFO = 10119, KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO = 10120, @@ -32,4 +32,4 @@ enum AtomID { KEY_OPERATION_WITH_GENERAL_INFO = 10123, RKP_ERROR_STATS = 10124, CRASH_STATS = 10125, -}
\ No newline at end of file +} diff --git a/keystore2/aidl/android/security/metrics/KeystoreAtom.aidl b/keystore2/aidl/android/security/metrics/KeystoreAtom.aidl index 266267ac..843e80b2 100644 --- a/keystore2/aidl/android/security/metrics/KeystoreAtom.aidl +++ b/keystore2/aidl/android/security/metrics/KeystoreAtom.aidl @@ -22,7 +22,7 @@ import android.security.metrics.KeystoreAtomPayload; * Encapsulates a particular atom object of type KeystoreAtomPayload its count. Note that * the field: count is only relevant for the atom types that are stored in the * in-memory metrics store. E.g. count field is not relevant for the atom types such as StorageStats - * and RkpPoolStats that are not stored in the metrics store. + * that are not stored in the metrics store. * @hide */ @RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) diff --git a/keystore2/aidl/android/security/metrics/KeystoreAtomPayload.aidl b/keystore2/aidl/android/security/metrics/KeystoreAtomPayload.aidl index a3e4dd68..2f89a2d1 100644 --- a/keystore2/aidl/android/security/metrics/KeystoreAtomPayload.aidl +++ b/keystore2/aidl/android/security/metrics/KeystoreAtomPayload.aidl @@ -24,14 +24,12 @@ import android.security.metrics.KeyOperationWithPurposeAndModesInfo; import android.security.metrics.StorageStats; import android.security.metrics.Keystore2AtomWithOverflow; import android.security.metrics.RkpErrorStats; -import android.security.metrics.RkpPoolStats; import android.security.metrics.CrashStats; /** @hide */ @RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) union KeystoreAtomPayload { StorageStats storageStats; - RkpPoolStats rkpPoolStats; KeyCreationWithGeneralInfo keyCreationWithGeneralInfo; KeyCreationWithAuthInfo keyCreationWithAuthInfo; KeyCreationWithPurposeAndModesInfo keyCreationWithPurposeAndModesInfo; @@ -40,4 +38,4 @@ union KeystoreAtomPayload { KeyOperationWithGeneralInfo keyOperationWithGeneralInfo; RkpErrorStats rkpErrorStats; CrashStats crashStats; -}
\ No newline at end of file +} diff --git a/keystore2/aidl/android/security/metrics/PoolStatus.aidl b/keystore2/aidl/android/security/metrics/PoolStatus.aidl deleted file mode 100644 index 35301639..00000000 --- a/keystore2/aidl/android/security/metrics/PoolStatus.aidl +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2021, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.metrics; - -/** - * Status of the remotely provisioned keys, as defined in RkpPoolStats of - * frameworks/proto_logging/stats/atoms.proto. - * @hide - */ -@Backing(type="int") -enum PoolStatus { - EXPIRING = 1, - UNASSIGNED = 2, - ATTESTED = 3, - TOTAL = 4, -}
\ No newline at end of file diff --git a/keystore2/aidl/android/security/metrics/RkpPoolStats.aidl b/keystore2/aidl/android/security/metrics/RkpPoolStats.aidl deleted file mode 100644 index 016b6ff3..00000000 --- a/keystore2/aidl/android/security/metrics/RkpPoolStats.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2021, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.metrics; - -import android.security.metrics.SecurityLevel; - -/** - * Count of keys in the attestation key pool related to Remote Key Provisioning (RKP). - * @hide - */ -@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) -parcelable RkpPoolStats { - SecurityLevel security_level; - int expiring; - int unassigned; - int attested; - int total; -}
\ No newline at end of file diff --git a/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl b/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl deleted file mode 100644 index 3528b423..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.remoteprovisioning; - -/** - * This parcelable provides information about the state of the attestation key pool. - * @hide - */ -parcelable AttestationPoolStatus { - /** - * The number of signed attestation certificate chains which will expire when the date provided - * to keystore to check against is reached. - */ - int expiring; - /** - * The number of signed attestation certificate chains which have not yet been assigned to an - * app. This should be less than or equal to signed keys. The remainder of `signed` - - * `unassigned` gives the number of signed keys that have been assigned to an app. - */ - int unassigned; - /** - * The number of signed attestation keys. This should be less than or equal to `total`. The - * remainder of `total` - `attested` gives the number of keypairs available to be sent off to - * the server for signing. - */ - int attested; - /** - * The total number of attestation keys. - */ - int total; -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl b/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl deleted file mode 100644 index ecdc7901..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.remoteprovisioning; - -import android.hardware.security.keymint.DeviceInfo; -import android.hardware.security.keymint.ProtectedData; -import android.hardware.security.keymint.SecurityLevel; -import android.security.remoteprovisioning.AttestationPoolStatus; -import android.security.remoteprovisioning.ImplInfo; - -/** - * `IRemoteProvisioning` is the interface provided to use the remote provisioning functionality - * provided through KeyStore. The intent is for a higher level system component to use these - * functions in order to drive the process through which the device can receive functioning - * attestation certificates. - * - * ## Error conditions - * Error conditions are reported as service specific errors. - * Positive codes correspond to `android.security.remoteprovisioning.ResponseCode` - * and indicate error conditions diagnosed by the Keystore 2.0 service. - * TODO: Remote Provisioning HAL error code info - * - * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permissions - * to use the RemoteProvisioning API. This permission is defined under access_vectors in SEPolicy - * in the keystore2 class: remotely_provision - * - * `ResponseCode::SYSTEM_ERROR` for any unexpected errors like IO or IPC failures. - * - * @hide - */ -interface IRemoteProvisioning { - - /** - * Returns the status of the attestation key pool in the database. - * - * @param expiredBy The date as seconds since epoch by which to judge expiration status of - * certificates. - * - * @param secLevel The security level to specify which KM instance to get the pool for. - * - * @return The `AttestationPoolStatus` parcelable contains fields communicating information - * relevant to making decisions about when to generate and provision - * more attestation keys. - */ - AttestationPoolStatus getPoolStatus(in long expiredBy, in SecurityLevel secLevel); - - /** - * This is the primary entry point for beginning a remote provisioning flow. The caller - * specifies how many CSRs should be generated and provides an X25519 ECDH public key along - * with a challenge to encrypt privacy sensitive portions of the returned CBOR blob and - * guarantee freshness of the request to the certifying third party. - * - * ## Error conditions - * `ResponseCode::NO_UNSIGNED_KEYS` if there are no unsigned keypairs in the database that can - * be used for the CSRs. - * - * A RemoteProvisioning HAL response code may indicate backend errors such as failed EEK - * verification. - * - * @param testMode Whether or not the TA implementing the Remote Provisioning HAL should accept - * any EEK (Endpoint Encryption Key), or only one signed by a chain - * that verifies back to the Root of Trust baked into the TA. True - * means that any key is accepted. - * - * @param numCsr How many certificate signing requests should be generated. - * - * @param eek A chain of certificates terminating in an X25519 public key, the Endpoint - * Encryption Key. - * - * @param challenge A challenge to be included and MACed in the returned CBOR blob. - * - * @param secLevel The security level to specify which KM instance from which to generate a - * CSR. - * - * @param protectedData The encrypted CBOR blob generated by the remote provisioner - * - * @return A CBOR blob composed of various elements required by the server to verify the - * request. - */ - byte[] generateCsr(in boolean testMode, in int numCsr, in byte[] eek, in byte[] challenge, - in SecurityLevel secLevel, out ProtectedData protectedData, out DeviceInfo deviceInfo); - - /** - * This method provides a way for the returned attestation certificate chains to be provisioned - * to the attestation key database. When an app requests an attesation key, it will be assigned - * one of these certificate chains along with the corresponding private key. - * - * @param publicKey The raw public key encoded in the leaf certificate. - * - * @param batchCert The batch certificate corresponding to the attestation key. Separated for - * the purpose of making Subject lookup for KM attestation easier. - * - * @param certs An X.509, DER encoded certificate chain for the attestation key. - * - * @param expirationDate The expiration date on the certificate chain, provided by the caller - * for convenience. - * - * @param secLevel The security level representing the KM instance containing the key that this - * chain corresponds to. - */ - void provisionCertChain(in byte[] publicKey, in byte[] batchCert, in byte[] certs, - in long expirationDate, in SecurityLevel secLevel); - - /** - * This method allows the caller to instruct KeyStore to generate and store a key pair to be - * used for attestation in the `generateCsr` method. The caller should handle spacing out these - * requests so as not to jam up the KeyStore work queue. - * - * @param is_test_mode Instructs the underlying HAL interface to mark the generated key with a - * tag to indicate that it's for testing. - * - * @param secLevel The security level to specify which KM instance should generate a key pair. - */ - void generateKeyPair(in boolean is_test_mode, in SecurityLevel secLevel); - - /** - * This method returns implementation information for whichever instances of - * IRemotelyProvisionedComponent are running on the device. The RemoteProvisioner app needs to - * know which KM instances it should be generating and managing attestation keys for, and which - * EC curves are supported in those instances. - * - * @return The array of ImplInfo parcelables. - */ - ImplInfo[] getImplementationInfo(); - - /** - * This method deletes all remotely provisioned attestation keys in the database, regardless - * of what state in their life cycle they are in. This is primarily useful to facilitate - * testing. - * - * @return Number of keys deleted - */ - long deleteAllKeys(); -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl b/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl deleted file mode 100644 index 7d45e52e..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.remoteprovisioning; - -import android.security.remoteprovisioning.RemotelyProvisionedKey; - -/** - * This is the interface providing access to remotely-provisioned attestation keys - * for an `IRemotelyProvisionedComponent`. - * - * @hide - */ -interface IRemotelyProvisionedKeyPool { - - /** - * Fetches an attestation key for the given uid and `IRemotelyProvisionedComponent`, as - * identified by the given id. - - * Callers require the keystore2::get_attestation_key permission. - * - * ## Error conditions - * `android.system.keystore2.ResponseCode::PERMISSION_DENIED` if the caller does not have the - * `keystore2::get_attestation_key` permission - * - * @param clientUid The client application for which an attestation key is needed. - * - * @param irpcId The unique identifier for the `IRemotelyProvisionedComponent` for which a key - * is requested. This id may be retrieved from a given component via the - * `IRemotelyProvisionedComponent::getHardwareInfo` function. - * - * @return A `RemotelyProvisionedKey` parcelable containing a key and certification chain for - * the given `IRemotelyProvisionedComponent`. - */ - RemotelyProvisionedKey getAttestationKey(in int clientUid, in @utf8InCpp String irpcId); -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/ImplInfo.aidl b/keystore2/aidl/android/security/remoteprovisioning/ImplInfo.aidl deleted file mode 100644 index 9baeb24b..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/ImplInfo.aidl +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.remoteprovisioning; - -import android.hardware.security.keymint.SecurityLevel; - -/** - * This parcelable provides information about the underlying IRemotelyProvisionedComponent - * implementation. - * @hide - */ -parcelable ImplInfo { - /** - * The security level of the underlying implementation: TEE or StrongBox. - */ - SecurityLevel secLevel; - /** - * An integer denoting which EC curve is supported in the underlying implementation. The current - * options are either P256 or 25519, with values defined in - * hardware/interfaces/security/keymint/aidl/.../RpcHardwareInfo.aidl - */ - int supportedCurve; -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl b/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl deleted file mode 100644 index ae218550..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.remoteprovisioning; - -/** - * A `RemotelyProvisionedKey` holds an attestation key and the corresponding remotely provisioned - * certificate chain. - * - * @hide - */ -@RustDerive(Eq=true, PartialEq=true) -parcelable RemotelyProvisionedKey { - /** - * The remotely-provisioned key that may be used to sign attestations. The format of this key - * is opaque, and need only be understood by the IRemotelyProvisionedComponent that generated - * it. - * - * Any private key material contained within this blob must be encrypted. - */ - byte[] keyBlob; - - /** - * Sequence of DER-encoded X.509 certificates that make up the attestation key's certificate - * chain. This is the binary encoding for a chain that is supported by Java's - * CertificateFactory.generateCertificates API. - */ - byte[] encodedCertChain; -} diff --git a/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl b/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl deleted file mode 100644 index c9877db5..00000000 --- a/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.remoteprovisioning; - -@Backing(type="int") -/** @hide */ -enum ResponseCode { - /** - * Returned if there are no keys available in the database to be used in a CSR - */ - NO_UNSIGNED_KEYS = 1, - /** - * The caller has imrproper SELinux permissions to access the Remote Provisioning API. - */ - PERMISSION_DENIED = 2, - /** - * An unexpected error occurred, likely with IO or IPC. - */ - SYSTEM_ERROR = 3, -} diff --git a/keystore2/android.system.keystore2-service.xml b/keystore2/android.system.keystore2-service.xml index 45f995c8..4d8a756d 100644 --- a/keystore2/android.system.keystore2-service.xml +++ b/keystore2/android.system.keystore2-service.xml @@ -1,7 +1,7 @@ <manifest version="1.0" type="framework"> <hal format="aidl"> <name>android.system.keystore2</name> - <version>3</version> + <version>4</version> <interface> <name>IKeystoreService</name> <instance>default</instance> diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp index 61697a85..9ca4612a 100644 --- a/keystore2/apc_compat/Android.bp +++ b/keystore2/apc_compat/Android.bp @@ -27,10 +27,10 @@ cc_library { "apc_compat.cpp", ], shared_libs: [ - "libbinder_ndk", - "android.hardware.confirmationui@1.0", "android.hardware.confirmationui-V1-ndk", + "android.hardware.confirmationui@1.0", "libbase", + "libbinder_ndk", "libhidlbase", "libutils", ], @@ -43,12 +43,12 @@ rust_bindgen { source_stem: "bindings", bindgen_flags: [ - "--allowlist-function=tryGetUserConfirmationService", - "--allowlist-function=promptUserConfirmation", "--allowlist-function=abortUserConfirmation", "--allowlist-function=closeUserConfirmationService", - "--allowlist-var=INVALID_SERVICE_HANDLE", + "--allowlist-function=promptUserConfirmation", + "--allowlist-function=tryGetUserConfirmationService", "--allowlist-var=APC_COMPAT_.*", + "--allowlist-var=INVALID_SERVICE_HANDLE", ], } diff --git a/keystore2/apc_compat/apc_compat.cpp b/keystore2/apc_compat/apc_compat.cpp index 9f60db2e..ffe7595e 100644 --- a/keystore2/apc_compat/apc_compat.cpp +++ b/keystore2/apc_compat/apc_compat.cpp @@ -118,8 +118,7 @@ class ConfuiHidlCompatSession : public HidlConfirmationResultCb, hidl_ui_options); if (!rc.isOk()) { LOG(ERROR) << "Communication error: promptUserConfirmation: " << rc.description(); - } - if (rc == ResponseCode::OK) { + } else if (rc == ResponseCode::OK) { callback_ = callback; } return responseCode2Compat(rc.withDefault(ResponseCode::SystemError)); diff --git a/keystore2/apc_compat/apc_compat.rs b/keystore2/apc_compat/apc_compat.rs index 480f14dd..e97ac59a 100644 --- a/keystore2/apc_compat/apc_compat.rs +++ b/keystore2/apc_compat/apc_compat.rs @@ -53,7 +53,10 @@ use std::{ffi::CString, slice}; /// ``` pub struct ApcHal(ApcCompatServiceHandle); +// SAFETY: This is a wrapper around `ApcCompatSession`, which can be used from any thread. unsafe impl Send for ApcHal {} +// SAFETY: `ApcCompatSession` can be called simultaneously from different threads because AIDL and +// HIDL are thread-safe. unsafe impl Sync for ApcHal {} impl Drop for ApcHal { @@ -120,6 +123,7 @@ impl ApcHal { // `closeUserConfirmationService` when dropped. let handle = unsafe { tryGetUserConfirmationService() }; match handle { + // SAFETY: This is just a constant. h if h == unsafe { INVALID_SERVICE_HANDLE } => None, h => Some(Self(h)), } diff --git a/keystore2/keystore2.rc b/keystore2/keystore2.rc index 6f88dd39..d7d6951c 100644 --- a/keystore2/keystore2.rc +++ b/keystore2/keystore2.rc @@ -11,3 +11,5 @@ service keystore2 /system/bin/keystore2 /data/misc/keystore user keystore group keystore readproc log task_profiles ProcessCapacityHigh + # The default memlock limit of 65536 bytes is too low for keystore. + rlimit memlock unlimited unlimited diff --git a/keystore2/legacykeystore/Android.bp b/keystore2/legacykeystore/Android.bp index 505b1653..de2edc20 100644 --- a/keystore2/legacykeystore/Android.bp +++ b/keystore2/legacykeystore/Android.bp @@ -31,6 +31,8 @@ rust_defaults { "android.security.legacykeystore-rust", "libanyhow", "libbinder_rs", + "libkeystore2_flags_rust", + "libkeystore2_flags_rust", "liblog_rust", "librusqlite", "librustutils", @@ -43,6 +45,7 @@ rust_library { defaults: ["liblegacykeystore-rust_defaults"], rustlibs: [ "libkeystore2", + "libkeystore2_flags_rust", "librusqlite", ], } @@ -58,6 +61,8 @@ rust_test { "libanyhow", "libbinder_rs", "libkeystore2", + "libkeystore2_flags_rust", + "libkeystore2_flags_rust", "libkeystore2_test_utils", "liblog_rust", "librusqlite", diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs index 464f0a29..db3eff63 100644 --- a/keystore2/legacykeystore/lib.rs +++ b/keystore2/legacykeystore/lib.rs @@ -29,9 +29,7 @@ use keystore2::{ legacy_blob::LegacyBlobLoader, maintenance::DeleteListener, maintenance::Domain, utils::uid_to_android_user, utils::watchdog as wd, }; -use rusqlite::{ - params, Connection, OptionalExtension, Transaction, TransactionBehavior, NO_PARAMS, -}; +use rusqlite::{params, Connection, OptionalExtension, Transaction, TransactionBehavior}; use std::sync::Arc; use std::{ collections::HashSet, @@ -57,7 +55,7 @@ impl DB { F: Fn(&Transaction) -> Result<T>, { loop { - match self + let result = self .conn .transaction_with_behavior(behavior) .context("In with_transaction.") @@ -65,7 +63,8 @@ impl DB { .and_then(|(result, tx)| { tx.commit().context("In with_transaction: Failed to commit transaction.")?; Ok(result) - }) { + }); + match result { Ok(result) => break Ok(result), Err(e) => { if Self::is_locked_error(&e) { @@ -95,7 +94,7 @@ impl DB { alias BLOB, profile BLOB, UNIQUE(owner, alias));", - NO_PARAMS, + [], ) .context("Failed to initialize \"profiles\" table.")?; Ok(()) @@ -123,6 +122,7 @@ impl DB { } fn put(&mut self, caller_uid: u32, alias: &str, entry: &[u8]) -> Result<()> { + ensure_keystore_put_is_enabled()?; self.with_transaction(TransactionBehavior::Immediate, |tx| { tx.execute( "INSERT OR REPLACE INTO profiles (owner, alias, profile) values (?, ?, ?)", @@ -203,6 +203,11 @@ impl Error { pub fn perm() -> Self { Error::Error(ERROR_PERMISSION_DENIED) } + + /// Short hand for `Error::Error(ERROR_SYSTEM_ERROR)` + pub fn deprecated() -> Self { + Error::Error(ERROR_SYSTEM_ERROR) + } } /// This function should be used by legacykeystore service calls to translate error conditions @@ -242,6 +247,17 @@ where ) } +fn ensure_keystore_put_is_enabled() -> Result<()> { + if keystore2_flags::disable_legacy_keystore_put_v2() { + Err(Error::deprecated()).context(concat!( + "Storing into Keystore's legacy database is ", + "no longer supported, store in an app-specific database instead" + )) + } else { + Ok(()) + } +} + struct LegacyKeystoreDeleteListener { legacy_keystore: Arc<LegacyKeystore>, } @@ -334,6 +350,7 @@ impl LegacyKeystore { } fn put(&self, alias: &str, uid: i32, entry: &[u8]) -> Result<()> { + ensure_keystore_put_is_enabled()?; let uid = Self::get_effective_uid(uid).context("In put.")?; let mut db = self.open_db().context("In put.")?; db.put(uid, alias, entry).context("In put: Trying to insert entry into DB.")?; @@ -502,8 +519,10 @@ impl LegacyKeystore { ) -> Result<bool> { let blob = legacy_loader .read_legacy_keystore_entry(uid, alias, |ciphertext, iv, tag, _salt, _key_size| { - if let Some(key) = - SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(uid)) + if let Some(key) = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(uid_to_android_user(uid)) { key.decrypt(ciphertext, iv, tag) } else { diff --git a/diced/open_dice_cbor/Android.bp b/keystore2/message_macro/Android.bp index a2534dcf..f1fbad76 100644 --- a/diced/open_dice_cbor/Android.bp +++ b/keystore2/message_macro/Android.bp @@ -1,4 +1,4 @@ -// Copyright 2021, The Android Open Source Project +// Copyright 2023, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,17 +13,25 @@ // 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: "diced_open_dice_cbor_test", - crate_name: "diced_open_dice_cbor_test", - srcs: ["lib.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "libdiced_open_dice", - "libdiced_sample_inputs", +rust_defaults { + name: "libmessage_macro_defaults", + crate_name: "message_macro", + srcs: ["src/lib.rs"], +} + +rust_library { + name: "libmessage_macro", + defaults: ["libmessage_macro_defaults"], + apex_available: [ + "//apex_available:platform", + "com.android.virt", ], } diff --git a/keystore2/src/ks_err.rs b/keystore2/message_macro/src/lib.rs index c9c38c0d..d8cfab0e 100644 --- a/keystore2/src/ks_err.rs +++ b/keystore2/message_macro/src/lib.rs @@ -12,20 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! A ks_err macro that expands error messages to include the file and line number +//! A macro that generates a message containing the current source file name +//! and line number. +/// Generates a message containing the current source file name and line number. /// /// # Examples /// /// ``` -/// use crate::ks_err; -/// -/// ks_err!("Key is expired."); +/// source_location_msg!("Key is expired."); /// Result: /// "src/lib.rs:7 Key is expired." /// ``` #[macro_export] -macro_rules! ks_err { +macro_rules! source_location_msg { { $($arg:tt)+ } => { format!("{}:{}: {}", file!(), line!(), format_args!($($arg)+)) }; diff --git a/diced/Android.bp b/keystore2/rkpd_client/Android.bp index 3e15ed71..d6a911fc 100644 --- a/diced/Android.bp +++ b/keystore2/rkpd_client/Android.bp @@ -1,4 +1,4 @@ -// Copyright 2021, The Android Open Source Project +// Copyright 2023, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,48 +21,35 @@ package { default_applicable_licenses: ["system_security_license"], } -rust_library { - name: "libdiced_utils", - crate_name: "diced_utils", - srcs: ["src/utils.rs"], - rustlibs: [ - "libanyhow", - ], -} - -rust_test { - name: "diced_utils_test", - crate_name: "diced_utils_test", - srcs: ["src/utils.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, +rust_defaults { + name: "librkpd_client_defaults", + crate_name: "rkpd_client", + srcs: ["src/lib.rs"], rustlibs: [ + "android.security.rkp_aidl-rust", "libanyhow", + "libbinder_rs", + "liblog_rust", + "libmessage_macro", + "libthiserror", + "libtokio", ], } rust_library { - name: "libdiced_sample_inputs", - crate_name: "diced_sample_inputs", - srcs: ["src/sample_inputs.rs"], - rustlibs: [ - "libanyhow", - "libciborium", - "libcoset", - "libdiced_open_dice", + name: "librkpd_client", + defaults: ["librkpd_client_defaults"], + apex_available: [ + "//apex_available:platform", + "com.android.virt", ], } rust_test { - name: "diced_sample_inputs_test", - crate_name: "diced_sample_inputs_test", - srcs: ["src/sample_inputs.rs"], + name: "librkpd_client.test", + defaults: ["librkpd_client_defaults"], test_suites: ["general-tests"], - auto_gen_config: true, rustlibs: [ - "libanyhow", - "libciborium", - "libcoset", - "libdiced_open_dice", + "librand", ], } diff --git a/keystore2/src/rkpd_client.rs b/keystore2/rkpd_client/src/lib.rs index 0ea2d392..d8a5276c 100644 --- a/keystore2/src/rkpd_client.rs +++ b/keystore2/rkpd_client/src/lib.rs @@ -14,11 +14,6 @@ //! Helper wrapper around RKPD interface. -use crate::error::{map_binder_status_code, Error, ResponseCode}; -use crate::globals::get_remotely_provisioned_component_name; -use crate::ks_err; -use crate::utils::watchdog as wd; -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel; use android_security_rkp_aidl::aidl::android::security::rkp::{ IGetKeyCallback::BnGetKeyCallback, IGetKeyCallback::ErrorCode::ErrorCode as GetKeyErrorCode, IGetKeyCallback::IGetKeyCallback, IGetRegistrationCallback::BnGetRegistrationCallback, @@ -28,8 +23,9 @@ use android_security_rkp_aidl::aidl::android::security::rkp::{ IStoreUpgradedKeyCallback::IStoreUpgradedKeyCallback, RemotelyProvisionedKey::RemotelyProvisionedKey, }; -use android_security_rkp_aidl::binder::{BinderFeatures, Interface, Strong}; use anyhow::{Context, Result}; +use binder::{BinderFeatures, Interface, StatusCode, Strong}; +use message_macro::source_location_msg; use std::sync::Mutex; use std::time::Duration; use tokio::sync::oneshot; @@ -44,6 +40,44 @@ fn tokio_rt() -> tokio::runtime::Runtime { tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap() } +/// Errors occurred during the interaction with RKPD. +#[derive(Debug, Clone, Copy, thiserror::Error, PartialEq, Eq)] +pub enum Error { + /// An RKPD request gets cancelled. + #[error("An RKPD request gets cancelled")] + RequestCancelled, + + /// Failed to get registration. + #[error("Failed to get registration")] + GetRegistrationFailed, + + /// Failed to get key. + #[error("Failed to get key: {0:?}")] + GetKeyFailed(GetKeyErrorCode), + + /// Failed to store upgraded key. + #[error("Failed to store upgraded key")] + StoreUpgradedKeyFailed, + + /// Retryable timeout when waiting for a callback. + #[error("Retryable timeout when waiting for a callback")] + RetryableTimeout, + + /// Timeout when waiting for a callback. + #[error("Timeout when waiting for a callback")] + Timeout, + + /// Wraps a Binder status code. + #[error("Binder transaction error {0:?}")] + BinderTransaction(StatusCode), +} + +impl From<StatusCode> for Error { + fn from(s: StatusCode) -> Self { + Self::BinderTransaction(s) + } +} + /// Thread-safe channel for sending a value once and only once. If a value has /// already been send, subsequent calls to send will noop. struct SafeSender<T> { @@ -84,52 +118,43 @@ impl Interface for GetRegistrationCallback {} impl IGetRegistrationCallback for GetRegistrationCallback { fn onSuccess(&self, registration: &Strong<dyn IRegistration>) -> binder::Result<()> { - let _wp = wd::watch_millis("IGetRegistrationCallback::onSuccess", 500); self.registration_tx.send(Ok(registration.clone())); Ok(()) } fn onCancel(&self) -> binder::Result<()> { - let _wp = wd::watch_millis("IGetRegistrationCallback::onCancel", 500); log::warn!("IGetRegistrationCallback cancelled"); self.registration_tx.send( - Err(Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) - .context(ks_err!("GetRegistrationCallback cancelled.")), + Err(Error::RequestCancelled) + .context(source_location_msg!("GetRegistrationCallback cancelled.")), ); Ok(()) } fn onError(&self, description: &str) -> binder::Result<()> { - let _wp = wd::watch_millis("IGetRegistrationCallback::onError", 500); log::error!("IGetRegistrationCallback failed: '{description}'"); self.registration_tx.send( - Err(Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) - .context(ks_err!("GetRegistrationCallback failed: {:?}", description)), + Err(Error::GetRegistrationFailed) + .context(source_location_msg!("GetRegistrationCallback failed: {:?}", description)), ); Ok(()) } } /// Make a new connection to a IRegistration service. -async fn get_rkpd_registration( - security_level: &SecurityLevel, -) -> Result<binder::Strong<dyn IRegistration>> { +async fn get_rkpd_registration(rpc_name: &str) -> Result<binder::Strong<dyn IRegistration>> { let remote_provisioning: Strong<dyn IRemoteProvisioning> = - map_binder_status_code(binder::get_interface("remote_provisioning")) - .context(ks_err!("Trying to connect to IRemoteProvisioning service."))?; - - let rpc_name = get_remotely_provisioned_component_name(security_level) - .context(ks_err!("Trying to get IRPC name."))?; + binder::get_interface("remote_provisioning") + .map_err(Error::from) + .context(source_location_msg!("Trying to connect to IRemoteProvisioning service."))?; let (tx, rx) = oneshot::channel(); let cb = GetRegistrationCallback::new_native_binder(tx); remote_provisioning - .getRegistration(&rpc_name, &cb) - .context(ks_err!("Trying to get registration."))?; + .getRegistration(rpc_name, &cb) + .context(source_location_msg!("Trying to get registration."))?; match timeout(RKPD_TIMEOUT, rx).await { - Err(e) => { - Err(Error::Rc(ResponseCode::SYSTEM_ERROR)).context(ks_err!("Waiting for RKPD: {:?}", e)) - } + Err(e) => Err(Error::Timeout).context(source_location_msg!("Waiting for RKPD: {:?}", e)), Ok(v) => v.unwrap(), } } @@ -151,7 +176,6 @@ impl Interface for GetKeyCallback {} impl IGetKeyCallback for GetKeyCallback { fn onSuccess(&self, key: &RemotelyProvisionedKey) -> binder::Result<()> { - let _wp = wd::watch_millis("IGetKeyCallback::onSuccess", 500); self.key_tx.send(Ok(RemotelyProvisionedKey { keyBlob: key.keyBlob.clone(), encodedCertChain: key.encodedCertChain.clone(), @@ -159,32 +183,15 @@ impl IGetKeyCallback for GetKeyCallback { Ok(()) } fn onCancel(&self) -> binder::Result<()> { - let _wp = wd::watch_millis("IGetKeyCallback::onCancel", 500); log::warn!("IGetKeyCallback cancelled"); self.key_tx.send( - Err(Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) - .context(ks_err!("GetKeyCallback cancelled.")), + Err(Error::RequestCancelled).context(source_location_msg!("GetKeyCallback cancelled.")), ); Ok(()) } fn onError(&self, error: GetKeyErrorCode, description: &str) -> binder::Result<()> { - let _wp = wd::watch_millis("IGetKeyCallback::onError", 500); log::error!("IGetKeyCallback failed: {description}"); - let rc = match error { - GetKeyErrorCode::ERROR_UNKNOWN => ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR, - GetKeyErrorCode::ERROR_PERMANENT => ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR, - GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY => { - ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY - } - GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH => { - ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE - } - _ => { - log::error!("Unexpected error from rkpd: {error:?}"); - ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR - } - }; - self.key_tx.send(Err(Error::Rc(rc)).context(ks_err!( + self.key_tx.send(Err(Error::GetKeyFailed(error)).context(source_location_msg!( "GetKeyCallback failed: {:?} {:?}", error, description @@ -202,7 +209,7 @@ async fn get_rkpd_attestation_key_from_registration_async( registration .getKey(caller_uid.try_into().unwrap(), &cb) - .context(ks_err!("Trying to get key."))?; + .context(source_location_msg!("Trying to get key."))?; match timeout(RKPD_TIMEOUT, rx).await { Err(e) => { @@ -210,20 +217,20 @@ async fn get_rkpd_attestation_key_from_registration_async( if let Err(e) = registration.cancelGetKey(&cb) { log::error!("IRegistration::cancelGetKey failed: {:?}", e); } - Err(Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) - .context(ks_err!("Waiting for RKPD key timed out: {:?}", e)) + Err(Error::RetryableTimeout) + .context(source_location_msg!("Waiting for RKPD key timed out: {:?}", e)) } Ok(v) => v.unwrap(), } } async fn get_rkpd_attestation_key_async( - security_level: &SecurityLevel, + rpc_name: &str, caller_uid: u32, ) -> Result<RemotelyProvisionedKey> { - let registration = get_rkpd_registration(security_level) + let registration = get_rkpd_registration(rpc_name) .await - .context(ks_err!("Trying to get to IRegistration service."))?; + .context(source_location_msg!("Trying to get to IRegistration service."))?; get_rkpd_attestation_key_from_registration_async(®istration, caller_uid).await } @@ -244,17 +251,15 @@ impl Interface for StoreUpgradedKeyCallback {} impl IStoreUpgradedKeyCallback for StoreUpgradedKeyCallback { fn onSuccess(&self) -> binder::Result<()> { - let _wp = wd::watch_millis("IGetRegistrationCallback::onSuccess", 500); self.completer.send(Ok(())); Ok(()) } fn onError(&self, error: &str) -> binder::Result<()> { - let _wp = wd::watch_millis("IGetRegistrationCallback::onError", 500); - log::error!("IGetRegistrationCallback failed: {error}"); + log::error!("IStoreUpgradedKeyCallback failed: {error}"); self.completer.send( - Err(Error::Rc(ResponseCode::SYSTEM_ERROR)) - .context(ks_err!("Failed to store upgraded key: {:?}", error)), + Err(Error::StoreUpgradedKeyFailed) + .context(source_location_msg!("Failed to store upgraded key: {:?}", error)), ); Ok(()) } @@ -270,61 +275,50 @@ async fn store_rkpd_attestation_key_with_registration_async( registration .storeUpgradedKeyAsync(key_blob, upgraded_blob, &cb) - .context(ks_err!("Failed to store upgraded blob with RKPD."))?; + .context(source_location_msg!("Failed to store upgraded blob with RKPD."))?; match timeout(RKPD_TIMEOUT, rx).await { - Err(e) => Err(Error::Rc(ResponseCode::SYSTEM_ERROR)) - .context(ks_err!("Waiting for RKPD to complete storing key: {:?}", e)), + Err(e) => Err(Error::Timeout) + .context(source_location_msg!("Waiting for RKPD to complete storing key: {:?}", e)), Ok(v) => v.unwrap(), } } async fn store_rkpd_attestation_key_async( - security_level: &SecurityLevel, + rpc_name: &str, key_blob: &[u8], upgraded_blob: &[u8], ) -> Result<()> { - let registration = get_rkpd_registration(security_level) + let registration = get_rkpd_registration(rpc_name) .await - .context(ks_err!("Trying to get to IRegistration service."))?; + .context(source_location_msg!("Trying to get to IRegistration service."))?; store_rkpd_attestation_key_with_registration_async(®istration, key_blob, upgraded_blob).await } /// Get attestation key from RKPD. -pub fn get_rkpd_attestation_key( - security_level: &SecurityLevel, - caller_uid: u32, -) -> Result<RemotelyProvisionedKey> { - let _wp = wd::watch_millis("Calling get_rkpd_attestation_key()", 500); - tokio_rt().block_on(get_rkpd_attestation_key_async(security_level, caller_uid)) +pub fn get_rkpd_attestation_key(rpc_name: &str, caller_uid: u32) -> Result<RemotelyProvisionedKey> { + tokio_rt().block_on(get_rkpd_attestation_key_async(rpc_name, caller_uid)) } /// Store attestation key in RKPD. pub fn store_rkpd_attestation_key( - security_level: &SecurityLevel, + rpc_name: &str, key_blob: &[u8], upgraded_blob: &[u8], ) -> Result<()> { - let _wp = wd::watch_millis("Calling store_rkpd_attestation_key()", 500); - tokio_rt().block_on(store_rkpd_attestation_key_async(security_level, key_blob, upgraded_blob)) + tokio_rt().block_on(store_rkpd_attestation_key_async(rpc_name, key_blob, upgraded_blob)) } #[cfg(test)] mod tests { use super::*; - use crate::error::map_km_error; - use crate::globals::get_keymint_device; - use crate::utils::upgrade_keyblob_if_required_with; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Algorithm::Algorithm, AttestationKey::AttestationKey, KeyParameter::KeyParameter, - KeyParameterValue::KeyParameterValue, Tag::Tag, - }; use android_security_rkp_aidl::aidl::android::security::rkp::IRegistration::BnRegistration; - use keystore2_crypto::parse_subject_from_certificate; - use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; + const DEFAULT_RPC_SERVICE_NAME: &str = + "android.hardware.security.keymint.IRemotelyProvisionedComponent/default"; + struct MockRegistrationValues { key: RemotelyProvisionedKey, latency: Option<Duration>, @@ -442,10 +436,7 @@ mod tests { assert!(cb.onCancel().is_ok()); let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!( - result.unwrap_err().downcast::<Error>().unwrap(), - Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) - ); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled); } #[test] @@ -455,10 +446,7 @@ mod tests { assert!(cb.onError("error").is_ok()); let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!( - result.unwrap_err().downcast::<Error>().unwrap(), - Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) - ); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::GetRegistrationFailed); } #[test] @@ -480,29 +468,11 @@ mod tests { assert!(cb.onCancel().is_ok()); let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!( - result.unwrap_err().downcast::<Error>().unwrap(), - Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) - ); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled); } #[test] fn test_get_key_cb_error() { - let error_mapping = HashMap::from([ - (GetKeyErrorCode::ERROR_UNKNOWN, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), - (GetKeyErrorCode::ERROR_PERMANENT, ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR), - ( - GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY, - ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY, - ), - ( - GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH, - ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE, - ), - ]); - - // Loop over the generated list of enum values to better ensure this test stays in - // sync with the AIDL. for get_key_error in GetKeyErrorCode::enum_values() { let (tx, rx) = oneshot::channel(); let cb = GetKeyCallback::new_native_binder(tx); @@ -511,7 +481,7 @@ mod tests { let result = tokio_rt().block_on(rx).unwrap(); assert_eq!( result.unwrap_err().downcast::<Error>().unwrap(), - Error::Rc(error_mapping[&get_key_error]), + Error::GetKeyFailed(get_key_error), ); } } @@ -532,10 +502,7 @@ mod tests { assert!(cb.onError("oh no! it failed").is_ok()); let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!( - result.unwrap_err().downcast::<Error>().unwrap(), - Error::Rc(ResponseCode::SYSTEM_ERROR) - ); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::StoreUpgradedKeyFailed); } #[test] @@ -559,10 +526,7 @@ mod tests { let result = tokio_rt().block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0)); - assert_eq!( - result.unwrap_err().downcast::<Error>().unwrap(), - Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) - ); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RetryableTimeout); } #[test] @@ -587,17 +551,14 @@ mod tests { &[], &[], )); - assert_eq!( - result.unwrap_err().downcast::<Error>().unwrap(), - Error::Rc(ResponseCode::SYSTEM_ERROR) - ); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::Timeout); } #[test] fn test_get_rkpd_attestation_key() { binder::ProcessState::start_thread_pool(); let key_id = get_next_key_id(); - let key = get_rkpd_attestation_key(&SecurityLevel::TRUSTED_ENVIRONMENT, key_id).unwrap(); + let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); assert!(!key.keyBlob.is_empty()); assert!(!key.encodedCertChain.is_empty()); } @@ -605,12 +566,11 @@ mod tests { #[test] fn test_get_rkpd_attestation_key_same_caller() { binder::ProcessState::start_thread_pool(); - let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; let key_id = get_next_key_id(); // Multiple calls should return the same key. - let first_key = get_rkpd_attestation_key(&sec_level, key_id).unwrap(); - let second_key = get_rkpd_attestation_key(&sec_level, key_id).unwrap(); + let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); + let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); assert_eq!(first_key.keyBlob, second_key.keyBlob); assert_eq!(first_key.encodedCertChain, second_key.encodedCertChain); @@ -619,13 +579,12 @@ mod tests { #[test] fn test_get_rkpd_attestation_key_different_caller() { binder::ProcessState::start_thread_pool(); - let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; let first_key_id = get_next_key_id(); let second_key_id = get_next_key_id(); // Different callers should be getting different keys. - let first_key = get_rkpd_attestation_key(&sec_level, first_key_id).unwrap(); - let second_key = get_rkpd_attestation_key(&sec_level, second_key_id).unwrap(); + let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, first_key_id).unwrap(); + let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, second_key_id).unwrap(); assert_ne!(first_key.keyBlob, second_key.keyBlob); assert_ne!(first_key.encodedCertChain, second_key.encodedCertChain); @@ -639,81 +598,24 @@ mod tests { // test case. fn test_store_rkpd_attestation_key() { binder::ProcessState::start_thread_pool(); - let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; let key_id = get_next_key_id(); - let key = get_rkpd_attestation_key(&SecurityLevel::TRUSTED_ENVIRONMENT, key_id).unwrap(); + let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); let new_blob: [u8; 8] = rand::random(); - assert!(store_rkpd_attestation_key(&sec_level, &key.keyBlob, &new_blob).is_ok()); + assert!( + store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &key.keyBlob, &new_blob).is_ok() + ); - let new_key = - get_rkpd_attestation_key(&SecurityLevel::TRUSTED_ENVIRONMENT, key_id).unwrap(); + let new_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); // Restore original key so that we don't leave RKPD with invalid blobs. - assert!(store_rkpd_attestation_key(&sec_level, &new_blob, &key.keyBlob).is_ok()); + assert!( + store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &new_blob, &key.keyBlob).is_ok() + ); assert_eq!(new_key.keyBlob, new_blob); } #[test] - // This is a helper for a manual test. We want to check that after a system upgrade RKPD - // attestation keys can also be upgraded and stored again with RKPD. The steps are: - // 1. Run this test and check in stdout that no key upgrade happened. - // 2. Perform a system upgrade. - // 3. Run this test and check in stdout that key upgrade did happen. - // - // Note that this test must be run with that same UID every time. Running as root, i.e. UID 0, - // should do the trick. Also, use "--nocapture" flag to get stdout. - fn test_rkpd_attestation_key_upgrade() { - binder::ProcessState::start_thread_pool(); - let security_level = SecurityLevel::TRUSTED_ENVIRONMENT; - let (keymint, _, _) = get_keymint_device(&security_level).unwrap(); - let key_id = get_next_key_id(); - let mut key_upgraded = false; - - let key = get_rkpd_attestation_key(&security_level, key_id).unwrap(); - assert!(!key.keyBlob.is_empty()); - assert!(!key.encodedCertChain.is_empty()); - - upgrade_keyblob_if_required_with( - &*keymint, - &key.keyBlob, - /*upgrade_params=*/ &[], - /*km_op=*/ - |blob| { - let params = vec![ - KeyParameter { - tag: Tag::ALGORITHM, - value: KeyParameterValue::Algorithm(Algorithm::AES), - }, - KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) }, - ]; - let attestation_key = AttestationKey { - keyBlob: blob.to_vec(), - attestKeyParams: vec![], - issuerSubjectName: parse_subject_from_certificate(&key.encodedCertChain) - .unwrap(), - }; - - map_km_error(keymint.generateKey(¶ms, Some(&attestation_key))) - }, - /*new_blob_handler=*/ - |new_blob| { - // This handler is only executed if a key upgrade was performed. - key_upgraded = true; - store_rkpd_attestation_key(&security_level, &key.keyBlob, new_blob).unwrap(); - Ok(()) - }, - ) - .unwrap(); - - if key_upgraded { - println!("RKPD key was upgraded and stored with RKPD."); - } else { - println!("RKPD key was NOT upgraded."); - } - } - - #[test] fn test_stress_get_rkpd_attestation_key() { binder::ProcessState::start_thread_pool(); let key_id = get_next_key_id(); @@ -724,8 +626,7 @@ mod tests { for _ in 0..NTHREADS { threads.push(std::thread::spawn(move || { for _ in 0..NCALLS { - let key = get_rkpd_attestation_key(&SecurityLevel::TRUSTED_ENVIRONMENT, key_id) - .unwrap(); + let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); assert!(!key.keyBlob.is_empty()); assert!(!key.encodedCertChain.is_empty()); } diff --git a/keystore2/selinux/src/concurrency_test.rs b/keystore2/selinux/src/concurrency_test.rs index a5d2df2c..fa97f3aa 100644 --- a/keystore2/selinux/src/concurrency_test.rs +++ b/keystore2/selinux/src/concurrency_test.rs @@ -69,7 +69,7 @@ fn test_concurrent_check_access() { android_logger::init_once( android_logger::Config::default() .with_tag("keystore2_selinux_concurrency_test") - .with_min_level(log::Level::Debug), + .with_max_level(log::LevelFilter::Debug), ); let cpus = num_cpus::get(); diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs index e5c3091b..695e0291 100644 --- a/keystore2/selinux/src/lib.rs +++ b/keystore2/selinux/src/lib.rs @@ -20,6 +20,9 @@ //! * selabel_lookup for the keystore2_key backend. //! And it provides an owning wrapper around context strings `Context`. +// TODO(b/290018030): Remove this and add proper safety comments. +#![allow(clippy::undocumented_unsafe_blocks)] + use anyhow::Context as AnyhowContext; use anyhow::{anyhow, Result}; use lazy_static::lazy_static; @@ -160,8 +163,9 @@ pub struct KeystoreKeyBackend { handle: *mut selinux::selabel_handle, } -// KeystoreKeyBackend is Sync because selabel_lookup is thread safe. +// SAFETY: KeystoreKeyBackend is Sync because selabel_lookup is thread safe. unsafe impl Sync for KeystoreKeyBackend {} +// SAFETY: KeystoreKeyBackend is Send because selabel_lookup is thread safe. unsafe impl Send for KeystoreKeyBackend {} impl KeystoreKeyBackend { @@ -716,7 +720,7 @@ mod tests { android_logger::init_once( android_logger::Config::default() .with_tag("keystore_selinux_tests") - .with_min_level(log::Level::Debug), + .with_max_level(log::LevelFilter::Debug), ); let scontext = Context::new("u:r:shell:s0")?; let backend = KeystoreKeyBackend::new()?; diff --git a/keystore2/src/apc.rs b/keystore2/src/apc.rs index 5d2083da..fbf94649 100644 --- a/keystore2/src/apc.rs +++ b/keystore2/src/apc.rs @@ -244,7 +244,7 @@ impl ApcManager { // If cancelled by the user or if aborted by the client. (ResponseCode::CANCELLED, _, _) | (ResponseCode::ABORTED, true, _) => { // Penalize. - let mut rate_info = state.rate_limiting.entry(uid).or_default(); + let rate_info = state.rate_limiting.entry(uid).or_default(); rate_info.counter += 1; rate_info.timestamp = start; } diff --git a/keystore2/src/attestation_key_utils.rs b/keystore2/src/attestation_key_utils.rs index 8c4cdea7..184b3cbd 100644 --- a/keystore2/src/attestation_key_utils.rs +++ b/keystore2/src/attestation_key_utils.rs @@ -30,17 +30,11 @@ use android_system_keystore2::aidl::android::system::keystore2::{ }; use anyhow::{Context, Result}; use keystore2_crypto::parse_subject_from_certificate; -use rustutils::system_properties; /// KeyMint takes two different kinds of attestation keys. Remote provisioned keys /// and those that have been generated by the user. Unfortunately, they need to be /// handled quite differently, thus the different representations. pub enum AttestationKeyInfo { - RemoteProvisioned { - key_id_guard: KeyIdGuard, - attestation_key: AttestationKey, - attestation_certs: Certificate, - }, RkpdProvisioned { attestation_key: AttestationKey, attestation_certs: Certificate, @@ -53,12 +47,6 @@ pub enum AttestationKeyInfo { }, } -fn use_rkpd() -> bool { - let property = "remote_provisioning.enable_rkpd"; - let default_value = true; - system_properties::read_bool(property, default_value).unwrap_or(default_value) -} - /// This function loads and, optionally, assigns the caller's remote provisioned /// attestation key if a challenge is present. Alternatively, if `attest_key_descriptor` is given, /// it loads the user generated attestation key from the database. @@ -75,34 +63,14 @@ pub fn get_attest_key_info( params.iter().any(|kp| kp.tag == Tag::DEVICE_UNIQUE_ATTESTATION); match attest_key_descriptor { // Do not select an RKP key if DEVICE_UNIQUE_ATTESTATION is present. - None if challenge_present && !is_device_unique_attestation => { - if use_rkpd() { - rem_prov_state - .get_rkpd_attestation_key_and_certs(key, caller_uid, params) - .context(ks_err!("Trying to get attestation key from RKPD.")) - .map(|result| { - result.map(|(attestation_key, attestation_certs)| { - AttestationKeyInfo::RkpdProvisioned { - attestation_key, - attestation_certs, - } - }) - }) - } else { - rem_prov_state - .get_remotely_provisioned_attestation_key_and_certs(key, caller_uid, params, db) - .context(ks_err!("Trying to get remotely provisioned attestation key.")) - .map(|result| { - result.map(|(key_id_guard, attestation_key, attestation_certs)| { - AttestationKeyInfo::RemoteProvisioned { - key_id_guard, - attestation_key, - attestation_certs, - } - }) - }) - } - } + None if challenge_present && !is_device_unique_attestation => rem_prov_state + .get_rkpd_attestation_key_and_certs(key, caller_uid, params) + .context(ks_err!("Trying to get attestation key from RKPD.")) + .map(|result| { + result.map(|(attestation_key, attestation_certs)| { + AttestationKeyInfo::RkpdProvisioned { attestation_key, attestation_certs } + }) + }), None => Ok(None), Some(attest_key) => get_user_generated_attestation_key(attest_key, caller_uid, db) .context(ks_err!("Trying to load attest key")) diff --git a/keystore2/src/audit_log.rs b/keystore2/src/audit_log.rs index 07509d36..8d9735e2 100644 --- a/keystore2/src/audit_log.rs +++ b/keystore2/src/audit_log.rs @@ -20,7 +20,7 @@ use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, }; use libc::uid_t; -use log_event_list::{LogContext, LogIdSecurity}; +use structured_log::{structured_log, LOG_ID_SECURITY}; const TAG_KEY_GENERATED: u32 = 210024; const TAG_KEY_IMPORTED: u32 = 210025; @@ -58,29 +58,19 @@ pub fn log_key_deleted(key: &KeyDescriptor, calling_app: uid_t, success: bool) { /// Logs key integrity violation to NIAP audit log. pub fn log_key_integrity_violation(key: &KeyDescriptor) { - with_log_context(TAG_KEY_INTEGRITY_VIOLATION, |ctx| { - let owner = key_owner(key.domain, key.nspace, key.nspace as i32); - ctx.append_str(key.alias.as_ref().map_or("none", String::as_str)).append_i32(owner) - }) + let owner = key_owner(key.domain, key.nspace, key.nspace as i32); + let alias = String::from(key.alias.as_ref().map_or("none", String::as_str)); + LOGS_HANDLER.queue_lo(move |_| { + let _result = + structured_log!(log_id: LOG_ID_SECURITY, TAG_KEY_INTEGRITY_VIOLATION, alias, owner); + }); } fn log_key_event(tag: u32, key: &KeyDescriptor, calling_app: uid_t, success: bool) { - with_log_context(tag, |ctx| { - let owner = key_owner(key.domain, key.nspace, calling_app as i32); - ctx.append_i32(i32::from(success)) - .append_str(key.alias.as_ref().map_or("none", String::as_str)) - .append_i32(owner) - }) -} - -fn with_log_context<F>(tag: u32, f: F) -where - F: Fn(LogContext) -> LogContext, -{ - if let Some(ctx) = LogContext::new(LogIdSecurity, tag) { - let event = f(ctx); - LOGS_HANDLER.queue_lo(move |_| { - event.write(); - }); - } + let owner = key_owner(key.domain, key.nspace, calling_app as i32); + let alias = String::from(key.alias.as_ref().map_or("none", String::as_str)); + LOGS_HANDLER.queue_lo(move |_| { + let _result = + structured_log!(log_id: LOG_ID_SECURITY, tag, i32::from(success), alias, owner); + }); } diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs index 19539201..243abf13 100644 --- a/keystore2/src/authorization.rs +++ b/keystore2/src/authorization.rs @@ -14,28 +14,29 @@ //! This module implements IKeystoreAuthorization AIDL interface. -use crate::ks_err; -use crate::error::Error as KeystoreError; use crate::error::anyhow_error_to_cstring; -use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_IMPORTER}; +use crate::error::Error as KeystoreError; +use crate::globals::{DB, ENFORCEMENTS, LEGACY_IMPORTER, SUPER_KEY}; +use crate::ks_err; use crate::permission::KeystorePerm; -use crate::super_key::UserState; use crate::utils::{check_keystore_permission, watchdog as wd}; +use aconfig_android_hardware_biometrics_rust; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - HardwareAuthToken::HardwareAuthToken, + HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType, }; -use android_security_authorization::binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, - Strong, Status as BinderStatus}; use android_security_authorization::aidl::android::security::authorization::{ - IKeystoreAuthorization::BnKeystoreAuthorization, IKeystoreAuthorization::IKeystoreAuthorization, - LockScreenEvent::LockScreenEvent, AuthorizationTokens::AuthorizationTokens, - ResponseCode::ResponseCode, + AuthorizationTokens::AuthorizationTokens, IKeystoreAuthorization::BnKeystoreAuthorization, + IKeystoreAuthorization::IKeystoreAuthorization, ResponseCode::ResponseCode, }; -use android_system_keystore2::aidl::android::system::keystore2::{ - ResponseCode::ResponseCode as KsResponseCode}; +use android_security_authorization::binder::{ + BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status as BinderStatus, + Strong, +}; +use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode as KsResponseCode; use anyhow::{Context, Result}; use keystore2_crypto::Password; use keystore2_selinux as selinux; +use std::ffi::CString; /// This is the Authorization error type, it wraps binder exceptions and the /// Authorization ResponseCode @@ -127,92 +128,94 @@ impl AuthorizationManager { fn add_auth_token(&self, auth_token: &HardwareAuthToken) -> Result<()> { // Check keystore permission. - check_keystore_permission(KeystorePerm::AddAuth).context(ks_err!())?; + check_keystore_permission(KeystorePerm::AddAuth) + .context(ks_err!("caller missing AddAuth permissions"))?; + + log::info!( + "add_auth_token(challenge={}, userId={}, authId={}, authType={:#x}, timestamp={}ms)", + auth_token.challenge, + auth_token.userId, + auth_token.authenticatorId, + auth_token.authenticatorType.0, + auth_token.timestamp.milliSeconds, + ); ENFORCEMENTS.add_auth_token(auth_token.clone()); Ok(()) } - fn on_lock_screen_event( - &self, - lock_screen_event: LockScreenEvent, - user_id: i32, - password: Option<Password>, - unlocking_sids: Option<&[i64]>, - ) -> Result<()> { + fn on_device_unlocked(&self, user_id: i32, password: Option<Password>) -> Result<()> { log::info!( - "on_lock_screen_event({:?}, user_id={:?}, password.is_some()={}, unlocking_sids={:?})", - lock_screen_event, + "on_device_unlocked(user_id={}, password.is_some()={})", user_id, password.is_some(), - unlocking_sids ); - match (lock_screen_event, password) { - (LockScreenEvent::UNLOCK, Some(password)) => { - // This corresponds to the unlock() method in legacy keystore API. - // check permission - check_keystore_permission(KeystorePerm::Unlock) - .context(ks_err!("Unlock with password."))?; - ENFORCEMENTS.set_device_locked(user_id, false); + check_keystore_permission(KeystorePerm::Unlock) + .context(ks_err!("caller missing Unlock permissions"))?; + ENFORCEMENTS.set_device_locked(user_id, false); - let mut skm = SUPER_KEY.write().unwrap(); + let mut skm = SUPER_KEY.write().unwrap(); + if let Some(password) = password { + DB.with(|db| { + skm.unlock_user(&mut db.borrow_mut(), &LEGACY_IMPORTER, user_id as u32, &password) + }) + .context(ks_err!("Unlock with password.")) + } else { + DB.with(|db| skm.try_unlock_user_with_biometric(&mut db.borrow_mut(), user_id as u32)) + .context(ks_err!("try_unlock_user_with_biometric failed user_id={user_id}")) + } + } - DB.with(|db| { - skm.unlock_screen_lock_bound_key( - &mut db.borrow_mut(), - user_id as u32, - &password, - ) - }) - .context(ks_err!("unlock_screen_lock_bound_key failed"))?; + fn on_device_locked( + &self, + user_id: i32, + unlocking_sids: &[i64], + mut weak_unlock_enabled: bool, + ) -> Result<()> { + log::info!( + "on_device_locked(user_id={}, unlocking_sids={:?}, weak_unlock_enabled={})", + user_id, + unlocking_sids, + weak_unlock_enabled + ); + if !android_security_flags::fix_unlocked_device_required_keys_v2() { + weak_unlock_enabled = false; + } + check_keystore_permission(KeystorePerm::Lock) + .context(ks_err!("caller missing Lock permission"))?; + ENFORCEMENTS.set_device_locked(user_id, true); + let mut skm = SUPER_KEY.write().unwrap(); + DB.with(|db| { + skm.lock_unlocked_device_required_keys( + &mut db.borrow_mut(), + user_id as u32, + unlocking_sids, + weak_unlock_enabled, + ); + }); + Ok(()) + } - // Unlock super key. - if let UserState::Uninitialized = DB - .with(|db| { - skm.unlock_and_get_user_state( - &mut db.borrow_mut(), - &LEGACY_IMPORTER, - user_id as u32, - &password, - ) - }) - .context(ks_err!("Unlock with password."))? - { - log::info!( - "In on_lock_screen_event. Trying to unlock when LSKF is uninitialized." - ); - } + fn on_weak_unlock_methods_expired(&self, user_id: i32) -> Result<()> { + log::info!("on_weak_unlock_methods_expired(user_id={})", user_id); + if !android_security_flags::fix_unlocked_device_required_keys_v2() { + return Ok(()); + } + check_keystore_permission(KeystorePerm::Lock) + .context(ks_err!("caller missing Lock permission"))?; + SUPER_KEY.write().unwrap().wipe_plaintext_unlocked_device_required_keys(user_id as u32); + Ok(()) + } - Ok(()) - } - (LockScreenEvent::UNLOCK, None) => { - check_keystore_permission(KeystorePerm::Unlock).context(ks_err!("Unlock."))?; - ENFORCEMENTS.set_device_locked(user_id, false); - let mut skm = SUPER_KEY.write().unwrap(); - DB.with(|db| { - skm.try_unlock_user_with_biometric(&mut db.borrow_mut(), user_id as u32) - }) - .context(ks_err!("try_unlock_user_with_biometric failed"))?; - Ok(()) - } - (LockScreenEvent::LOCK, None) => { - check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?; - ENFORCEMENTS.set_device_locked(user_id, true); - let mut skm = SUPER_KEY.write().unwrap(); - DB.with(|db| { - skm.lock_screen_lock_bound_key( - &mut db.borrow_mut(), - user_id as u32, - unlocking_sids.unwrap_or(&[]), - ); - }); - Ok(()) - } - _ => { - // Any other combination is not supported. - Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!("Unknown event.")) - } + fn on_non_lskf_unlock_methods_expired(&self, user_id: i32) -> Result<()> { + log::info!("on_non_lskf_unlock_methods_expired(user_id={})", user_id); + if !android_security_flags::fix_unlocked_device_required_keys_v2() { + return Ok(()); } + check_keystore_permission(KeystorePerm::Lock) + .context(ks_err!("caller missing Lock permission"))?; + SUPER_KEY.write().unwrap().wipe_all_unlocked_device_required_keys(user_id as u32); + Ok(()) } fn get_auth_tokens_for_credstore( @@ -223,7 +226,8 @@ impl AuthorizationManager { ) -> Result<AuthorizationTokens> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. - check_keystore_permission(KeystorePerm::GetAuthToken).context(ks_err!("GetAuthToken"))?; + check_keystore_permission(KeystorePerm::GetAuthToken) + .context(ks_err!("caller missing GetAuthToken permission"))?; // If the challenge is zero, return error if challenge == 0 { @@ -235,6 +239,32 @@ impl AuthorizationManager { ENFORCEMENTS.get_auth_tokens(challenge, secure_user_id, auth_token_max_age_millis)?; Ok(AuthorizationTokens { authToken: auth_token, timestampToken: ts_token }) } + + fn get_last_auth_time( + &self, + secure_user_id: i64, + auth_types: &[HardwareAuthenticatorType], + ) -> Result<i64> { + // Check keystore permission. + check_keystore_permission(KeystorePerm::GetLastAuthTime) + .context(ks_err!("caller missing GetLastAuthTime permission"))?; + + let mut max_time: i64 = -1; + for auth_type in auth_types.iter() { + if let Some(time) = ENFORCEMENTS.get_last_auth_time(secure_user_id, *auth_type) { + if time.milliseconds() > max_time { + max_time = time.milliseconds(); + } + } + } + + if max_time >= 0 { + Ok(max_time) + } else { + Err(Error::Rc(ResponseCode::NO_AUTH_TOKEN_FOUND)) + .context(ks_err!("No auth token found")) + } + } } impl Interface for AuthorizationManager {} @@ -245,26 +275,29 @@ impl IKeystoreAuthorization for AuthorizationManager { map_or_log_err(self.add_auth_token(auth_token), Ok) } - fn onLockScreenEvent( + fn onDeviceUnlocked(&self, user_id: i32, password: Option<&[u8]>) -> BinderResult<()> { + let _wp = wd::watch_millis("IKeystoreAuthorization::onDeviceUnlocked", 500); + map_or_log_err(self.on_device_unlocked(user_id, password.map(|pw| pw.into())), Ok) + } + + fn onDeviceLocked( &self, - lock_screen_event: LockScreenEvent, user_id: i32, - password: Option<&[u8]>, - unlocking_sids: Option<&[i64]>, + unlocking_sids: &[i64], + weak_unlock_enabled: bool, ) -> BinderResult<()> { - let _wp = - wd::watch_millis_with("IKeystoreAuthorization::onLockScreenEvent", 500, move || { - format!("lock event: {}", lock_screen_event.0) - }); - map_or_log_err( - self.on_lock_screen_event( - lock_screen_event, - user_id, - password.map(|pw| pw.into()), - unlocking_sids, - ), - Ok, - ) + let _wp = wd::watch_millis("IKeystoreAuthorization::onDeviceLocked", 500); + map_or_log_err(self.on_device_locked(user_id, unlocking_sids, weak_unlock_enabled), Ok) + } + + fn onWeakUnlockMethodsExpired(&self, user_id: i32) -> BinderResult<()> { + let _wp = wd::watch_millis("IKeystoreAuthorization::onWeakUnlockMethodsExpired", 500); + map_or_log_err(self.on_weak_unlock_methods_expired(user_id), Ok) + } + + fn onNonLskfUnlockMethodsExpired(&self, user_id: i32) -> BinderResult<()> { + let _wp = wd::watch_millis("IKeystoreAuthorization::onNonLskfUnlockMethodsExpired", 500); + map_or_log_err(self.on_non_lskf_unlock_methods_expired(user_id), Ok) } fn getAuthTokensForCredStore( @@ -283,4 +316,19 @@ impl IKeystoreAuthorization for AuthorizationManager { Ok, ) } + + fn getLastAuthTime( + &self, + secure_user_id: i64, + auth_types: &[HardwareAuthenticatorType], + ) -> binder::Result<i64> { + if aconfig_android_hardware_biometrics_rust::last_authentication_time() { + map_or_log_err(self.get_last_auth_time(secure_user_id, auth_types), Ok) + } else { + Err(BinderStatus::new_service_specific_error( + ResponseCode::PERMISSION_DENIED.0, + Some(CString::new("Feature is not enabled.").unwrap().as_c_str()), + )) + } + } } diff --git a/keystore2/src/crypto/Android.bp b/keystore2/src/crypto/Android.bp index 1ac64674..f8da06ff 100644 --- a/keystore2/src/crypto/Android.bp +++ b/keystore2/src/crypto/Android.bp @@ -32,8 +32,8 @@ rust_library { "libthiserror", ], shared_libs: [ - "libkeystore2_crypto", "libcrypto", + "libkeystore2_crypto", ], vendor_available: true, apex_available: [ @@ -45,8 +45,8 @@ rust_library { cc_library { name: "libkeystore2_crypto", srcs: [ - "crypto.cpp", "certificate_utils.cpp", + "crypto.cpp", ], export_include_dirs: ["include"], shared_libs: [ @@ -69,29 +69,28 @@ rust_bindgen { vendor_available: true, shared_libs: ["libcrypto"], bindgen_flags: [ - "--size_t-is-usize", - "--allowlist-function", "hmacSha256", - "--allowlist-function", "randomBytes", - "--allowlist-function", "AES_gcm_encrypt", - "--allowlist-function", "AES_gcm_decrypt", - "--allowlist-function", "CreateKeyId", - "--allowlist-function", "generateKeyFromPassword", - "--allowlist-function", "HKDFExtract", - "--allowlist-function", "HKDFExpand", - "--allowlist-function", "ECDHComputeKey", - "--allowlist-function", "ECKEYGenerateKey", - "--allowlist-function", "ECKEYMarshalPrivateKey", - "--allowlist-function", "ECKEYParsePrivateKey", - "--allowlist-function", "EC_KEY_get0_public_key", - "--allowlist-function", "ECPOINTPoint2Oct", - "--allowlist-function", "ECPOINTOct2Point", - "--allowlist-function", "EC_KEY_free", - "--allowlist-function", "EC_POINT_free", - "--allowlist-function", "extractSubjectFromCertificate", - "--allowlist-type", "EC_KEY", - "--allowlist-type", "EC_POINT", - "--allowlist-var", "EC_MAX_BYTES", - "--allowlist-var", "EVP_MAX_MD_SIZE", + "--allowlist-function=AES_gcm_decrypt", + "--allowlist-function=AES_gcm_encrypt", + "--allowlist-function=CreateKeyId", + "--allowlist-function=ECDHComputeKey", + "--allowlist-function=ECKEYGenerateKey", + "--allowlist-function=ECKEYMarshalPrivateKey", + "--allowlist-function=ECKEYParsePrivateKey", + "--allowlist-function=ECPOINTOct2Point", + "--allowlist-function=ECPOINTPoint2Oct", + "--allowlist-function=EC_KEY_free", + "--allowlist-function=EC_KEY_get0_public_key", + "--allowlist-function=EC_POINT_free", + "--allowlist-function=HKDFExpand", + "--allowlist-function=HKDFExtract", + "--allowlist-function=PBKDF2", + "--allowlist-function=extractSubjectFromCertificate", + "--allowlist-function=hmacSha256", + "--allowlist-function=randomBytes", + "--allowlist-type=EC_KEY", + "--allowlist-type=EC_POINT", + "--allowlist-var=EC_MAX_BYTES", + "--allowlist-var=EVP_MAX_MD_SIZE", ], cflags: ["-DBORINGSSL_NO_CXX"], apex_available: [ diff --git a/keystore2/src/crypto/crypto.cpp b/keystore2/src/crypto/crypto.cpp index 7feeaff6..56d8de6c 100644 --- a/keystore2/src/crypto/crypto.cpp +++ b/keystore2/src/crypto/crypto.cpp @@ -141,7 +141,8 @@ bool AES_gcm_decrypt(const uint8_t* in, uint8_t* out, size_t len, const uint8_t* EVP_DecryptUpdate(ctx.get(), out_pos, &out_len, in, len); out_pos += out_len; if (!EVP_DecryptFinal_ex(ctx.get(), out_pos, &out_len)) { - ALOGE("Failed to decrypt blob; ciphertext or tag is likely corrupted"); + // No error log here; this is expected when trying two different keys to see which one + // works. The callers handle the error appropriately. return false; } out_pos += out_len; @@ -191,8 +192,7 @@ static constexpr size_t SALT_SIZE = 16; // Copied from system/security/keystore/user_state.cpp. -void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw, size_t pw_len, - const uint8_t* salt) { +void PBKDF2(uint8_t* key, size_t key_len, const char* pw, size_t pw_len, const uint8_t* salt) { const EVP_MD* digest = EVP_sha256(); // SHA1 was used prior to increasing the key size diff --git a/keystore2/src/crypto/crypto.hpp b/keystore2/src/crypto/crypto.hpp index 4a161e6c..f67f6407 100644 --- a/keystore2/src/crypto/crypto.hpp +++ b/keystore2/src/crypto/crypto.hpp @@ -37,8 +37,7 @@ extern "C" { bool CreateKeyId(const uint8_t* key_blob, size_t len, km_id_t* out_id); // The salt parameter must be non-nullptr and point to 16 bytes of data. - void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw, - size_t pw_len, const uint8_t* salt); + void PBKDF2(uint8_t* key, size_t key_len, const char* pw, size_t pw_len, const uint8_t* salt); #include "openssl/digest.h" #include "openssl/ec_key.h" diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs index 08b7589e..09b84ec8 100644 --- a/keystore2/src/crypto/lib.rs +++ b/keystore2/src/crypto/lib.rs @@ -19,10 +19,10 @@ mod error; pub mod zvec; pub use error::Error; use keystore2_crypto_bindgen::{ - extractSubjectFromCertificate, generateKeyFromPassword, hmacSha256, randomBytes, - AES_gcm_decrypt, AES_gcm_encrypt, ECDHComputeKey, ECKEYGenerateKey, ECKEYMarshalPrivateKey, - ECKEYParsePrivateKey, ECPOINTOct2Point, ECPOINTPoint2Oct, EC_KEY_free, EC_KEY_get0_public_key, - EC_POINT_free, HKDFExpand, HKDFExtract, EC_KEY, EC_MAX_BYTES, EC_POINT, EVP_MAX_MD_SIZE, + extractSubjectFromCertificate, hmacSha256, randomBytes, AES_gcm_decrypt, AES_gcm_encrypt, + ECDHComputeKey, ECKEYGenerateKey, ECKEYMarshalPrivateKey, ECKEYParsePrivateKey, + ECPOINTOct2Point, ECPOINTPoint2Oct, EC_KEY_free, EC_KEY_get0_public_key, EC_POINT_free, + HKDFExpand, HKDFExtract, EC_KEY, EC_MAX_BYTES, EC_POINT, EVP_MAX_MD_SIZE, PBKDF2, }; use std::convert::TryFrom; use std::convert::TryInto; @@ -49,8 +49,8 @@ pub const LEGACY_IV_LENGTH: usize = 16; /// Generate an AES256 key, essentially 32 random bytes from the underlying /// boringssl library discretely stuffed into a ZVec. pub fn generate_aes256_key() -> Result<ZVec, Error> { - // Safety: key has the same length as the requested number of random bytes. let mut key = ZVec::new(AES_256_KEY_LENGTH)?; + // Safety: key has the same length as the requested number of random bytes. if unsafe { randomBytes(key.as_mut_ptr(), AES_256_KEY_LENGTH) } { Ok(key) } else { @@ -65,8 +65,8 @@ pub fn generate_salt() -> Result<Vec<u8>, Error> { /// Generate random data of the given size. pub fn generate_random_data(size: usize) -> Result<Vec<u8>, Error> { - // Safety: data has the same length as the requested number of random bytes. let mut data = vec![0; size]; + // Safety: data has the same length as the requested number of random bytes. if unsafe { randomBytes(data.as_mut_ptr(), size) } { Ok(data) } else { @@ -172,7 +172,7 @@ pub fn aes_gcm_encrypt(plaintext: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8> } } -/// Represents a "password" that can be used to key the PBKDF2 algorithm. +/// A high-entropy synthetic password from which an AES key may be derived. pub enum Password<'a> { /// Borrow an existing byte array Ref(&'a [u8]), @@ -194,23 +194,28 @@ impl<'a> Password<'a> { } } - /// Generate a key from the given password and salt. - /// The salt must be exactly 16 bytes long. - /// Two key sizes are accepted: 16 and 32 bytes. - pub fn derive_key(&self, salt: &[u8], key_length: usize) -> Result<ZVec, Error> { + /// Derives a key from the given password and salt, using PBKDF2 with 8192 iterations. + /// + /// The salt length must be 16 bytes, and the output key length must be 16 or 32 bytes. + /// + /// This function exists only for backwards compatibility reasons. Keystore now receives only + /// high-entropy synthetic passwords, which do not require key stretching. + pub fn derive_key_pbkdf2(&self, salt: &[u8], out_len: usize) -> Result<ZVec, Error> { if salt.len() != SALT_LENGTH { return Err(Error::InvalidSaltLength); } - match key_length { + match out_len { AES_128_KEY_LENGTH | AES_256_KEY_LENGTH => {} _ => return Err(Error::InvalidKeyLength), } let pw = self.get_key(); - let mut result = ZVec::new(key_length)?; + let mut result = ZVec::new(out_len)?; + // Safety: We checked that the salt is exactly 16 bytes long. The other pointers are valid, + // and have matching lengths. unsafe { - generateKeyFromPassword( + PBKDF2( result.as_mut_ptr(), result.len(), pw.as_ptr() as *const std::os::raw::c_char, @@ -222,6 +227,13 @@ impl<'a> Password<'a> { Ok(result) } + /// Derives a key from the given high-entropy synthetic password and salt, using HKDF. + pub fn derive_key_hkdf(&self, salt: &[u8], out_len: usize) -> Result<ZVec, Error> { + let prk = hkdf_extract(self.get_key(), salt)?; + let info = []; + hkdf_expand(out_len, &prk, &info) + } + /// Try to make another Password object with the same data. pub fn try_clone(&self) -> Result<Password<'static>, Error> { Ok(Password::Owned(ZVec::try_from(self.get_key())?)) @@ -324,10 +336,10 @@ impl Drop for OwnedECPoint { /// Calls the boringssl ECDH_compute_key function. pub fn ecdh_compute_key(pub_key: &EC_POINT, priv_key: &ECKey) -> Result<ZVec, Error> { let mut buf = ZVec::new(EC_MAX_BYTES)?; + let result = // Safety: Our ECDHComputeKey wrapper passes EC_MAX_BYES to ECDH_compute_key, which // writes at most that many bytes to the output. // The two keys are valid objects. - let result = unsafe { ECDHComputeKey(buf.as_mut_ptr() as *mut std::ffi::c_void, pub_key, priv_key.0) }; if result == -1 { return Err(Error::ECDHComputeKeyFailed); @@ -469,9 +481,7 @@ pub fn parse_subject_from_certificate(cert_buf: &[u8]) -> Result<Vec<u8>, Error> mod tests { use super::*; - use keystore2_crypto_bindgen::{ - generateKeyFromPassword, AES_gcm_decrypt, AES_gcm_encrypt, CreateKeyId, - }; + use keystore2_crypto_bindgen::{AES_gcm_decrypt, AES_gcm_encrypt, CreateKeyId, PBKDF2}; #[test] fn test_wrapper_roundtrip() { @@ -487,9 +497,11 @@ mod tests { let input = vec![0; 16]; let mut out = vec![0; 16]; let mut out2 = vec![0; 16]; - let key = vec![0; 16]; - let iv = vec![0; 12]; + let key = [0; 16]; + let iv = [0; 12]; let mut tag = vec![0; 16]; + // SAFETY: The various pointers are obtained from references so they are valid, and + // `AES_gcm_encrypt` and `AES_gcm_decrypt` don't do anything with them after they return. unsafe { let res = AES_gcm_encrypt( input.as_ptr(), @@ -519,22 +531,27 @@ mod tests { #[test] fn test_create_key_id() { - let blob = vec![0; 16]; + let blob = [0; 16]; let mut out: u64 = 0; + // SAFETY: The pointers are obtained from references so they are valid, the length matches + // the length of the array, and `CreateKeyId` doesn't access them after it returns. unsafe { - let res = CreateKeyId(blob.as_ptr(), 16, &mut out); + let res = CreateKeyId(blob.as_ptr(), blob.len(), &mut out); assert!(res); assert_ne!(out, 0); } } #[test] - fn test_generate_key_from_password() { + fn test_pbkdf2() { let mut key = vec![0; 16]; - let pw = vec![0; 16]; - let salt = vec![0; 16]; + let pw = [0; 16]; + let salt = [0; 16]; + // SAFETY: The pointers are obtained from references so they are valid, the salt is the + // expected length, the other lengths match the lengths of the arrays, and `PBKDF2` doesn't + // access them after it returns. unsafe { - generateKeyFromPassword(key.as_mut_ptr(), 16, pw.as_ptr(), 16, salt.as_ptr()); + PBKDF2(key.as_mut_ptr(), key.len(), pw.as_ptr(), pw.len(), salt.as_ptr()); } assert_ne!(key, vec![0; 16]); } diff --git a/keystore2/src/crypto/tests/certificate_utils_test.cpp b/keystore2/src/crypto/tests/certificate_utils_test.cpp index bd949282..a8517987 100644 --- a/keystore2/src/crypto/tests/certificate_utils_test.cpp +++ b/keystore2/src/crypto/tests/certificate_utils_test.cpp @@ -313,7 +313,15 @@ TEST_P(CertificateUtilsWithRsa, CertSigningWithCallbackRsa) { const uint8_t* p = encCert.data(); X509_Ptr decoded_cert(d2i_X509(nullptr, &p, (long)encCert.size())); EVP_PKEY_Ptr decoded_pkey(X509_get_pubkey(decoded_cert.get())); - ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get())); + if ((padding == Padding::PSS) && (digest == Digest::SHA1 || digest == Digest::SHA224)) { + // BoringSSL after https://boringssl-review.googlesource.com/c/boringssl/+/53865 + // does not support these PSS combinations, so skip certificate verification for them + // and just check _something_ was returned. + EXPECT_NE(decoded_cert.get(), nullptr); + EXPECT_NE(decoded_pkey.get(), nullptr); + } else { + ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get())); + } } TEST(TimeStringTests, toTimeStringTest) { diff --git a/keystore2/src/crypto/zvec.rs b/keystore2/src/crypto/zvec.rs index 5a173c30..00cbb1c8 100644 --- a/keystore2/src/crypto/zvec.rs +++ b/keystore2/src/crypto/zvec.rs @@ -20,6 +20,7 @@ use std::convert::TryFrom; use std::fmt; use std::ops::{Deref, DerefMut}; use std::ptr::write_volatile; +use std::ptr::NonNull; /// A semi fixed size u8 vector that is zeroed when dropped. It can shrink in /// size but cannot grow larger than the original size (and if it shrinks it @@ -45,7 +46,8 @@ impl ZVec { let v: Vec<u8> = vec![0; size]; let b = v.into_boxed_slice(); if size > 0 { - unsafe { mlock(b.as_ptr() as *const std::ffi::c_void, b.len()) }?; + // SAFETY: The address range is part of our address space. + unsafe { mlock(NonNull::from(&b).cast(), b.len()) }?; } Ok(Self { elems: b, len: size }) } @@ -71,11 +73,14 @@ impl ZVec { impl Drop for ZVec { fn drop(&mut self) { for i in 0..self.elems.len() { - unsafe { write_volatile(self.elems.as_mut_ptr().add(i), 0) }; + // SAFETY: The pointer is valid and properly aligned because it came from a reference. + unsafe { write_volatile(&mut self.elems[i], 0) }; } if !self.elems.is_empty() { if let Err(e) = - unsafe { munlock(self.elems.as_ptr() as *const std::ffi::c_void, self.elems.len()) } + // SAFETY: The address range is part of our address space, and was previously locked + // by `mlock` in `ZVec::new` or the `TryFrom<Vec<u8>>` implementation. + unsafe { munlock(NonNull::from(&self.elems).cast(), self.elems.len()) } { log::error!("In ZVec::drop: `munlock` failed: {:?}.", e); } @@ -130,7 +135,8 @@ impl TryFrom<Vec<u8>> for ZVec { v.resize(v.capacity(), 0); let b = v.into_boxed_slice(); if !b.is_empty() { - unsafe { mlock(b.as_ptr() as *const std::ffi::c_void, b.len()) }?; + // SAFETY: The address range is part of our address space. + unsafe { mlock(NonNull::from(&b).cast(), b.len()) }?; } Ok(Self { elems: b, len }) } diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs index c9c28f6d..0cc982a8 100644 --- a/keystore2/src/database.rs +++ b/keystore2/src/database.rs @@ -46,37 +46,30 @@ pub(crate) mod utils; mod versioning; use crate::gc::Gc; -use crate::globals::get_keymint_dev_by_uuid; use crate::impl_metadata; // This is in db_utils.rs -use crate::key_parameter::{KeyParameter, Tag}; +use crate::key_parameter::{KeyParameter, KeyParameterValue, Tag}; use crate::ks_err; -use crate::metrics_store::log_rkp_error_stats; use crate::permission::KeyPermSet; use crate::utils::{get_current_time_in_milliseconds, watchdog as wd, AID_USER_OFFSET}; use crate::{ error::{Error as KsError, ErrorCode, ResponseCode}, super_key::SuperKeyType, }; -use anyhow::{anyhow, Context, Result}; -use std::{convert::TryFrom, convert::TryInto, ops::Deref, time::SystemTimeError}; -use utils as db_utils; -use utils::SqlField; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - HardwareAuthToken::HardwareAuthToken, - HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel, + HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType, + SecurityLevel::SecurityLevel, +}; +use android_security_metrics::aidl::android::security::metrics::{ + Storage::Storage as MetricsStorage, StorageStats::StorageStats, }; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, }; -use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{ - AttestationPoolStatus::AttestationPoolStatus, -}; -use android_security_metrics::aidl::android::security::metrics::{ - StorageStats::StorageStats, - Storage::Storage as MetricsStorage, - RkpError::RkpError as MetricsRkpError, -}; +use anyhow::{anyhow, Context, Result}; +use keystore2_flags; +use std::{convert::TryFrom, convert::TryInto, ops::Deref, time::SystemTimeError}; +use utils as db_utils; +use utils::SqlField; use keystore2_crypto::ZVec; use lazy_static::lazy_static; @@ -89,7 +82,7 @@ use rusqlite::{ types::FromSqlResult, types::ToSqlOutput, types::{FromSqlError, Value, ValueRef}, - Connection, OptionalExtension, ToSql, Transaction, TransactionBehavior, NO_PARAMS, + Connection, OptionalExtension, ToSql, Transaction, TransactionBehavior, }; use std::{ @@ -256,8 +249,6 @@ pub enum KeyType { /// This is a super key type. These keys are created by keystore itself and used to encrypt /// other key blobs to provide LSKF binding. Super, - /// This is an attestation key. These keys are created by the remote provisioning mechanism. - Attestation, } impl ToSql for KeyType { @@ -265,7 +256,6 @@ impl ToSql for KeyType { Ok(ToSqlOutput::Owned(Value::Integer(match self { KeyType::Client => 0, KeyType::Super => 1, - KeyType::Attestation => 2, }))) } } @@ -275,7 +265,6 @@ impl FromSql for KeyType { match i64::column_result(value)? { 0 => Ok(KeyType::Client), 1 => Ok(KeyType::Super), - 2 => Ok(KeyType::Attestation), v => Err(FromSqlError::OutOfRange(v)), } } @@ -325,8 +314,6 @@ pub static KEYSTORE_UUID: Uuid = Uuid([ 0x41, 0xe3, 0xb9, 0xce, 0x27, 0x58, 0x4e, 0x91, 0xbc, 0xfd, 0xa5, 0x5d, 0x91, 0x85, 0xab, 0x11, ]); -static EXPIRATION_BUFFER_MS: i64 = 12 * 60 * 60 * 1000; - /// Indicates how the sensitive part of this key blob is encrypted. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum EncryptedBy { @@ -774,22 +761,22 @@ pub struct KeystoreDB { } /// Database representation of the monotonic time retrieved from the system call clock_gettime with -/// CLOCK_MONOTONIC_RAW. Stores monotonic time as i64 in milliseconds. +/// CLOCK_BOOTTIME. Stores monotonic time as i64 in milliseconds. #[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)] -pub struct MonotonicRawTime(i64); +pub struct BootTime(i64); -impl MonotonicRawTime { - /// Constructs a new MonotonicRawTime +impl BootTime { + /// Constructs a new BootTime pub fn now() -> Self { Self(get_current_time_in_milliseconds()) } - /// Returns the value of MonotonicRawTime in milliseconds as i64 + /// Returns the value of BootTime in milliseconds as i64 pub fn milliseconds(&self) -> i64 { self.0 } - /// Returns the integer value of MonotonicRawTime as i64 + /// Returns the integer value of BootTime as i64 pub fn seconds(&self) -> i64 { self.0 / 1000 } @@ -800,13 +787,13 @@ impl MonotonicRawTime { } } -impl ToSql for MonotonicRawTime { +impl ToSql for BootTime { fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> { Ok(ToSqlOutput::Owned(Value::Integer(self.0))) } } -impl FromSql for MonotonicRawTime { +impl FromSql for BootTime { fn column_result(value: ValueRef) -> FromSqlResult<Self> { Ok(Self(i64::column_result(value)?)) } @@ -818,11 +805,11 @@ impl FromSql for MonotonicRawTime { pub struct AuthTokenEntry { auth_token: HardwareAuthToken, // Time received in milliseconds - time_received: MonotonicRawTime, + time_received: BootTime, } impl AuthTokenEntry { - fn new(auth_token: HardwareAuthToken, time_received: MonotonicRawTime) -> Self { + fn new(auth_token: HardwareAuthToken, time_received: BootTime) -> Self { AuthTokenEntry { auth_token, time_received } } @@ -845,7 +832,7 @@ impl AuthTokenEntry { } /// Returns the time that this auth token was received. - pub fn time_received(&self) -> MonotonicRawTime { + pub fn time_received(&self) -> BootTime { self.time_received } @@ -855,11 +842,6 @@ impl AuthTokenEntry { } } -/// Shared in-memory databases get destroyed as soon as the last connection to them gets closed. -/// This object does not allow access to the database connection. But it keeps a database -/// connection alive in order to keep the in memory per boot database alive. -pub struct PerBootDbKeepAlive(Connection); - impl KeystoreDB { const UNASSIGNED_KEY_ID: i64 = -1i64; const CURRENT_DB_VERSION: u32 = 1; @@ -918,21 +900,21 @@ impl KeystoreDB { alias BLOB, state INTEGER, km_uuid BLOB);", - NO_PARAMS, + [], ) .context("Failed to initialize \"keyentry\" table.")?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.keyentry_id_index ON keyentry(id);", - NO_PARAMS, + [], ) .context("Failed to create index keyentry_id_index.")?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.keyentry_domain_namespace_index ON keyentry(domain, namespace, alias);", - NO_PARAMS, + [], ) .context("Failed to create index keyentry_domain_namespace_index.")?; @@ -942,14 +924,14 @@ impl KeystoreDB { subcomponent_type INTEGER, keyentryid INTEGER, blob BLOB);", - NO_PARAMS, + [], ) .context("Failed to initialize \"blobentry\" table.")?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.blobentry_keyentryid_index ON blobentry(keyentryid);", - NO_PARAMS, + [], ) .context("Failed to create index blobentry_keyentryid_index.")?; @@ -960,14 +942,14 @@ impl KeystoreDB { tag INTEGER, data ANY, UNIQUE (blobentryid, tag));", - NO_PARAMS, + [], ) .context("Failed to initialize \"blobmetadata\" table.")?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.blobmetadata_blobentryid_index ON blobmetadata(blobentryid);", - NO_PARAMS, + [], ) .context("Failed to create index blobmetadata_blobentryid_index.")?; @@ -977,14 +959,14 @@ impl KeystoreDB { tag INTEGER, data ANY, security_level INTEGER);", - NO_PARAMS, + [], ) .context("Failed to initialize \"keyparameter\" table.")?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.keyparameter_keyentryid_index ON keyparameter(keyentryid);", - NO_PARAMS, + [], ) .context("Failed to create index keyparameter_keyentryid_index.")?; @@ -994,14 +976,14 @@ impl KeystoreDB { tag INTEGER, data ANY, UNIQUE (keyentryid, tag));", - NO_PARAMS, + [], ) .context("Failed to initialize \"keymetadata\" table.")?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.keymetadata_keyentryid_index ON keymetadata(keyentryid);", - NO_PARAMS, + [], ) .context("Failed to create index keymetadata_keyentryid_index.")?; @@ -1011,7 +993,7 @@ impl KeystoreDB { grantee INTEGER, keyentryid INTEGER, access_vector INTEGER);", - NO_PARAMS, + [], ) .context("Failed to initialize \"grant\" table.")?; @@ -1027,6 +1009,14 @@ impl KeystoreDB { let mut persistent_path_str = "file:".to_owned(); persistent_path_str.push_str(&persistent_path.to_string_lossy()); + // Connect to database in specific mode + let persistent_path_mode = if keystore2_flags::wal_db_journalmode_v3() { + "?journal_mode=WAL".to_owned() + } else { + "?journal_mode=DELETE".to_owned() + }; + persistent_path_str.push_str(&persistent_path_mode); + Ok(persistent_path_str) } @@ -1172,9 +1162,9 @@ impl KeystoreDB { "DELETE FROM persistent.blobmetadata WHERE blobentryid = ?;", params![blob_id], ) - .context("Trying to delete blob metadata.")?; + .context(ks_err!("Trying to delete blob metadata: {:?}", blob_id))?; tx.execute("DELETE FROM persistent.blobentry WHERE id = ?;", params![blob_id]) - .context("Trying to blob.")?; + .context(ks_err!("Trying to delete blob: {:?}", blob_id))?; } Self::cleanup_unreferenced(tx).context("Trying to cleanup unreferenced.")?; @@ -1472,7 +1462,7 @@ impl KeystoreDB { F: Fn(&Transaction) -> Result<(bool, T)>, { loop { - match self + let result = self .conn .transaction_with_behavior(behavior) .context(ks_err!()) @@ -1480,7 +1470,8 @@ impl KeystoreDB { .and_then(|(result, tx)| { tx.commit().context(ks_err!("Failed to commit transaction."))?; Ok(result) - }) { + }); + match result { Ok(result) => break Ok(result), Err(e) => { if Self::is_locked_error(&e) { @@ -1565,48 +1556,6 @@ impl KeystoreDB { )) } - /// Creates a new attestation key entry and allocates a new randomized id for the new key. - /// The key id gets associated with a domain and namespace later but not with an alias. The - /// alias will be used to denote if a key has been signed as each key can only be bound to one - /// domain and namespace pairing so there is no need to use them as a value for indexing into - /// a key. - pub fn create_attestation_key_entry( - &mut self, - maced_public_key: &[u8], - raw_public_key: &[u8], - private_key: &[u8], - km_uuid: &Uuid, - ) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::create_attestation_key_entry", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let key_id = KEY_ID_LOCK.get( - Self::insert_with_retry(|id| { - tx.execute( - "INSERT into persistent.keyentry - (id, key_type, domain, namespace, alias, state, km_uuid) - VALUES(?, ?, NULL, NULL, NULL, ?, ?);", - params![id, KeyType::Attestation, KeyLifeCycle::Live, km_uuid], - ) - }) - .context(ks_err!())?, - ); - Self::set_blob_internal( - tx, - key_id.0, - SubComponentType::KEY_BLOB, - Some(private_key), - None, - )?; - let mut metadata = KeyMetaData::new(); - metadata.add(KeyMetaEntry::AttestationMacedPublicKey(maced_public_key.to_vec())); - metadata.add(KeyMetaEntry::AttestationRawPubKey(raw_public_key.to_vec())); - metadata.store_in_db(key_id.0, tx)?; - Ok(()).no_gc() - }) - .context(ks_err!()) - } - /// Set a new blob and associates it with the given key id. Each blob /// has a sub component type. /// Each key can have one of each sub component type associated. If more @@ -1666,7 +1615,7 @@ impl KeystoreDB { .context(ks_err!("Failed to insert blob."))?; if let Some(blob_metadata) = blob_metadata { let blob_id = tx - .query_row("SELECT MAX(id) FROM persistent.blobentry;", NO_PARAMS, |row| { + .query_row("SELECT MAX(id) FROM persistent.blobentry;", [], |row| { row.get(0) }) .context(ks_err!("Failed to get new blob id."))?; @@ -1734,442 +1683,6 @@ impl KeystoreDB { .context(ks_err!()) } - /// Stores a signed certificate chain signed by a remote provisioning server, keyed - /// on the public key. - pub fn store_signed_attestation_certificate_chain( - &mut self, - raw_public_key: &[u8], - batch_cert: &[u8], - cert_chain: &[u8], - expiration_date: i64, - km_uuid: &Uuid, - ) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::store_signed_attestation_certificate_chain", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let mut stmt = tx - .prepare( - "SELECT keyentryid - FROM persistent.keymetadata - WHERE tag = ? AND data = ? AND keyentryid IN - (SELECT id - FROM persistent.keyentry - WHERE - alias IS NULL AND - domain IS NULL AND - namespace IS NULL AND - key_type = ? AND - km_uuid = ?);", - ) - .context("Failed to store attestation certificate chain.")?; - let mut rows = stmt - .query(params![ - KeyMetaData::AttestationRawPubKey, - raw_public_key, - KeyType::Attestation, - km_uuid - ]) - .context("Failed to fetch keyid")?; - let key_id = db_utils::with_rows_extract_one(&mut rows, |row| { - row.map_or_else(|| Err(KsError::Rc(ResponseCode::KEY_NOT_FOUND)), Ok)? - .get(0) - .context("Failed to unpack id.") - }) - .context("Failed to get key_id.")?; - let num_updated = tx - .execute( - "UPDATE persistent.keyentry - SET alias = ? - WHERE id = ?;", - params!["signed", key_id], - ) - .context("Failed to update alias.")?; - if num_updated != 1 { - return Err(KsError::sys()).context("Alias not updated for the key."); - } - let mut metadata = KeyMetaData::new(); - metadata.add(KeyMetaEntry::AttestationExpirationDate(DateTime::from_millis_epoch( - expiration_date, - ))); - metadata.store_in_db(key_id, tx).context("Failed to insert key metadata.")?; - Self::set_blob_internal( - tx, - key_id, - SubComponentType::CERT_CHAIN, - Some(cert_chain), - None, - ) - .context("Failed to insert cert chain")?; - Self::set_blob_internal(tx, key_id, SubComponentType::CERT, Some(batch_cert), None) - .context("Failed to insert cert")?; - Ok(()).no_gc() - }) - .context(ks_err!()) - } - - /// Assigns the next unassigned attestation key to a domain/namespace combo that does not - /// currently have a key assigned to it. - pub fn assign_attestation_key( - &mut self, - domain: Domain, - namespace: i64, - km_uuid: &Uuid, - ) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::assign_attestation_key", 500); - - match domain { - Domain::APP | Domain::SELINUX => {} - _ => { - return Err(KsError::sys()) - .context(ks_err!("Domain {:?} must be either App or SELinux.", domain)); - } - } - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let result = tx - .execute( - "UPDATE persistent.keyentry - SET domain=?1, namespace=?2 - WHERE - id = - (SELECT MIN(id) - FROM persistent.keyentry - WHERE ALIAS IS NOT NULL - AND domain IS NULL - AND key_type IS ?3 - AND state IS ?4 - AND km_uuid IS ?5) - AND - (SELECT COUNT(*) - FROM persistent.keyentry - WHERE domain=?1 - AND namespace=?2 - AND key_type IS ?3 - AND state IS ?4 - AND km_uuid IS ?5) = 0;", - params![ - domain.0 as u32, - namespace, - KeyType::Attestation, - KeyLifeCycle::Live, - km_uuid, - ], - ) - .context("Failed to assign attestation key")?; - if result == 0 { - let (_, hw_info) = get_keymint_dev_by_uuid(km_uuid) - .context("Error in retrieving keymint device by UUID.")?; - log_rkp_error_stats(MetricsRkpError::OUT_OF_KEYS, &hw_info.securityLevel); - return Err(KsError::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) - .context("Out of keys."); - } else if result > 1 { - return Err(KsError::sys()) - .context(format!("Expected to update 1 entry, instead updated {}", result)); - } - Ok(()).no_gc() - }) - .context(ks_err!()) - } - - /// Retrieves num_keys number of attestation keys that have not yet been signed by a remote - /// provisioning server, or the maximum number available if there are not num_keys number of - /// entries in the table. - pub fn fetch_unsigned_attestation_keys( - &mut self, - num_keys: i32, - km_uuid: &Uuid, - ) -> Result<Vec<Vec<u8>>> { - let _wp = wd::watch_millis("KeystoreDB::fetch_unsigned_attestation_keys", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let mut stmt = tx - .prepare( - "SELECT data - FROM persistent.keymetadata - WHERE tag = ? AND keyentryid IN - (SELECT id - FROM persistent.keyentry - WHERE - alias IS NULL AND - domain IS NULL AND - namespace IS NULL AND - key_type = ? AND - km_uuid = ? - LIMIT ?);", - ) - .context("Failed to prepare statement")?; - let rows = stmt - .query_map( - params![ - KeyMetaData::AttestationMacedPublicKey, - KeyType::Attestation, - km_uuid, - num_keys - ], - |row| row.get(0), - )? - .collect::<rusqlite::Result<Vec<Vec<u8>>>>() - .context("Failed to execute statement")?; - Ok(rows).no_gc() - }) - .context(ks_err!()) - } - - /// Removes any keys that have expired as of the current time. Returns the number of keys - /// marked unreferenced that are bound to be garbage collected. - pub fn delete_expired_attestation_keys(&mut self) -> Result<i32> { - let _wp = wd::watch_millis("KeystoreDB::delete_expired_attestation_keys", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let mut stmt = tx - .prepare( - "SELECT keyentryid, data - FROM persistent.keymetadata - WHERE tag = ? AND keyentryid IN - (SELECT id - FROM persistent.keyentry - WHERE key_type = ?);", - ) - .context("Failed to prepare query")?; - let key_ids_to_check = stmt - .query_map( - params![KeyMetaData::AttestationExpirationDate, KeyType::Attestation], - |row| Ok((row.get(0)?, row.get(1)?)), - )? - .collect::<rusqlite::Result<Vec<(i64, DateTime)>>>() - .context("Failed to get date metadata")?; - // Calculate curr_time with a discount factor to avoid a key that's milliseconds away - // from expiration dodging this delete call. - let curr_time = DateTime::from_millis_epoch( - SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64 - + EXPIRATION_BUFFER_MS, - ); - let mut num_deleted = 0; - for id in key_ids_to_check.iter().filter(|kt| kt.1 < curr_time).map(|kt| kt.0) { - if Self::mark_unreferenced(tx, id)? { - num_deleted += 1; - } - } - Ok(num_deleted).do_gc(num_deleted != 0) - }) - .context(ks_err!()) - } - - /// Deletes all remotely provisioned attestation keys in the system, regardless of the state - /// they are in. This is useful primarily as a testing mechanism. - pub fn delete_all_attestation_keys(&mut self) -> Result<i64> { - let _wp = wd::watch_millis("KeystoreDB::delete_all_attestation_keys", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let mut stmt = tx - .prepare( - "SELECT id FROM persistent.keyentry - WHERE key_type IS ?;", - ) - .context("Failed to prepare statement")?; - let keys_to_delete = stmt - .query_map(params![KeyType::Attestation], |row| row.get(0))? - .collect::<rusqlite::Result<Vec<i64>>>() - .context("Failed to execute statement")?; - let num_deleted = keys_to_delete - .iter() - .map(|id| Self::mark_unreferenced(tx, *id)) - .collect::<Result<Vec<bool>>>() - .context("Failed to execute mark_unreferenced on a keyid")? - .into_iter() - .filter(|result| *result) - .count() as i64; - Ok(num_deleted).do_gc(num_deleted != 0) - }) - .context(ks_err!()) - } - - /// Counts the number of keys that will expire by the provided epoch date and the number of - /// keys not currently assigned to a domain. - pub fn get_attestation_pool_status( - &mut self, - date: i64, - km_uuid: &Uuid, - ) -> Result<AttestationPoolStatus> { - let _wp = wd::watch_millis("KeystoreDB::get_attestation_pool_status", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let mut stmt = tx.prepare( - "SELECT data - FROM persistent.keymetadata - WHERE tag = ? AND keyentryid IN - (SELECT id - FROM persistent.keyentry - WHERE alias IS NOT NULL - AND key_type = ? - AND km_uuid = ? - AND state = ?);", - )?; - let times = stmt - .query_map( - params![ - KeyMetaData::AttestationExpirationDate, - KeyType::Attestation, - km_uuid, - KeyLifeCycle::Live - ], - |row| row.get(0), - )? - .collect::<rusqlite::Result<Vec<DateTime>>>() - .context("Failed to execute metadata statement")?; - let expiring = - times.iter().filter(|time| time < &&DateTime::from_millis_epoch(date)).count() - as i32; - stmt = tx.prepare( - "SELECT alias, domain - FROM persistent.keyentry - WHERE key_type = ? AND km_uuid = ? AND state = ?;", - )?; - let rows = stmt - .query_map(params![KeyType::Attestation, km_uuid, KeyLifeCycle::Live], |row| { - Ok((row.get(0)?, row.get(1)?)) - })? - .collect::<rusqlite::Result<Vec<(Option<String>, Option<u32>)>>>() - .context("Failed to execute keyentry statement")?; - let mut unassigned = 0i32; - let mut attested = 0i32; - let total = rows.len() as i32; - for (alias, domain) in rows { - match (alias, domain) { - (Some(_alias), None) => { - attested += 1; - unassigned += 1; - } - (Some(_alias), Some(_domain)) => { - attested += 1; - } - _ => {} - } - } - Ok(AttestationPoolStatus { expiring, unassigned, attested, total }).no_gc() - }) - .context(ks_err!()) - } - - fn query_kid_for_attestation_key_and_cert_chain( - &self, - tx: &Transaction, - domain: Domain, - namespace: i64, - km_uuid: &Uuid, - ) -> Result<Option<i64>> { - let mut stmt = tx.prepare( - "SELECT id - FROM persistent.keyentry - WHERE key_type = ? - AND domain = ? - AND namespace = ? - AND state = ? - AND km_uuid = ?;", - )?; - let rows = stmt - .query_map( - params![ - KeyType::Attestation, - domain.0 as u32, - namespace, - KeyLifeCycle::Live, - km_uuid - ], - |row| row.get(0), - )? - .collect::<rusqlite::Result<Vec<i64>>>() - .context("query failed.")?; - if rows.is_empty() { - return Ok(None); - } - Ok(Some(rows[0])) - } - - /// Fetches the private key and corresponding certificate chain assigned to a - /// domain/namespace pair. Will either return nothing if the domain/namespace is - /// not assigned, or one CertificateChain. - pub fn retrieve_attestation_key_and_cert_chain( - &mut self, - domain: Domain, - namespace: i64, - km_uuid: &Uuid, - ) -> Result<Option<(KeyIdGuard, CertificateChain)>> { - let _wp = wd::watch_millis("KeystoreDB::retrieve_attestation_key_and_cert_chain", 500); - - match domain { - Domain::APP | Domain::SELINUX => {} - _ => { - return Err(KsError::sys()) - .context(format!("Domain {:?} must be either App or SELinux.", domain)); - } - } - - self.delete_expired_attestation_keys() - .context(ks_err!("Failed to prune expired attestation keys",))?; - let tx = self - .conn - .unchecked_transaction() - .context(ks_err!("Failed to initialize transaction."))?; - let key_id: i64 = match self - .query_kid_for_attestation_key_and_cert_chain(&tx, domain, namespace, km_uuid)? - { - None => return Ok(None), - Some(kid) => kid, - }; - tx.commit().context(ks_err!("Failed to commit keyid query"))?; - let key_id_guard = KEY_ID_LOCK.get(key_id); - let tx = self - .conn - .unchecked_transaction() - .context(ks_err!("Failed to initialize transaction."))?; - let mut stmt = tx.prepare( - "SELECT subcomponent_type, blob - FROM persistent.blobentry - WHERE keyentryid = ?;", - )?; - let rows = stmt - .query_map(params![key_id_guard.id()], |row| Ok((row.get(0)?, row.get(1)?)))? - .collect::<rusqlite::Result<Vec<(SubComponentType, Vec<u8>)>>>() - .context("query failed.")?; - if rows.is_empty() { - return Ok(None); - } else if rows.len() != 3 { - return Err(KsError::sys()).context(format!( - concat!( - "Expected to get a single attestation", - "key, cert, and cert chain for a total of 3 entries, but instead got {}." - ), - rows.len() - )); - } - let mut km_blob: Vec<u8> = Vec::new(); - let mut cert_chain_blob: Vec<u8> = Vec::new(); - let mut batch_cert_blob: Vec<u8> = Vec::new(); - for row in rows { - let sub_type: SubComponentType = row.0; - match sub_type { - SubComponentType::KEY_BLOB => { - km_blob = row.1; - } - SubComponentType::CERT_CHAIN => { - cert_chain_blob = row.1; - } - SubComponentType::CERT => { - batch_cert_blob = row.1; - } - _ => Err(KsError::sys()).context("Unknown or incorrect subcomponent type.")?, - } - } - Ok(Some(( - key_id_guard, - CertificateChain { - private_key: ZVec::try_from(km_blob)?, - batch_cert: batch_cert_blob, - cert_chain: cert_chain_blob, - }, - ))) - } - /// Updates the alias column of the given key id `newid` with the given alias, /// and atomically, removes the alias, domain, and namespace from another row /// with the same alias-domain-namespace tuple if such row exits. @@ -2879,33 +2392,33 @@ impl KeystoreDB { "DELETE FROM persistent.keymetadata WHERE keyentryid IN ( SELECT id FROM persistent.keyentry - WHERE domain = ? AND namespace = ? AND (key_type = ? OR key_type = ?) + WHERE domain = ? AND namespace = ? AND key_type = ? );", - params![domain.0, namespace, KeyType::Client, KeyType::Attestation], + params![domain.0, namespace, KeyType::Client], ) .context("Trying to delete keymetadata.")?; tx.execute( "DELETE FROM persistent.keyparameter WHERE keyentryid IN ( SELECT id FROM persistent.keyentry - WHERE domain = ? AND namespace = ? AND (key_type = ? OR key_type = ?) + WHERE domain = ? AND namespace = ? AND key_type = ? );", - params![domain.0, namespace, KeyType::Client, KeyType::Attestation], + params![domain.0, namespace, KeyType::Client], ) .context("Trying to delete keyparameters.")?; tx.execute( "DELETE FROM persistent.grant WHERE keyentryid IN ( SELECT id FROM persistent.keyentry - WHERE domain = ? AND namespace = ? AND (key_type = ? OR key_type = ?) + WHERE domain = ? AND namespace = ? AND key_type = ? );", - params![domain.0, namespace, KeyType::Client, KeyType::Attestation], + params![domain.0, namespace, KeyType::Client], ) .context("Trying to delete grants.")?; tx.execute( "DELETE FROM persistent.keyentry - WHERE domain = ? AND namespace = ? AND (key_type = ? OR key_type = ?);", - params![domain.0, namespace, KeyType::Client, KeyType::Attestation], + WHERE domain = ? AND namespace = ? AND key_type = ?;", + params![domain.0, namespace, KeyType::Client], ) .context("Trying to delete keyentry.")?; Ok(()).need_gc() @@ -3030,6 +2543,70 @@ impl KeystoreDB { .context(ks_err!()) } + /// Deletes all auth-bound keys, i.e. keys that require user authentication, for the given user. + /// This runs when the user's lock screen is being changed to Swipe or None. + /// + /// This intentionally does *not* delete keys that require that the device be unlocked, unless + /// such keys also require user authentication. Keystore's concept of user authentication is + /// fairly strong, and it requires that keys that require authentication be deleted as soon as + /// authentication is no longer possible. In contrast, keys that just require that the device + /// be unlocked should remain usable when the lock screen is set to Swipe or None, as the device + /// is always considered "unlocked" in that case. + pub fn unbind_auth_bound_keys_for_user(&mut self, user_id: u32) -> Result<()> { + let _wp = wd::watch_millis("KeystoreDB::unbind_auth_bound_keys_for_user", 500); + + self.with_transaction(TransactionBehavior::Immediate, |tx| { + let mut stmt = tx + .prepare(&format!( + "SELECT id from persistent.keyentry + WHERE key_type = ? + AND domain = ? + AND cast ( (namespace/{aid_user_offset}) as int) = ? + AND state = ?;", + aid_user_offset = AID_USER_OFFSET + )) + .context(concat!( + "In unbind_auth_bound_keys_for_user. ", + "Failed to prepare the query to find the keys created by apps." + ))?; + + let mut rows = stmt + .query(params![KeyType::Client, Domain::APP.0 as u32, user_id, KeyLifeCycle::Live,]) + .context(ks_err!("Failed to query the keys created by apps."))?; + + let mut key_ids: Vec<i64> = Vec::new(); + db_utils::with_rows_extract_all(&mut rows, |row| { + key_ids + .push(row.get(0).context("Failed to read key id of a key created by an app.")?); + Ok(()) + }) + .context(ks_err!())?; + + let mut notify_gc = false; + let mut num_unbound = 0; + for key_id in key_ids { + // Load the key parameters and filter out non-auth-bound keys. To identify + // auth-bound keys, use the presence of UserSecureID. The absence of NoAuthRequired + // could also be used, but UserSecureID is what Keystore treats as authoritative + // when actually enforcing the key parameters (it might not matter, though). + let params = Self::load_key_parameters(key_id, tx) + .context("Failed to load key parameters.")?; + let is_auth_bound_key = params.iter().any(|kp| { + matches!(kp.key_parameter_value(), KeyParameterValue::UserSecureID(_)) + }); + if is_auth_bound_key { + notify_gc = Self::mark_unreferenced(tx, key_id) + .context("In unbind_auth_bound_keys_for_user.")? + || notify_gc; + num_unbound += 1; + } + } + log::info!("Deleting {num_unbound} auth-bound keys for user {user_id}"); + Ok(()).do_gc(notify_gc) + }) + .context(ks_err!()) + } + fn load_key_components( tx: &Transaction, load_bits: KeyEntryLoadBits, @@ -3058,32 +2635,50 @@ impl KeystoreDB { }) } - /// Returns a list of KeyDescriptors in the selected domain/namespace. + /// Returns a list of KeyDescriptors in the selected domain/namespace whose + /// aliases are greater than the specified 'start_past_alias'. If no value + /// is provided, returns all KeyDescriptors. /// The key descriptors will have the domain, nspace, and alias field set. + /// The returned list will be sorted by alias. /// Domain must be APP or SELINUX, the caller must make sure of that. - pub fn list( + pub fn list_past_alias( &mut self, domain: Domain, namespace: i64, key_type: KeyType, + start_past_alias: Option<&str>, ) -> Result<Vec<KeyDescriptor>> { - let _wp = wd::watch_millis("KeystoreDB::list", 500); + let _wp = wd::watch_millis("KeystoreDB::list_past_alias", 500); - self.with_transaction(TransactionBehavior::Deferred, |tx| { - let mut stmt = tx - .prepare( - "SELECT alias FROM persistent.keyentry + let query = format!( + "SELECT DISTINCT alias FROM persistent.keyentry WHERE domain = ? AND namespace = ? AND alias IS NOT NULL AND state = ? - AND key_type = ?;", - ) - .context(ks_err!("Failed to prepare."))?; + AND key_type = ? + {} + ORDER BY alias ASC;", + if start_past_alias.is_some() { " AND alias > ?" } else { "" } + ); - let mut rows = stmt - .query(params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type]) - .context(ks_err!("Failed to query."))?; + self.with_transaction(TransactionBehavior::Deferred, |tx| { + let mut stmt = tx.prepare(&query).context(ks_err!("Failed to prepare."))?; + + let mut rows = match start_past_alias { + Some(past_alias) => stmt + .query(params![ + domain.0 as u32, + namespace, + KeyLifeCycle::Live, + key_type, + past_alias + ]) + .context(ks_err!("Failed to query."))?, + None => stmt + .query(params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type,]) + .context(ks_err!("Failed to query."))?, + }; let mut descriptors: Vec<KeyDescriptor> = Vec::new(); db_utils::with_rows_extract_all(&mut rows, |row| { @@ -3100,6 +2695,33 @@ impl KeystoreDB { }) } + /// Returns a number of KeyDescriptors in the selected domain/namespace. + /// Domain must be APP or SELINUX, the caller must make sure of that. + pub fn count_keys( + &mut self, + domain: Domain, + namespace: i64, + key_type: KeyType, + ) -> Result<usize> { + let _wp = wd::watch_millis("KeystoreDB::countKeys", 500); + + let num_keys = self.with_transaction(TransactionBehavior::Deferred, |tx| { + tx.query_row( + "SELECT COUNT(alias) FROM persistent.keyentry + WHERE domain = ? + AND namespace = ? + AND alias IS NOT NULL + AND state = ? + AND key_type = ?;", + params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type], + |row| row.get(0), + ) + .context(ks_err!("Failed to count number of keys.")) + .no_gc() + })?; + Ok(num_keys) + } + /// Adds a grant to the grant table. /// Like `load_key_entry` this function loads the access tuple before /// it uses the callback for a permission check. Upon success, @@ -3232,33 +2854,16 @@ impl KeystoreDB { /// Insert or replace the auth token based on (user_id, auth_id, auth_type) pub fn insert_auth_token(&mut self, auth_token: &HardwareAuthToken) { - self.perboot.insert_auth_token_entry(AuthTokenEntry::new( - auth_token.clone(), - MonotonicRawTime::now(), - )) + self.perboot + .insert_auth_token_entry(AuthTokenEntry::new(auth_token.clone(), BootTime::now())) } /// Find the newest auth token matching the given predicate. - pub fn find_auth_token_entry<F>(&self, p: F) -> Option<(AuthTokenEntry, MonotonicRawTime)> + pub fn find_auth_token_entry<F>(&self, p: F) -> Option<AuthTokenEntry> where F: Fn(&AuthTokenEntry) -> bool, { - self.perboot.find_auth_token_entry(p).map(|entry| (entry, self.get_last_off_body())) - } - - /// Insert last_off_body into the metadata table at the initialization of auth token table - pub fn insert_last_off_body(&self, last_off_body: MonotonicRawTime) { - self.perboot.set_last_off_body(last_off_body) - } - - /// Update last_off_body when on_device_off_body is called - pub fn update_last_off_body(&self, last_off_body: MonotonicRawTime) { - self.perboot.set_last_off_body(last_off_body) - } - - /// Get last_off_body time when finding auth tokens - fn get_last_off_body(&self) -> MonotonicRawTime { - self.perboot.get_last_off_body() + self.perboot.find_auth_token_entry(p) } /// Load descriptor of a key by key id @@ -3284,6 +2889,75 @@ impl KeystoreDB { }) .context(ks_err!()) } + + /// Returns a list of app UIDs that have keys authenticated by the given secure_user_id + /// (for the given user_id). + /// This is helpful for finding out which apps will have their keys invalidated when + /// the user changes biometrics enrollment or removes their LSKF. + pub fn get_app_uids_affected_by_sid( + &mut self, + user_id: i32, + secure_user_id: i64, + ) -> Result<Vec<i64>> { + let _wp = wd::watch_millis("KeystoreDB::get_app_uids_affected_by_sid", 500); + + let key_ids_and_app_uids = self.with_transaction(TransactionBehavior::Immediate, |tx| { + let mut stmt = tx + .prepare(&format!( + "SELECT id, namespace from persistent.keyentry + WHERE key_type = ? + AND domain = ? + AND cast ( (namespace/{AID_USER_OFFSET}) as int) = ? + AND state = ?;", + )) + .context(concat!( + "In get_app_uids_affected_by_sid, ", + "failed to prepare the query to find the keys created by apps." + ))?; + + let mut rows = stmt + .query(params![KeyType::Client, Domain::APP.0 as u32, user_id, KeyLifeCycle::Live,]) + .context(ks_err!("Failed to query the keys created by apps."))?; + + let mut key_ids_and_app_uids: HashMap<i64, i64> = Default::default(); + db_utils::with_rows_extract_all(&mut rows, |row| { + key_ids_and_app_uids.insert( + row.get(0).context("Failed to read key id of a key created by an app.")?, + row.get(1).context("Failed to read the app uid")?, + ); + Ok(()) + })?; + Ok(key_ids_and_app_uids).no_gc() + })?; + let mut app_uids_affected_by_sid: HashSet<i64> = Default::default(); + for (key_id, app_uid) in key_ids_and_app_uids { + // Read the key parameters for each key in its own transaction. It is OK to ignore + // an error to get the properties of a particular key since it might have been deleted + // under our feet after the previous transaction concluded. If the key was deleted + // then it is no longer applicable if it was auth-bound or not. + if let Ok(is_key_bound_to_sid) = + self.with_transaction(TransactionBehavior::Immediate, |tx| { + let params = Self::load_key_parameters(key_id, tx) + .context("Failed to load key parameters.")?; + // Check if the key is bound to this secure user ID. + let is_key_bound_to_sid = params.iter().any(|kp| { + matches!( + kp.key_parameter_value(), + KeyParameterValue::UserSecureID(sid) if *sid == secure_user_id + ) + }); + Ok(is_key_bound_to_sid).no_gc() + }) + { + if is_key_bound_to_sid { + app_uids_affected_by_sid.insert(app_uid); + } + } + } + + let app_uids_vec: Vec<i64> = app_uids_affected_by_sid.into_iter().collect(); + Ok(app_uids_vec) + } } #[cfg(test)] @@ -3296,7 +2970,7 @@ pub mod tests { }; use crate::key_perm_set; use crate::permission::{KeyPerm, KeyPermSet}; - use crate::super_key::{SuperKeyManager, USER_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType}; + use crate::super_key::{SuperKeyManager, USER_AFTER_FIRST_UNLOCK_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType}; use keystore2_test_utils::TempDir; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, @@ -3305,13 +2979,12 @@ pub mod tests { use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ Timestamp::Timestamp, }; - use rusqlite::NO_PARAMS; use rusqlite::TransactionBehavior; use std::cell::RefCell; use std::collections::BTreeMap; use std::fmt::Write; use std::sync::atomic::{AtomicU8, Ordering}; - use std::sync::{Arc, RwLock}; + use std::sync::Arc; use std::thread; use std::time::{Duration, SystemTime}; use crate::utils::AesGcm; @@ -3328,18 +3001,6 @@ pub mod tests { Ok(db) } - fn new_test_db_with_gc<F>(path: &Path, cb: F) -> Result<KeystoreDB> - where - F: Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static, - { - let super_key: Arc<RwLock<SuperKeyManager>> = Default::default(); - - let gc_db = KeystoreDB::new(path, None).expect("Failed to open test gc db_connection."); - let gc = Gc::new_init_with(Default::default(), move || (Box::new(cb), gc_db, super_key)); - - KeystoreDB::new(path, Some(Arc::new(gc))) - } - fn rebind_alias( db: &mut KeystoreDB, newid: &KeyIdGuard, @@ -3356,7 +3017,7 @@ pub mod tests { #[test] fn datetime() -> Result<()> { let conn = Connection::open_in_memory()?; - conn.execute("CREATE TABLE test (ts DATETIME);", NO_PARAMS)?; + conn.execute("CREATE TABLE test (ts DATETIME);", [])?; let now = SystemTime::now(); let duration = Duration::from_secs(1000); let then = now.checked_sub(duration).unwrap(); @@ -3366,7 +3027,7 @@ pub mod tests { params![DateTime::try_from(now)?, DateTime::try_from(then)?, DateTime::try_from(soon)?], )?; let mut stmt = conn.prepare("SELECT ts FROM test ORDER BY ts ASC;")?; - let mut rows = stmt.query(NO_PARAMS)?; + let mut rows = stmt.query([])?; assert_eq!(DateTime::try_from(then)?, rows.next()?.unwrap().get(0)?); assert_eq!(DateTime::try_from(now)?, rows.next()?.unwrap().get(0)?); assert_eq!(DateTime::try_from(soon)?, rows.next()?.unwrap().get(0)?); @@ -3517,251 +3178,6 @@ pub mod tests { } #[test] - fn test_add_unsigned_key() -> Result<()> { - let mut db = new_test_db()?; - let public_key: Vec<u8> = vec![0x01, 0x02, 0x03]; - let private_key: Vec<u8> = vec![0x04, 0x05, 0x06]; - let raw_public_key: Vec<u8> = vec![0x07, 0x08, 0x09]; - db.create_attestation_key_entry( - &public_key, - &raw_public_key, - &private_key, - &KEYSTORE_UUID, - )?; - let keys = db.fetch_unsigned_attestation_keys(5, &KEYSTORE_UUID)?; - assert_eq!(keys.len(), 1); - assert_eq!(keys[0], public_key); - Ok(()) - } - - #[test] - fn test_store_signed_attestation_certificate_chain() -> Result<()> { - let mut db = new_test_db()?; - let expiration_date: i64 = - SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64 - + EXPIRATION_BUFFER_MS - + 10000; - let namespace: i64 = 30; - let base_byte: u8 = 1; - let loaded_values = - load_attestation_key_pool(&mut db, expiration_date, namespace, base_byte)?; - let chain = - db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace, &KEYSTORE_UUID)?; - assert!(chain.is_some()); - let (_, cert_chain) = chain.unwrap(); - assert_eq!(cert_chain.private_key.to_vec(), loaded_values.priv_key); - assert_eq!(cert_chain.batch_cert, loaded_values.batch_cert); - assert_eq!(cert_chain.cert_chain, loaded_values.cert_chain); - Ok(()) - } - - #[test] - fn test_get_attestation_pool_status() -> Result<()> { - let mut db = new_test_db()?; - let namespace: i64 = 30; - load_attestation_key_pool( - &mut db, 10, /* expiration */ - namespace, 0x01, /* base_byte */ - )?; - load_attestation_key_pool(&mut db, 20 /* expiration */, namespace + 1, 0x02)?; - load_attestation_key_pool(&mut db, 40 /* expiration */, namespace + 2, 0x03)?; - let mut status = db.get_attestation_pool_status(9 /* expiration */, &KEYSTORE_UUID)?; - assert_eq!(status.expiring, 0); - assert_eq!(status.attested, 3); - assert_eq!(status.unassigned, 0); - assert_eq!(status.total, 3); - assert_eq!( - db.get_attestation_pool_status(15 /* expiration */, &KEYSTORE_UUID)?.expiring, - 1 - ); - assert_eq!( - db.get_attestation_pool_status(25 /* expiration */, &KEYSTORE_UUID)?.expiring, - 2 - ); - assert_eq!( - db.get_attestation_pool_status(60 /* expiration */, &KEYSTORE_UUID)?.expiring, - 3 - ); - let public_key: Vec<u8> = vec![0x01, 0x02, 0x03]; - let private_key: Vec<u8> = vec![0x04, 0x05, 0x06]; - let raw_public_key: Vec<u8> = vec![0x07, 0x08, 0x09]; - let cert_chain: Vec<u8> = vec![0x0a, 0x0b, 0x0c]; - let batch_cert: Vec<u8> = vec![0x0d, 0x0e, 0x0f]; - db.create_attestation_key_entry( - &public_key, - &raw_public_key, - &private_key, - &KEYSTORE_UUID, - )?; - status = db.get_attestation_pool_status(0 /* expiration */, &KEYSTORE_UUID)?; - assert_eq!(status.attested, 3); - assert_eq!(status.unassigned, 0); - assert_eq!(status.total, 4); - db.store_signed_attestation_certificate_chain( - &raw_public_key, - &batch_cert, - &cert_chain, - 20, - &KEYSTORE_UUID, - )?; - status = db.get_attestation_pool_status(0 /* expiration */, &KEYSTORE_UUID)?; - assert_eq!(status.attested, 4); - assert_eq!(status.unassigned, 1); - assert_eq!(status.total, 4); - Ok(()) - } - - #[test] - fn test_remove_expired_certs() -> Result<()> { - let temp_dir = - TempDir::new("test_remove_expired_certs_").expect("Failed to create temp dir."); - let mut db = new_test_db_with_gc(temp_dir.path(), |_, _| Ok(()))?; - let expiration_date: i64 = - SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64 - + EXPIRATION_BUFFER_MS - + 10000; - let namespace: i64 = 30; - let namespace_del1: i64 = 45; - let namespace_del2: i64 = 60; - let entry_values = load_attestation_key_pool( - &mut db, - expiration_date, - namespace, - 0x01, /* base_byte */ - )?; - load_attestation_key_pool(&mut db, 45, namespace_del1, 0x02)?; - load_attestation_key_pool(&mut db, expiration_date - 10001, namespace_del2, 0x03)?; - - let blob_entry_row_count: u32 = db - .conn - .query_row("SELECT COUNT(id) FROM persistent.blobentry;", NO_PARAMS, |row| row.get(0)) - .expect("Failed to get blob entry row count."); - // We expect 9 rows here because there are three blobs per attestation key, i.e., - // one key, one certificate chain, and one certificate. - assert_eq!(blob_entry_row_count, 9); - - assert_eq!(db.delete_expired_attestation_keys()?, 2); - - let mut cert_chain = - db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace, &KEYSTORE_UUID)?; - assert!(cert_chain.is_some()); - let (_, value) = cert_chain.unwrap(); - assert_eq!(entry_values.batch_cert, value.batch_cert); - assert_eq!(entry_values.cert_chain, value.cert_chain); - assert_eq!(entry_values.priv_key, value.private_key.to_vec()); - - cert_chain = db.retrieve_attestation_key_and_cert_chain( - Domain::APP, - namespace_del1, - &KEYSTORE_UUID, - )?; - assert!(cert_chain.is_none()); - cert_chain = db.retrieve_attestation_key_and_cert_chain( - Domain::APP, - namespace_del2, - &KEYSTORE_UUID, - )?; - assert!(cert_chain.is_none()); - - // Give the garbage collector half a second to catch up. - std::thread::sleep(Duration::from_millis(500)); - - let blob_entry_row_count: u32 = db - .conn - .query_row("SELECT COUNT(id) FROM persistent.blobentry;", NO_PARAMS, |row| row.get(0)) - .expect("Failed to get blob entry row count."); - // There shound be 3 blob entries left, because we deleted two of the attestation - // key entries with three blobs each. - assert_eq!(blob_entry_row_count, 3); - - Ok(()) - } - - fn compare_rem_prov_values( - expected: &RemoteProvValues, - actual: Option<(KeyIdGuard, CertificateChain)>, - ) { - assert!(actual.is_some()); - let (_, value) = actual.unwrap(); - assert_eq!(expected.batch_cert, value.batch_cert); - assert_eq!(expected.cert_chain, value.cert_chain); - assert_eq!(expected.priv_key, value.private_key.to_vec()); - } - - #[test] - fn test_dont_remove_valid_certs() -> Result<()> { - let temp_dir = - TempDir::new("test_remove_expired_certs_").expect("Failed to create temp dir."); - let mut db = new_test_db_with_gc(temp_dir.path(), |_, _| Ok(()))?; - let expiration_date: i64 = - SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64 - + EXPIRATION_BUFFER_MS - + 10000; - let namespace1: i64 = 30; - let namespace2: i64 = 45; - let namespace3: i64 = 60; - let entry_values1 = load_attestation_key_pool( - &mut db, - expiration_date, - namespace1, - 0x01, /* base_byte */ - )?; - let entry_values2 = - load_attestation_key_pool(&mut db, expiration_date + 40000, namespace2, 0x02)?; - let entry_values3 = - load_attestation_key_pool(&mut db, expiration_date - 9000, namespace3, 0x03)?; - - let blob_entry_row_count: u32 = db - .conn - .query_row("SELECT COUNT(id) FROM persistent.blobentry;", NO_PARAMS, |row| row.get(0)) - .expect("Failed to get blob entry row count."); - // We expect 9 rows here because there are three blobs per attestation key, i.e., - // one key, one certificate chain, and one certificate. - assert_eq!(blob_entry_row_count, 9); - - let mut cert_chain = - db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace1, &KEYSTORE_UUID)?; - compare_rem_prov_values(&entry_values1, cert_chain); - - cert_chain = - db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace2, &KEYSTORE_UUID)?; - compare_rem_prov_values(&entry_values2, cert_chain); - - cert_chain = - db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace3, &KEYSTORE_UUID)?; - compare_rem_prov_values(&entry_values3, cert_chain); - - // Give the garbage collector half a second to catch up. - std::thread::sleep(Duration::from_millis(500)); - - let blob_entry_row_count: u32 = db - .conn - .query_row("SELECT COUNT(id) FROM persistent.blobentry;", NO_PARAMS, |row| row.get(0)) - .expect("Failed to get blob entry row count."); - // There shound be 9 blob entries left, because all three keys are valid with - // three blobs each. - assert_eq!(blob_entry_row_count, 9); - - Ok(()) - } - #[test] - fn test_delete_all_attestation_keys() -> Result<()> { - let mut db = new_test_db()?; - load_attestation_key_pool(&mut db, 45 /* expiration */, 1 /* namespace */, 0x02)?; - load_attestation_key_pool(&mut db, 80 /* expiration */, 2 /* namespace */, 0x03)?; - db.create_key_entry(&Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?; - let result = db.delete_all_attestation_keys()?; - - // Give the garbage collector half a second to catch up. - std::thread::sleep(Duration::from_millis(500)); - - // Attestation keys should be deleted, and the regular key should remain. - assert_eq!(result, 2); - - Ok(()) - } - - #[test] fn test_rebind_alias() -> Result<()> { fn extractor( ke: &KeyEntryRow, @@ -3962,15 +3378,9 @@ pub mod tests { let mut stmt = db .conn .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?; - let mut rows = - stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>(NO_PARAMS, |row| { - Ok(( - row.get(0)?, - row.get(1)?, - row.get(2)?, - KeyPermSet::from(row.get::<_, i32>(3)?), - )) - })?; + let mut rows = stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>([], |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, KeyPermSet::from(row.get::<_, i32>(3)?))) + })?; let r = rows.next().unwrap().unwrap(); assert_eq!(r, (next_random, GRANTEE_UID, 1, PVEC1)); @@ -4014,7 +3424,7 @@ pub mod tests { ORDER BY subcomponent_type ASC;", )?; let mut rows = stmt - .query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>(NO_PARAMS, |row| { + .query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>([], |row| { Ok(((row.get(0)?, row.get(1)?, row.get(2)?), row.get(3)?)) })?; let (r, id) = rows.next().unwrap().unwrap(); @@ -5047,7 +4457,7 @@ pub mod tests { }) .collect(); list_o_descriptors.sort(); - let mut list_result = db.list(*domain, *namespace, KeyType::Client)?; + let mut list_result = db.list_past_alias(*domain, *namespace, KeyType::Client, None)?; list_result.sort(); assert_eq!(list_o_descriptors, list_result); @@ -5077,7 +4487,10 @@ pub mod tests { loaded_entries.sort_unstable(); assert_eq!(list_o_ids, loaded_entries); } - assert_eq!(Vec::<KeyDescriptor>::new(), db.list(Domain::SELINUX, 101, KeyType::Client)?); + assert_eq!( + Vec::<KeyDescriptor>::new(), + db.list_past_alias(Domain::SELINUX, 101, KeyType::Client, None)? + ); Ok(()) } @@ -5112,7 +4525,7 @@ pub mod tests { fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> { db.conn .prepare("SELECT * FROM persistent.keyentry;")? - .query_map(NO_PARAMS, |row| { + .query_map([], |row| { Ok(KeyEntryRow { id: row.get(0)?, key_type: row.get(1)?, @@ -5127,39 +4540,17 @@ pub mod tests { .collect::<Result<Vec<_>>>() } - struct RemoteProvValues { - cert_chain: Vec<u8>, - priv_key: Vec<u8>, - batch_cert: Vec<u8>, - } - - fn load_attestation_key_pool( - db: &mut KeystoreDB, - expiration_date: i64, - namespace: i64, - base_byte: u8, - ) -> Result<RemoteProvValues> { - let public_key: Vec<u8> = vec![base_byte, 0x02 * base_byte]; - let cert_chain: Vec<u8> = vec![0x03 * base_byte, 0x04 * base_byte]; - let priv_key: Vec<u8> = vec![0x05 * base_byte, 0x06 * base_byte]; - let raw_public_key: Vec<u8> = vec![0x0b * base_byte, 0x0c * base_byte]; - let batch_cert: Vec<u8> = vec![base_byte * 0x0d, base_byte * 0x0e]; - db.create_attestation_key_entry(&public_key, &raw_public_key, &priv_key, &KEYSTORE_UUID)?; - db.store_signed_attestation_certificate_chain( - &raw_public_key, - &batch_cert, - &cert_chain, - expiration_date, - &KEYSTORE_UUID, - )?; - db.assign_attestation_key(Domain::APP, namespace, &KEYSTORE_UUID)?; - Ok(RemoteProvValues { cert_chain, priv_key, batch_cert }) + fn make_test_params(max_usage_count: Option<i32>) -> Vec<KeyParameter> { + make_test_params_with_sids(max_usage_count, &[42]) } // Note: The parameters and SecurityLevel associations are nonsensical. This // collection is only used to check if the parameters are preserved as expected by the // database. - fn make_test_params(max_usage_count: Option<i32>) -> Vec<KeyParameter> { + fn make_test_params_with_sids( + max_usage_count: Option<i32>, + user_secure_ids: &[i64], + ) -> Vec<KeyParameter> { let mut params = vec![ KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::TRUSTED_ENVIRONMENT), KeyParameter::new( @@ -5258,7 +4649,6 @@ pub mod tests { SecurityLevel::TRUSTED_ENVIRONMENT, ), KeyParameter::new(KeyParameterValue::UserID(1), SecurityLevel::STRONGBOX), - KeyParameter::new(KeyParameterValue::UserSecureID(42), SecurityLevel::STRONGBOX), KeyParameter::new( KeyParameterValue::NoAuthRequired, SecurityLevel::TRUSTED_ENVIRONMENT, @@ -5386,15 +4776,33 @@ pub mod tests { SecurityLevel::SOFTWARE, )); } + + for sid in user_secure_ids.iter() { + params.push(KeyParameter::new( + KeyParameterValue::UserSecureID(*sid), + SecurityLevel::STRONGBOX, + )); + } params } - fn make_test_key_entry( + pub fn make_test_key_entry( + db: &mut KeystoreDB, + domain: Domain, + namespace: i64, + alias: &str, + max_usage_count: Option<i32>, + ) -> Result<KeyIdGuard> { + make_test_key_entry_with_sids(db, domain, namespace, alias, max_usage_count, &[42]) + } + + pub fn make_test_key_entry_with_sids( db: &mut KeystoreDB, domain: Domain, namespace: i64, alias: &str, max_usage_count: Option<i32>, + sids: &[i64], ) -> Result<KeyIdGuard> { let key_id = db.create_key_entry(&domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?; let mut blob_metadata = BlobMetaData::new(); @@ -5413,7 +4821,7 @@ pub mod tests { db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?; db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?; - let params = make_test_params(max_usage_count); + let params = make_test_params_with_sids(max_usage_count, sids); db.insert_keyparameter(&key_id, ¶ms)?; let mut metadata = KeyMetaData::new(); @@ -5448,7 +4856,7 @@ pub mod tests { } } - fn make_bootlevel_key_entry( + pub fn make_bootlevel_key_entry( db: &mut KeystoreDB, domain: Domain, namespace: i64, @@ -5483,6 +4891,53 @@ pub mod tests { Ok(key_id) } + // Creates an app key that is marked as being superencrypted by the given + // super key ID and that has the given authentication and unlocked device + // parameters. This does not actually superencrypt the key blob. + fn make_superencrypted_key_entry( + db: &mut KeystoreDB, + namespace: i64, + alias: &str, + requires_authentication: bool, + requires_unlocked_device: bool, + super_key_id: i64, + ) -> Result<KeyIdGuard> { + let domain = Domain::APP; + let key_id = db.create_key_entry(&domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?; + + let mut blob_metadata = BlobMetaData::new(); + blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); + blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id))); + db.set_blob( + &key_id, + SubComponentType::KEY_BLOB, + Some(TEST_KEY_BLOB), + Some(&blob_metadata), + )?; + + let mut params = vec![]; + if requires_unlocked_device { + params.push(KeyParameter::new( + KeyParameterValue::UnlockedDeviceRequired, + SecurityLevel::TRUSTED_ENVIRONMENT, + )); + } + if requires_authentication { + params.push(KeyParameter::new( + KeyParameterValue::UserSecureID(42), + SecurityLevel::TRUSTED_ENVIRONMENT, + )); + } + db.insert_keyparameter(&key_id, ¶ms)?; + + let mut metadata = KeyMetaData::new(); + metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); + db.insert_key_metadata(&key_id, &metadata)?; + + rebind_alias(db, &key_id, alias, domain, namespace)?; + Ok(key_id) + } + fn make_bootlevel_test_key_entry_test_vector(key_id: i64, logical_only: bool) -> KeyEntry { let mut params = make_test_params(None); params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE)); @@ -5513,7 +4968,7 @@ pub mod tests { "SELECT id, key_type, domain, namespace, alias, state, km_uuid FROM persistent.keyentry;", )?; let rows = stmt.query_map::<(i64, KeyType, i32, i64, String, KeyLifeCycle, Uuid), _, _>( - NO_PARAMS, + [], |row| { Ok(( row.get(0)?, @@ -5542,7 +4997,7 @@ pub mod tests { let mut stmt = db .conn .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?; - let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>(NO_PARAMS, |row| { + let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>([], |row| { Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) })?; @@ -5558,7 +5013,7 @@ pub mod tests { // This allows us to test repeated elements. thread_local! { - static RANDOM_COUNTER: RefCell<i64> = RefCell::new(0); + static RANDOM_COUNTER: RefCell<i64> = const { RefCell::new(0) }; } fn reset_random() { @@ -5576,23 +5031,6 @@ pub mod tests { } #[test] - fn test_last_off_body() -> Result<()> { - let mut db = new_test_db()?; - db.insert_last_off_body(MonotonicRawTime::now()); - let tx = db.conn.transaction_with_behavior(TransactionBehavior::Immediate)?; - tx.commit()?; - let last_off_body_1 = db.get_last_off_body(); - let one_second = Duration::from_secs(1); - thread::sleep(one_second); - db.update_last_off_body(MonotonicRawTime::now()); - let tx2 = db.conn.transaction_with_behavior(TransactionBehavior::Immediate)?; - tx2.commit()?; - let last_off_body_2 = db.get_last_off_body(); - assert!(last_off_body_1 < last_off_body_2); - Ok(()) - } - - #[test] fn test_unbind_keys_for_user() -> Result<()> { let mut db = new_test_db()?; db.unbind_keys_for_user(1, false)?; @@ -5601,11 +5039,11 @@ pub mod tests { make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?; db.unbind_keys_for_user(2, false)?; - assert_eq!(1, db.list(Domain::APP, 110000, KeyType::Client)?.len()); - assert_eq!(0, db.list(Domain::APP, 210000, KeyType::Client)?.len()); + assert_eq!(1, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len()); + assert_eq!(0, db.list_past_alias(Domain::APP, 210000, KeyType::Client, None)?.len()); db.unbind_keys_for_user(1, true)?; - assert_eq!(0, db.list(Domain::APP, 110000, KeyType::Client)?.len()); + assert_eq!(0, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len()); Ok(()) } @@ -5621,11 +5059,13 @@ pub mod tests { let key_name_enc = SuperKeyType { alias: "test_super_key_1", algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + name: "test_super_key_1", }; let key_name_nonenc = SuperKeyType { alias: "test_super_key_2", algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + name: "test_super_key_2", }; // Install two super keys. @@ -5684,6 +5124,71 @@ pub mod tests { Ok(()) } + fn app_key_exists(db: &mut KeystoreDB, nspace: i64, alias: &str) -> Result<bool> { + db.key_exists(Domain::APP, nspace, alias, KeyType::Client) + } + + // Tests the unbind_auth_bound_keys_for_user() function. + #[test] + fn test_unbind_auth_bound_keys_for_user() -> Result<()> { + let mut db = new_test_db()?; + let user_id = 1; + let nspace: i64 = (user_id * AID_USER_OFFSET).into(); + let other_user_id = 2; + let other_user_nspace: i64 = (other_user_id * AID_USER_OFFSET).into(); + let super_key_type = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY; + + // Create a superencryption key. + let super_key = keystore2_crypto::generate_aes256_key()?; + let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); + let (encrypted_super_key, blob_metadata) = + SuperKeyManager::encrypt_with_password(&super_key, &pw)?; + db.store_super_key( + user_id, + super_key_type, + &encrypted_super_key, + &blob_metadata, + &KeyMetaData::new(), + )?; + let super_key_id = db.load_super_key(super_key_type, user_id)?.unwrap().0 .0; + + // Store 4 superencrypted app keys, one for each possible combination of + // (authentication required, unlocked device required). + make_superencrypted_key_entry(&mut db, nspace, "noauth_noud", false, false, super_key_id)?; + make_superencrypted_key_entry(&mut db, nspace, "noauth_ud", false, true, super_key_id)?; + make_superencrypted_key_entry(&mut db, nspace, "auth_noud", true, false, super_key_id)?; + make_superencrypted_key_entry(&mut db, nspace, "auth_ud", true, true, super_key_id)?; + assert!(app_key_exists(&mut db, nspace, "noauth_noud")?); + assert!(app_key_exists(&mut db, nspace, "noauth_ud")?); + assert!(app_key_exists(&mut db, nspace, "auth_noud")?); + assert!(app_key_exists(&mut db, nspace, "auth_ud")?); + + // Also store a key for a different user that requires authentication. + make_superencrypted_key_entry( + &mut db, + other_user_nspace, + "auth_ud", + true, + true, + super_key_id, + )?; + + db.unbind_auth_bound_keys_for_user(user_id)?; + + // Verify that only the user's app keys that require authentication were + // deleted. Keys that require an unlocked device but not authentication + // should *not* have been deleted, nor should the super key have been + // deleted, nor should other users' keys have been deleted. + assert!(db.load_super_key(super_key_type, user_id)?.is_some()); + assert!(app_key_exists(&mut db, nspace, "noauth_noud")?); + assert!(app_key_exists(&mut db, nspace, "noauth_ud")?); + assert!(!app_key_exists(&mut db, nspace, "auth_noud")?); + assert!(!app_key_exists(&mut db, nspace, "auth_ud")?); + assert!(app_key_exists(&mut db, other_user_nspace, "auth_ud")?); + + Ok(()) + } + #[test] fn test_store_super_key() -> Result<()> { let mut db = new_test_db()?; @@ -5697,18 +5202,23 @@ pub mod tests { SuperKeyManager::encrypt_with_password(&super_key, &pw)?; db.store_super_key( 1, - &USER_SUPER_KEY, + &USER_AFTER_FIRST_UNLOCK_SUPER_KEY, &encrypted_super_key, &metadata, &KeyMetaData::new(), )?; // Check if super key exists. - assert!(db.key_exists(Domain::APP, 1, USER_SUPER_KEY.alias, KeyType::Super)?); + assert!(db.key_exists( + Domain::APP, + 1, + USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias, + KeyType::Super + )?); - let (_, key_entry) = db.load_super_key(&USER_SUPER_KEY, 1)?.unwrap(); + let (_, key_entry) = db.load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, 1)?.unwrap(); let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry( - USER_SUPER_KEY.algorithm, + USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm, key_entry, &pw, None, @@ -5775,7 +5285,6 @@ pub mod tests { for storage in increased_storage_types { // Verify the expected storage increased. let new = db.get_storage_stat(storage).unwrap(); - let storage = storage; let old = &baseline[&storage.0]; assert!(new.size >= old.size, "{}: {} >= {}", storage.0, new.size, old.size); assert!( @@ -5945,7 +5454,7 @@ pub mod tests { // All three entries are in the database assert_eq!(db.perboot.auth_tokens_len(), 3); // It selected the most recent timestamp - assert_eq!(db.find_auth_token_entry(|_| true).unwrap().0.auth_token.mac, b"mac2".to_vec()); + assert_eq!(db.find_auth_token_entry(|_| true).unwrap().auth_token.mac, b"mac2".to_vec()); Ok(()) } @@ -5964,4 +5473,111 @@ pub mod tests { assert_eq!(db.load_key_descriptor(key_id + 1)?, None); Ok(()) } + + #[test] + fn test_get_list_app_uids_for_sid() -> Result<()> { + let uid: i32 = 1; + let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64); + let first_sid = 667; + let second_sid = 669; + let first_app_id: i64 = 123 + uid_offset; + let second_app_id: i64 = 456 + uid_offset; + let third_app_id: i64 = 789 + uid_offset; + let unrelated_app_id: i64 = 1011 + uid_offset; + let mut db = new_test_db()?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + first_app_id, + TEST_ALIAS, + None, + &[first_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + second_app_id, + "alias2", + None, + &[first_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + second_app_id, + TEST_ALIAS, + None, + &[second_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + third_app_id, + "alias3", + None, + &[second_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + unrelated_app_id, + TEST_ALIAS, + None, + &[], + ) + .context("test_get_list_app_uids_for_sid")?; + + let mut first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?; + first_sid_apps.sort(); + assert_eq!(first_sid_apps, vec![first_app_id, second_app_id]); + let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?; + second_sid_apps.sort(); + assert_eq!(second_sid_apps, vec![second_app_id, third_app_id]); + Ok(()) + } + + #[test] + fn test_get_list_app_uids_with_multiple_sids() -> Result<()> { + let uid: i32 = 1; + let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64); + let first_sid = 667; + let second_sid = 669; + let third_sid = 772; + let first_app_id: i64 = 123 + uid_offset; + let second_app_id: i64 = 456 + uid_offset; + let mut db = new_test_db()?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + first_app_id, + TEST_ALIAS, + None, + &[first_sid, second_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + second_app_id, + "alias2", + None, + &[second_sid, third_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + + let first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?; + assert_eq!(first_sid_apps, vec![first_app_id]); + + let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?; + second_sid_apps.sort(); + assert_eq!(second_sid_apps, vec![first_app_id, second_app_id]); + + let third_sid_apps = db.get_app_uids_affected_by_sid(uid, third_sid)?; + assert_eq!(third_sid_apps, vec![second_app_id]); + Ok(()) + } } diff --git a/keystore2/src/database/perboot.rs b/keystore2/src/database/perboot.rs index 7ff35fa4..4727015f 100644 --- a/keystore2/src/database/perboot.rs +++ b/keystore2/src/database/perboot.rs @@ -13,15 +13,14 @@ // limitations under the License. //! This module implements a per-boot, shared, in-memory storage of auth tokens -//! and last-time-on-body for the main Keystore 2.0 database module. +//! for the main Keystore 2.0 database module. -use super::{AuthTokenEntry, MonotonicRawTime}; +use super::AuthTokenEntry; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType, }; use lazy_static::lazy_static; use std::collections::HashSet; -use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::Arc; use std::sync::RwLock; @@ -62,17 +61,13 @@ impl PartialEq<AuthTokenEntryWrap> for AuthTokenEntryWrap { impl Eq for AuthTokenEntryWrap {} -/// Per-boot state structure. Currently only used to track auth tokens and -/// last-off-body. +/// Per-boot state structure. Currently only used to track auth tokens. #[derive(Default)] pub struct PerbootDB { // We can use a .unwrap() discipline on this lock, because only panicking // while holding a .write() lock will poison it. The only write usage is // an insert call which inserts a pre-constructed pair. auth_tokens: RwLock<HashSet<AuthTokenEntryWrap>>, - // Ordering::Relaxed is appropriate for accessing this atomic, since it - // does not currently need to be synchronized with anything else. - last_off_body: AtomicI64, } lazy_static! { @@ -102,14 +97,6 @@ impl PerbootDB { matches.sort_by_key(|x| x.0.time_received); matches.last().map(|x| x.0.clone()) } - /// Get the last time the device was off the user's body - pub fn get_last_off_body(&self) -> MonotonicRawTime { - MonotonicRawTime(self.last_off_body.load(Ordering::Relaxed)) - } - /// Set the last time the device was off the user's body - pub fn set_last_off_body(&self, last_off_body: MonotonicRawTime) { - self.last_off_body.store(last_off_body.0, Ordering::Relaxed) - } /// Return how many auth tokens are currently tracked. pub fn auth_tokens_len(&self) -> usize { self.auth_tokens.read().unwrap().len() diff --git a/keystore2/src/database/versioning.rs b/keystore2/src/database/versioning.rs index e3a95c8e..2c816f44 100644 --- a/keystore2/src/database/versioning.rs +++ b/keystore2/src/database/versioning.rs @@ -13,21 +13,19 @@ // limitations under the License. use anyhow::{anyhow, Context, Result}; -use rusqlite::{params, OptionalExtension, Transaction, NO_PARAMS}; +use rusqlite::{params, OptionalExtension, Transaction}; pub fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32> { tx.execute( "CREATE TABLE IF NOT EXISTS persistent.version ( id INTEGER PRIMARY KEY, version INTEGER);", - NO_PARAMS, + [], ) .context("In create_or_get_version: Failed to create version table.")?; let version = tx - .query_row("SELECT version FROM persistent.version WHERE id = 0;", NO_PARAMS, |row| { - row.get(0) - }) + .query_row("SELECT version FROM persistent.version WHERE id = 0;", [], |row| row.get(0)) .optional() .context("In create_or_get_version: Failed to read version.")?; @@ -44,7 +42,7 @@ pub fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u .query_row( "SELECT name FROM persistent.sqlite_master WHERE type = 'table' AND name = 'keyentry';", - NO_PARAMS, + [], |_| Ok(()), ) .optional() @@ -94,12 +92,12 @@ where #[cfg(test)] mod test { use super::*; - use rusqlite::{Connection, TransactionBehavior, NO_PARAMS}; + use rusqlite::{Connection, TransactionBehavior}; #[test] fn upgrade_database_test() { let mut conn = Connection::open_in_memory().unwrap(); - conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", NO_PARAMS).unwrap(); + conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", []).unwrap(); let upgraders: Vec<_> = (0..30_u32) .map(move |i| { @@ -125,19 +123,19 @@ mod test { alias BLOB, state INTEGER, km_uuid BLOB);", - NO_PARAMS, + [], ) .unwrap(); } for from in 1..29 { for to in from..30 { - conn.execute("DROP TABLE IF EXISTS persistent.version;", NO_PARAMS).unwrap(); - conn.execute("DROP TABLE IF EXISTS persistent.test;", NO_PARAMS).unwrap(); + conn.execute("DROP TABLE IF EXISTS persistent.version;", []).unwrap(); + conn.execute("DROP TABLE IF EXISTS persistent.test;", []).unwrap(); conn.execute( "CREATE TABLE IF NOT EXISTS persistent.test ( id INTEGER PRIMARY KEY, test_field INTEGER);", - NO_PARAMS, + [], ) .unwrap(); @@ -163,7 +161,7 @@ mod test { to - from, conn.query_row( "SELECT COUNT(test_field) FROM persistent.test;", - NO_PARAMS, + [], |row| row.get(0) ) .unwrap() @@ -188,7 +186,7 @@ mod test { #[test] fn create_or_get_version_new_database() { let mut conn = Connection::open_in_memory().unwrap(); - conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", NO_PARAMS).unwrap(); + conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", []).unwrap(); { let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap(); let version = create_or_get_version(&tx, 3).unwrap(); @@ -202,7 +200,7 @@ mod test { conn.query_row( "SELECT name FROM persistent.sqlite_master WHERE type = 'table' AND name = 'version';", - NO_PARAMS, + [], |row| row.get(0), ) ); @@ -210,18 +208,14 @@ mod test { // There is exactly one row in the version table. assert_eq!( Ok(1), - conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row - .get(0)) + conn.query_row("SELECT COUNT(id) from persistent.version;", [], |row| row.get(0)) ); // The version must be set to 3 assert_eq!( Ok(3), - conn.query_row( - "SELECT version from persistent.version WHERE id = 0;", - NO_PARAMS, - |row| row.get(0) - ) + conn.query_row("SELECT version from persistent.version WHERE id = 0;", [], |row| row + .get(0)) ); // Will subsequent calls to create_or_get_version still return the same version even @@ -236,8 +230,7 @@ mod test { // There is still exactly one row in the version table. assert_eq!( Ok(1), - conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row - .get(0)) + conn.query_row("SELECT COUNT(id) from persistent.version;", [], |row| row.get(0)) ); // Bump the version. @@ -258,25 +251,21 @@ mod test { // There is still exactly one row in the version table. assert_eq!( Ok(1), - conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row - .get(0)) + conn.query_row("SELECT COUNT(id) from persistent.version;", [], |row| row.get(0)) ); // The version must be set to 5 assert_eq!( Ok(5), - conn.query_row( - "SELECT version from persistent.version WHERE id = 0;", - NO_PARAMS, - |row| row.get(0) - ) + conn.query_row("SELECT version from persistent.version WHERE id = 0;", [], |row| row + .get(0)) ); } #[test] fn create_or_get_version_legacy_database() { let mut conn = Connection::open_in_memory().unwrap(); - conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", NO_PARAMS).unwrap(); + conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", []).unwrap(); // A legacy (version 0) database is detected if the keyentry table exists but no // version table. conn.execute( @@ -288,7 +277,7 @@ mod test { alias BLOB, state INTEGER, km_uuid BLOB);", - NO_PARAMS, + [], ) .unwrap(); @@ -306,7 +295,7 @@ mod test { conn.query_row( "SELECT name FROM persistent.sqlite_master WHERE type = 'table' AND name = 'version';", - NO_PARAMS, + [], |row| row.get(0), ) ); @@ -314,18 +303,14 @@ mod test { // There is exactly one row in the version table. assert_eq!( Ok(1), - conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row - .get(0)) + conn.query_row("SELECT COUNT(id) from persistent.version;", [], |row| row.get(0)) ); // The version must be set to 0 assert_eq!( Ok(0), - conn.query_row( - "SELECT version from persistent.version WHERE id = 0;", - NO_PARAMS, - |row| row.get(0) - ) + conn.query_row("SELECT version from persistent.version WHERE id = 0;", [], |row| row + .get(0)) ); // Will subsequent calls to create_or_get_version still return the same version even @@ -340,8 +325,7 @@ mod test { // There is still exactly one row in the version table. assert_eq!( Ok(1), - conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row - .get(0)) + conn.query_row("SELECT COUNT(id) from persistent.version;", [], |row| row.get(0)) ); // Bump the version. @@ -362,18 +346,14 @@ mod test { // There is still exactly one row in the version table. assert_eq!( Ok(1), - conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row - .get(0)) + conn.query_row("SELECT COUNT(id) from persistent.version;", [], |row| row.get(0)) ); // The version must be set to 5 assert_eq!( Ok(5), - conn.query_row( - "SELECT version from persistent.version WHERE id = 0;", - NO_PARAMS, - |row| row.get(0) - ) + conn.query_row("SELECT version from persistent.version WHERE id = 0;", [], |row| row + .get(0)) ); } } diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs index 8d5e9855..95dd026d 100644 --- a/keystore2/src/enforcements.rs +++ b/keystore2/src/enforcements.rs @@ -20,7 +20,7 @@ use crate::globals::{get_timestamp_service, ASYNC_TASK, DB, ENFORCEMENTS}; use crate::key_parameter::{KeyParameter, KeyParameterValue}; use crate::{authorization::Error as AuthzError, super_key::SuperEncryptionType}; use crate::{ - database::{AuthTokenEntry, MonotonicRawTime}, + database::{AuthTokenEntry, BootTime}, globals::SUPER_KEY, }; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ @@ -51,9 +51,9 @@ enum AuthRequestState { /// An outstanding per operation authorization request. OpAuth, /// An outstanding request for per operation authorization and secure timestamp. - TimeStampedOpAuth(Receiver<Result<TimeStampToken, Error>>), + TimeStampedOpAuth(Mutex<Receiver<Result<TimeStampToken, Error>>>), /// An outstanding request for a timestamp token. - TimeStamp(Receiver<Result<TimeStampToken, Error>>), + TimeStamp(Mutex<Receiver<Result<TimeStampToken, Error>>>), } #[derive(Debug)] @@ -64,8 +64,6 @@ struct AuthRequest { hat: Mutex<Option<HardwareAuthToken>>, } -unsafe impl Sync for AuthRequest {} - impl AuthRequest { fn op_auth() -> Arc<Self> { Arc::new(Self { state: AuthRequestState::OpAuth, hat: Mutex::new(None) }) @@ -73,7 +71,7 @@ impl AuthRequest { fn timestamped_op_auth(receiver: Receiver<Result<TimeStampToken, Error>>) -> Arc<Self> { Arc::new(Self { - state: AuthRequestState::TimeStampedOpAuth(receiver), + state: AuthRequestState::TimeStampedOpAuth(Mutex::new(receiver)), hat: Mutex::new(None), }) } @@ -82,7 +80,10 @@ impl AuthRequest { hat: HardwareAuthToken, receiver: Receiver<Result<TimeStampToken, Error>>, ) -> Arc<Self> { - Arc::new(Self { state: AuthRequestState::TimeStamp(receiver), hat: Mutex::new(Some(hat)) }) + Arc::new(Self { + state: AuthRequestState::TimeStamp(Mutex::new(receiver)), + hat: Mutex::new(Some(hat)), + }) } fn add_auth_token(&self, hat: HardwareAuthToken) { @@ -100,7 +101,11 @@ impl AuthRequest { let tst = match &self.state { AuthRequestState::TimeStampedOpAuth(recv) | AuthRequestState::TimeStamp(recv) => { - let result = recv.recv().context("In get_auth_tokens: Sender disconnected.")?; + let result = recv + .lock() + .unwrap() + .recv() + .context("In get_auth_tokens: Sender disconnected.")?; Some(result.context(ks_err!( "Worker responded with error \ from generating timestamp token.", @@ -471,7 +476,6 @@ impl Enforcements { let mut user_id: i32 = -1; let mut user_secure_ids = Vec::<i64>::new(); let mut key_time_out: Option<i64> = None; - let mut allow_while_on_body = false; let mut unlocked_device_required = false; let mut key_usage_limited: Option<i64> = None; let mut confirmation_token_receiver: Option<Arc<Mutex<Option<Receiver<Vec<u8>>>>>> = None; @@ -528,9 +532,6 @@ impl Enforcements { KeyParameterValue::UnlockedDeviceRequired => { unlocked_device_required = true; } - KeyParameterValue::AllowWhileOnBody => { - allow_while_on_body = true; - } KeyParameterValue::UsageCountLimit(_) => { // We don't examine the limit here because this is enforced on finish. // Instead, we store the key_id so that finish can look up the key @@ -598,6 +599,41 @@ impl Enforcements { } } + if android_security_flags::fix_unlocked_device_required_keys_v2() { + let (hat, state) = if user_secure_ids.is_empty() { + (None, DeferredAuthState::NoAuthRequired) + } else if let Some(key_time_out) = key_time_out { + let hat = Self::find_auth_token(|hat: &AuthTokenEntry| match user_auth_type { + Some(auth_type) => hat.satisfies(&user_secure_ids, auth_type), + None => false, // not reachable due to earlier check + }) + .ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) + .context(ks_err!("No suitable auth token found."))?; + let now = BootTime::now(); + let token_age = now + .checked_sub(&hat.time_received()) + .ok_or_else(Error::sys) + .context(ks_err!( + "Overflow while computing Auth token validity. \ + Validity cannot be established." + ))?; + + if token_age.seconds() > key_time_out { + return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) + .context(ks_err!("matching auth token is expired.")); + } + let state = if requires_timestamp { + DeferredAuthState::TimeStampRequired(hat.auth_token().clone()) + } else { + DeferredAuthState::NoAuthRequired + }; + (Some(hat.take_auth_token()), state) + } else { + (None, DeferredAuthState::OpAuthRequired) + }; + return Ok((hat, AuthInfo { state, key_usage_limited, confirmation_token_receiver })); + } + if !unlocked_device_required && no_auth_required { return Ok(( None, @@ -617,8 +653,8 @@ impl Enforcements { let need_auth_token = timeout_bound || unlocked_device_required; - let hat_and_last_off_body = if need_auth_token { - let hat_and_last_off_body = Self::find_auth_token(|hat: &AuthTokenEntry| { + let hat = if need_auth_token { + let hat = Self::find_auth_token(|hat: &AuthTokenEntry| { if let (Some(auth_type), true) = (user_auth_type, timeout_bound) { hat.satisfies(&user_secure_ids, auth_type) } else { @@ -626,8 +662,7 @@ impl Enforcements { } }); Some( - hat_and_last_off_body - .ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) + hat.ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) .context(ks_err!("No suitable auth token found."))?, ) } else { @@ -635,9 +670,9 @@ impl Enforcements { }; // Now check the validity of the auth token if the key is timeout bound. - let hat = match (hat_and_last_off_body, key_time_out) { - (Some((hat, last_off_body)), Some(key_time_out)) => { - let now = MonotonicRawTime::now(); + let hat = match (hat, key_time_out) { + (Some(hat), Some(key_time_out)) => { + let now = BootTime::now(); let token_age = now .checked_sub(&hat.time_received()) .ok_or_else(Error::sys) @@ -646,15 +681,13 @@ impl Enforcements { Validity cannot be established." ))?; - let on_body_extended = allow_while_on_body && last_off_body < hat.time_received(); - - if token_age.seconds() > key_time_out && !on_body_extended { + if token_age.seconds() > key_time_out { return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) .context(ks_err!("matching auth token is expired.")); } Some(hat) } - (Some((hat, _)), None) => Some(hat), + (Some(hat), None) => Some(hat), // If timeout_bound is true, above code must have retrieved a HAT or returned with // KEY_USER_NOT_AUTHENTICATED. This arm should not be reachable. (None, Some(_)) => panic!("Logical error."), @@ -685,7 +718,7 @@ impl Enforcements { }) } - fn find_auth_token<F>(p: F) -> Option<(AuthTokenEntry, MonotonicRawTime)> + fn find_auth_token<F>(p: F) -> Option<AuthTokenEntry> where F: Fn(&AuthTokenEntry) -> bool, { @@ -769,10 +802,10 @@ impl Enforcements { Candidate { priority: 3, enc_type: SuperEncryptionType::BootLevel(*level) } } KeyParameterValue::UnlockedDeviceRequired if *domain == Domain::APP => { - Candidate { priority: 2, enc_type: SuperEncryptionType::ScreenLockBound } + Candidate { priority: 2, enc_type: SuperEncryptionType::UnlockedDeviceRequired } } KeyParameterValue::UserSecureID(_) if *domain == Domain::APP => { - Candidate { priority: 1, enc_type: SuperEncryptionType::LskfBound } + Candidate { priority: 1, enc_type: SuperEncryptionType::AfterFirstUnlock } } _ => Candidate { priority: 0, enc_type: SuperEncryptionType::None }, }; @@ -805,12 +838,12 @@ impl Enforcements { (challenge == hat.challenge()) && hat.satisfies(&sids, auth_type) }); - let auth_token = if let Some((auth_token_entry, _)) = result { + let auth_token = if let Some(auth_token_entry) = result { auth_token_entry.take_auth_token() } else { // Filter the matching auth tokens by age. if auth_token_max_age_millis != 0 { - let now_in_millis = MonotonicRawTime::now(); + let now_in_millis = BootTime::now(); let result = Self::find_auth_token(|auth_token_entry: &AuthTokenEntry| { let token_valid = now_in_millis .checked_sub(&auth_token_entry.time_received()) @@ -820,7 +853,7 @@ impl Enforcements { token_valid && auth_token_entry.satisfies(&sids, auth_type) }); - if let Some((auth_token_entry, _)) = result { + if let Some(auth_token_entry) = result { auth_token_entry.take_auth_token() } else { return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND)) @@ -840,6 +873,20 @@ impl Enforcements { get_timestamp_token(challenge).context(ks_err!("Error in getting timestamp token."))?; Ok((auth_token, tst)) } + + /// Finds the most recent received time for an auth token that matches the given secure user id and authenticator + pub fn get_last_auth_time( + &self, + secure_user_id: i64, + auth_type: HardwareAuthenticatorType, + ) -> Option<BootTime> { + let sids: Vec<i64> = vec![secure_user_id]; + + let result = + Self::find_auth_token(|entry: &AuthTokenEntry| entry.satisfies(&sids, auth_type)); + + result.map(|auth_token_entry| auth_token_entry.time_received()) + } } // TODO: Add tests to enforcement module (b/175578618). diff --git a/keystore2/src/entropy.rs b/keystore2/src/entropy.rs index de381875..1dcdc86f 100644 --- a/keystore2/src/entropy.rs +++ b/keystore2/src/entropy.rs @@ -29,7 +29,7 @@ struct FeederInfo { /// Register the entropy feeder as an idle callback. pub fn register_feeder() { crate::globals::ASYNC_TASK.add_idle(|shelf| { - let mut info = shelf.get_mut::<FeederInfo>(); + let info = shelf.get_mut::<FeederInfo>(); let now = Instant::now(); let feed_needed = match info.last_feed { None => true, diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs index 3ca3942a..f0d0d27b 100644 --- a/keystore2/src/error.rs +++ b/keystore2/src/error.rs @@ -13,29 +13,28 @@ // limitations under the License. //! Keystore error provides convenience methods and types for Keystore error handling. -//! Clients of Keystore expect one of two error codes, i.e., a Keystore ResponseCode as -//! defined by the Keystore AIDL interface, or a Keymint ErrorCode as defined by -//! the Keymint HAL specification. -//! This crate provides `Error` which can wrap both. It is to be used -//! internally by Keystore to diagnose error conditions that need to be reported to -//! the client. To report the error condition to the client the Keystore AIDL -//! interface defines a wire type `Result` which is distinctly different from Rust's -//! `enum Result<T,E>`. //! -//! This crate provides the convenience method `map_or_log_err` to convert `anyhow::Error` -//! into this wire type. In addition to handling the conversion of `Error` -//! to the `Result` wire type it handles any other error by mapping it to -//! `ResponseCode::SYSTEM_ERROR` and logs any error condition. +//! Here are some important types and helper functions: //! -//! Keystore functions should use `anyhow::Result` to return error conditions, and -//! context should be added every time an error is forwarded. +//! `Error` type encapsulate Keystore, Keymint, and Binder errors. It is used internally by +//! Keystore to diagnose error conditions that need to be reported to the client. +//! +//! `SerializedError` is used send error codes on the wire. +//! +//! `map_or_log_err` is a convenience method used to convert `anyhow::Error` into `SerializedError` +//! wire type. +//! +//! Keystore functions should use `anyhow::Result` to return error conditions, and context should +//! be added every time an error is forwarded. pub use android_hardware_security_keymint::aidl::android::hardware::security::keymint::ErrorCode::ErrorCode; +use android_security_rkp_aidl::aidl::android::security::rkp::IGetKeyCallback::ErrorCode::ErrorCode as GetKeyErrorCode; pub use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode; use android_system_keystore2::binder::{ ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode, }; use keystore2_selinux as selinux; +use rkpd_client::Error as RkpdError; use std::cmp::PartialEq; use std::ffi::CString; @@ -55,10 +54,6 @@ pub enum Error { /// Wraps a Binder status code. #[error("Binder transaction error {0:?}")] BinderTransaction(StatusCode), - /// Wraps a Remote Provisioning ErrorCode as defined by the IRemotelyProvisionedComponent - /// AIDL interface spec. - #[error("Error::Rp({0:?})")] - Rp(ErrorCode), } impl Error { @@ -73,6 +68,49 @@ impl Error { } } +impl From<RkpdError> for Error { + fn from(e: RkpdError) -> Self { + match e { + RkpdError::RequestCancelled | RkpdError::GetRegistrationFailed => { + Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) + } + RkpdError::GetKeyFailed(e) => { + let response_code = match e { + GetKeyErrorCode::ERROR_UNKNOWN => ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR, + GetKeyErrorCode::ERROR_PERMANENT => ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR, + GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY => { + ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY + } + GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH => { + ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE + } + _ => { + log::error!("Unexpected get key error from rkpd: {e:?}"); + ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR + } + }; + Error::Rc(response_code) + } + RkpdError::RetryableTimeout => Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), + RkpdError::StoreUpgradedKeyFailed | RkpdError::Timeout => { + Error::Rc(ResponseCode::SYSTEM_ERROR) + } + RkpdError::BinderTransaction(s) => Error::BinderTransaction(s), + } + } +} + +/// Maps an `rkpd_client::Error` that is wrapped with an `anyhow::Error` to a keystore2 `Error`. +pub fn wrapped_rkpd_error_to_ks_error(e: &anyhow::Error) -> Error { + match e.downcast_ref::<RkpdError>() { + Some(e) => Error::from(*e), + None => { + log::error!("Failed to downcast the anyhow::Error to rkpd_client::Error: {e:?}"); + Error::Rc(ResponseCode::SYSTEM_ERROR) + } + } +} + /// Helper function to map the binder status we get from calls into KeyMint /// to a Keystore Error. We don't create an anyhow error here to make /// it easier to evaluate KeyMint errors, which we must do in some cases, e.g., @@ -103,16 +141,6 @@ pub fn map_km_error<T>(r: BinderResult<T>) -> Result<T, Error> { }) } -/// Helper function to map the binder status we get from calls into a RemotelyProvisionedComponent -/// to a Keystore Error. We don't create an anyhow error here to make -/// it easier to evaluate service specific errors. -pub fn map_rem_prov_error<T>(r: BinderResult<T>) -> Result<T, Error> { - r.map_err(|s| match s.exception_code() { - ExceptionCode::SERVICE_SPECIFIC => Error::Rp(ErrorCode(s.service_specific_error())), - e_code => Error::Binder(e_code, 0), - }) -} - /// This function is similar to map_km_error only that we don't expect /// any KeyMint error codes, we simply preserve the exception code and optional /// service specific exception. @@ -140,14 +168,6 @@ pub fn map_binder_status_code<T>(r: Result<T, StatusCode>) -> Result<T, Error> { /// /// All error conditions get logged by this function, except for KEY_NOT_FOUND error. /// -/// All `Error::Rc(x)` and `Error::Km(x)` variants get mapped onto a service specific error -/// code of x. This is possible because KeyMint `ErrorCode` errors are always negative and -/// `ResponseCode` codes are always positive. -/// `selinux::Error::PermissionDenied` is mapped on `ResponseCode::PERMISSION_DENIED`. -/// -/// All non `Error` error conditions and the Error::Binder variant get mapped onto -/// ResponseCode::SYSTEM_ERROR`. -/// /// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed /// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it /// typically returns Ok(value). @@ -214,9 +234,9 @@ where result.map_or_else( |e| { let e = map_err(e); - let rc = get_error_code(&e); + let rc = anyhow_error_to_serialized_error(&e); Err(BinderStatus::new_service_specific_error( - rc, + rc.0, anyhow_error_to_cstring(&e).as_deref(), )) }, @@ -224,22 +244,42 @@ where ) } -/// Returns the error code given a reference to the error -pub fn get_error_code(e: &anyhow::Error) -> i32 { +/// This type is used to send error codes on the wire. +/// +/// Errors are squashed into one number space using following rules: +/// - All Keystore and Keymint errors codes are identity mapped. It's possible because by +/// convention Keystore `ResponseCode` errors are positive, and Keymint `ErrorCode` errors are +/// negative. +/// - `selinux::Error::PermissionDenied` is mapped to `ResponseCode::PERMISSION_DENIED`. +/// - All other error conditions, e.g. Binder errors, are mapped to `ResponseCode::SYSTEM_ERROR`. +/// +/// The type should be used to forward all error codes to clients of Keystore AIDL interface and to +/// metrics events. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct SerializedError(pub i32); + +/// Returns a SerializedError given a reference to Error. +pub fn error_to_serialized_error(e: &Error) -> SerializedError { + match e { + Error::Rc(rcode) => SerializedError(rcode.0), + Error::Km(ec) => SerializedError(ec.0), + // Binder errors are reported as system error. + Error::Binder(_, _) | Error::BinderTransaction(_) => { + SerializedError(ResponseCode::SYSTEM_ERROR.0) + } + } +} + +/// Returns a SerializedError given a reference to anyhow::Error. +pub fn anyhow_error_to_serialized_error(e: &anyhow::Error) -> SerializedError { let root_cause = e.root_cause(); match root_cause.downcast_ref::<Error>() { - Some(Error::Rc(rcode)) => rcode.0, - Some(Error::Km(ec)) => ec.0, - Some(Error::Rp(_)) => ResponseCode::SYSTEM_ERROR.0, - // If an Error::Binder reaches this stage we report a system error. - // The exception code and possible service specific error will be - // printed in the error log above. - Some(Error::Binder(_, _)) | Some(Error::BinderTransaction(_)) => { - ResponseCode::SYSTEM_ERROR.0 - } + Some(e) => error_to_serialized_error(e), None => match root_cause.downcast_ref::<selinux::Error>() { - Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0, - _ => ResponseCode::SYSTEM_ERROR.0, + Some(selinux::Error::PermissionDenied) => { + SerializedError(ResponseCode::PERMISSION_DENIED.0) + } + _ => SerializedError(ResponseCode::SYSTEM_ERROR.0), }, } } @@ -312,7 +352,7 @@ pub mod tests { android_logger::init_once( android_logger::Config::default() .with_tag("keystore_error_tests") - .with_min_level(log::Level::Debug), + .with_max_level(log::LevelFilter::Debug), ); // All Error::Rc(x) get mapped on a service specific error // code of x. @@ -414,4 +454,35 @@ pub mod tests { expected_error_string ); } + + #[test] + fn rkpd_error_is_in_sync_with_response_code() { + let error_mapping = [ + (RkpdError::RequestCancelled, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), + (RkpdError::GetRegistrationFailed, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), + ( + RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_UNKNOWN), + ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR, + ), + ( + RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PERMANENT), + ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR, + ), + ( + RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY), + ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY, + ), + ( + RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH), + ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE, + ), + (RkpdError::StoreUpgradedKeyFailed, ResponseCode::SYSTEM_ERROR), + (RkpdError::RetryableTimeout, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), + (RkpdError::Timeout, ResponseCode::SYSTEM_ERROR), + ]; + for (rkpd_error, expected_response_code) in error_mapping { + let e: Error = rkpd_error.into(); + assert_eq!(e, Error::Rc(expected_response_code)); + } + } } // mod tests diff --git a/keystore2/src/fuzzers/Android.bp b/keystore2/src/fuzzers/Android.bp index 9a2d98d1..7ddbfc09 100644 --- a/keystore2/src/fuzzers/Android.bp +++ b/keystore2/src/fuzzers/Android.bp @@ -20,13 +20,13 @@ rust_fuzz { name: "keystore2_unsafe_fuzzer", srcs: ["keystore2_unsafe_fuzzer.rs"], rustlibs: [ + "libarbitrary", "libkeystore2", - "libkeystore2_crypto_rust", - "libkeystore2_vintf_rust", "libkeystore2_aaid-rust", "libkeystore2_apc_compat-rust", + "libkeystore2_crypto_rust", + "libkeystore2_hal_names_rust", "libkeystore2_selinux", - "libarbitrary", ], fuzz_config: { fuzz_on_haiku_device: true, @@ -38,19 +38,18 @@ rust_fuzz { }, } - rust_fuzz { name: "authorization_service_fuzzer", srcs: ["aidl-fuzzers/authorization_service_fuzzer.rs"], rustlibs: [ + "libbinder_random_parcel_rs", + "libbinder_rs", "libkeystore2", - "libkeystore2_crypto_rust", - "libkeystore2_vintf_rust", "libkeystore2_aaid-rust", "libkeystore2_apc_compat-rust", + "libkeystore2_crypto_rust", + "libkeystore2_hal_names_rust", "libkeystore2_selinux", - "libbinder_rs", - "libbinder_random_parcel_rs", ], fuzz_config: { fuzz_on_haiku_device: true, @@ -58,7 +57,7 @@ rust_fuzz { cc: [ "android-media-fuzzing-reports@google.com", "smoreland@google.com", - "waghpawan@google.com" + "waghpawan@google.com", ], // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer hotlists: ["4637097"], diff --git a/keystore2/src/fuzzers/aidl-fuzzers/authorization_service_fuzzer.rs b/keystore2/src/fuzzers/aidl-fuzzers/authorization_service_fuzzer.rs index c1b2098f..9f83b400 100644 --- a/keystore2/src/fuzzers/aidl-fuzzers/authorization_service_fuzzer.rs +++ b/keystore2/src/fuzzers/aidl-fuzzers/authorization_service_fuzzer.rs @@ -16,11 +16,10 @@ #![allow(missing_docs)] #![no_main] -#[macro_use] -extern crate libfuzzer_sys; use binder_random_parcel_rs::fuzz_service; use keystore2::authorization::AuthorizationManager; +use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { let authorization_service = AuthorizationManager::new_native_binder().unwrap_or_else(|e| { diff --git a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs index 1a385e7e..fb4c9ad7 100644 --- a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs +++ b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs @@ -14,10 +14,8 @@ //! Fuzzes unsafe APIs of libkeystore2 module -#![feature(slice_internals)] #![no_main] -use core::slice::memchr; use keystore2::{legacy_blob::LegacyBlobLoader, utils::ui_opts_2_compat}; use keystore2_aaid::get_aaid; use keystore2_apc_compat::ApcHal; @@ -27,8 +25,8 @@ use keystore2_crypto::{ ec_point_point_to_oct, ecdh_compute_key, generate_random_data, hkdf_expand, hkdf_extract, hmac_sha256, parse_subject_from_certificate, Password, ZVec, }; +use keystore2_hal_names::get_hidl_instances; use keystore2_selinux::{check_access, getpidcon, setcon, Backend, Context, KeystoreKeyBackend}; -use keystore2_vintf::{get_aidl_instances, get_hidl_instances}; use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target}; use std::{ffi::CString, sync::Arc}; @@ -37,7 +35,7 @@ const MAX_SIZE_MODIFIER: usize = 1024; /// CString does not contain any internal 0 bytes fn get_valid_cstring_data(data: &[u8]) -> &[u8] { - match memchr::memchr(0, data) { + match data.iter().position(|&b| b == 0) { Some(idx) => &data[0..idx], None => data, } @@ -95,11 +93,6 @@ enum FuzzCommand<'a> { minor_version: usize, hidl_interface_name: &'a str, }, - GetAidlInstances { - aidl_package: &'a str, - version: usize, - aidl_interface_name: &'a str, - }, GetAaid { aaid_uid: u32, }, @@ -151,7 +144,8 @@ fuzz_target!(|commands: Vec<FuzzCommand>| { let _res = aes_gcm_encrypt(plaintext, key_aes_encrypt); } FuzzCommand::Password { pw, salt, key_length } => { - let _res = Password::from(pw).derive_key(salt, key_length % MAX_SIZE_MODIFIER); + let _res = + Password::from(pw).derive_key_pbkdf2(salt, key_length % MAX_SIZE_MODIFIER); } FuzzCommand::HkdfExtract { hkdf_secret, hkdf_salt } => { let _res = hkdf_extract(hkdf_secret, hkdf_salt); @@ -191,9 +185,6 @@ fuzz_target!(|commands: Vec<FuzzCommand>| { } => { get_hidl_instances(hidl_package, major_version, minor_version, hidl_interface_name); } - FuzzCommand::GetAidlInstances { aidl_package, version, aidl_interface_name } => { - get_aidl_instances(aidl_package, version, aidl_interface_name); - } FuzzCommand::GetAaid { aaid_uid } => { let _res = get_aaid(aaid_uid); } diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs index ed595784..7ac1038d 100644 --- a/keystore2/src/globals.rs +++ b/keystore2/src/globals.rs @@ -16,33 +16,36 @@ //! database connections and connections to services that Keystore needs //! to talk to. -use crate::ks_err; +use crate::async_task::AsyncTask; use crate::gc::Gc; +use crate::km_compat::{BacklevelKeyMintWrapper, KeyMintV1}; +use crate::ks_err; use crate::legacy_blob::LegacyBlobLoader; use crate::legacy_importer::LegacyImporter; use crate::super_key::SuperKeyManager; use crate::utils::watchdog as wd; -use crate::{async_task::AsyncTask, database::MonotonicRawTime}; use crate::{ database::KeystoreDB, database::Uuid, error::{map_binder_status, map_binder_status_code, Error, ErrorCode}, }; -use crate::km_compat::{KeyMintV1, BacklevelKeyMintWrapper}; use crate::{enforcements::Enforcements, error::map_km_error}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - IKeyMintDevice::IKeyMintDevice, KeyMintHardwareInfo::KeyMintHardwareInfo, - SecurityLevel::SecurityLevel, + IKeyMintDevice::BpKeyMintDevice, IKeyMintDevice::IKeyMintDevice, + KeyMintHardwareInfo::KeyMintHardwareInfo, SecurityLevel::SecurityLevel, +}; +use android_hardware_security_keymint::binder::{StatusCode, Strong}; +use android_hardware_security_rkp::aidl::android::hardware::security::keymint::{ + IRemotelyProvisionedComponent::BpRemotelyProvisionedComponent, + IRemotelyProvisionedComponent::IRemotelyProvisionedComponent, }; -use android_hardware_security_rkp::aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent::IRemotelyProvisionedComponent; use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ - ISecureClock::ISecureClock, + ISecureClock::BpSecureClock, ISecureClock::ISecureClock, }; -use android_hardware_security_keymint::binder::{StatusCode, Strong}; use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; use anyhow::{Context, Result}; +use binder::get_declared_instances; use binder::FromIBinder; -use keystore2_vintf::get_aidl_instances; use lazy_static::lazy_static; use std::sync::{Arc, Mutex, RwLock}; use std::{cell::RefCell, sync::Once}; @@ -65,7 +68,6 @@ pub fn create_thread_local_db() -> KeystoreDB { DB_INIT.call_once(|| { log::info!("Touching Keystore 2.0 database for this first time since boot."); - db.insert_last_off_body(MonotonicRawTime::now()); log::info!("Calling cleanup leftovers."); let n = db.cleanup_leftovers().expect("Failed to cleanup database on startup."); if n != 0 { @@ -134,26 +136,6 @@ impl<T: FromIBinder + ?Sized> Default for DevicesMap<T> { } } -struct RemotelyProvisionedDevicesMap<T: FromIBinder + ?Sized> { - devices_by_sec_level: HashMap<SecurityLevel, Strong<T>>, -} - -impl<T: FromIBinder + ?Sized> Default for RemotelyProvisionedDevicesMap<T> { - fn default() -> Self { - Self { devices_by_sec_level: HashMap::<SecurityLevel, Strong<T>>::new() } - } -} - -impl<T: FromIBinder + ?Sized> RemotelyProvisionedDevicesMap<T> { - fn dev_by_sec_level(&self, sec_level: &SecurityLevel) -> Option<Strong<T>> { - self.devices_by_sec_level.get(sec_level).map(|dev| (*dev).clone()) - } - - fn insert(&mut self, sec_level: SecurityLevel, dev: Strong<T>) { - self.devices_by_sec_level.insert(sec_level, dev); - } -} - lazy_static! { /// The path where keystore stores all its keys. pub static ref DB_PATH: RwLock<PathBuf> = RwLock::new( @@ -164,10 +146,6 @@ lazy_static! { static ref KEY_MINT_DEVICES: Mutex<DevicesMap<dyn IKeyMintDevice>> = Default::default(); /// Timestamp service. static ref TIME_STAMP_DEVICE: Mutex<Option<Strong<dyn ISecureClock>>> = Default::default(); - /// RemotelyProvisionedComponent HAL devices. - static ref REMOTELY_PROVISIONED_COMPONENT_DEVICES: - Mutex<RemotelyProvisionedDevicesMap<dyn IRemotelyProvisionedComponent>> = - Default::default(); /// A single on-demand worker thread that handles deferred tasks with two different /// priorities. pub static ref ASYNC_TASK: Arc<AsyncTask> = Default::default(); @@ -198,43 +176,37 @@ lazy_static! { })); } -static KEYMINT_SERVICE_NAME: &str = "android.hardware.security.keymint.IKeyMintDevice"; - /// Determine the service name for a KeyMint device of the given security level -/// which implements at least the specified version of the `IKeyMintDevice` -/// interface. -fn keymint_service_name_by_version( - security_level: &SecurityLevel, - version: i32, -) -> Result<Option<(i32, String)>> { - let keymint_instances = - get_aidl_instances("android.hardware.security.keymint", version as usize, "IKeyMintDevice"); +/// gotten by binder service from the device and determining what services +/// are available. +fn keymint_service_name(security_level: &SecurityLevel) -> Result<Option<String>> { + let keymint_descriptor: &str = <BpKeyMintDevice as IKeyMintDevice>::get_descriptor(); + let keymint_instances = get_declared_instances(keymint_descriptor).unwrap(); let service_name = match *security_level { SecurityLevel::TRUSTED_ENVIRONMENT => { if keymint_instances.iter().any(|instance| *instance == "default") { - Some(format!("{}/default", KEYMINT_SERVICE_NAME)) + Some(format!("{}/default", keymint_descriptor)) } else { None } } SecurityLevel::STRONGBOX => { if keymint_instances.iter().any(|instance| *instance == "strongbox") { - Some(format!("{}/strongbox", KEYMINT_SERVICE_NAME)) + Some(format!("{}/strongbox", keymint_descriptor)) } else { None } } _ => { return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(ks_err!( - "Trying to find keymint V{} for security level: {:?}", - version, + "Trying to find keymint for security level: {:?}", security_level )); } }; - Ok(service_name.map(|service_name| (version, service_name))) + Ok(service_name) } /// Make a new connection to a KeyMint device of the given security level. @@ -243,28 +215,22 @@ fn keymint_service_name_by_version( fn connect_keymint( security_level: &SecurityLevel, ) -> Result<(Strong<dyn IKeyMintDevice>, KeyMintHardwareInfo)> { - // Count down from the current interface version back to one in order to - // also find out the interface version -- an implementation of V2 will show - // up in the list of V1-capable devices, but not vice-versa. - let service_name = keymint_service_name_by_version(security_level, 2) - .and_then(|sl| { - if sl.is_none() { - keymint_service_name_by_version(security_level, 1) - } else { - Ok(sl) - } - }) - .context(ks_err!())?; + // Show the keymint interface that is registered in the binder + // service and use the security level to get the service name. + let service_name = keymint_service_name(security_level) + .context(ks_err!("Get service name from binder service"))?; - let (keymint, hal_version) = if let Some((version, service_name)) = service_name { + let (keymint, hal_version) = if let Some(service_name) = service_name { let km: Strong<dyn IKeyMintDevice> = map_binder_status_code(binder::get_interface(&service_name)) .context(ks_err!("Trying to connect to genuine KeyMint service."))?; // Map the HAL version code for KeyMint to be <AIDL version> * 100, so // - V1 is 100 // - V2 is 200 + // - V3 is 300 // etc. - (km, Some(version * 100)) + let km_version = km.getInterfaceVersion()?; + (km, Some(km_version * 100)) } else { // This is a no-op if it was called before. keystore2_km_compat::add_keymint_device_service(); @@ -280,7 +246,11 @@ fn connect_keymint( } e => e, }) - .context(ks_err!("Trying to get Legacy wrapper."))?, + .context(ks_err!( + "Trying to get Legacy wrapper. Attempt to get keystore \ + compat service for security level {:?}", + *security_level + ))?, None, ) }; @@ -288,8 +258,17 @@ fn connect_keymint( // If the KeyMint device is back-level, use a wrapper that intercepts and // emulates things that are not supported by the hardware. let keymint = match hal_version { + Some(300) => { + // Current KeyMint version: use as-is as v3 Keymint is current version + log::info!( + "KeyMint device is current version ({:?}) for security level: {:?}", + hal_version, + security_level + ); + keymint + } Some(200) => { - // Current KeyMint version: use as-is. + // Previous KeyMint version: use as-is as we don't have any software emulation of v3-specific KeyMint features. log::info!( "KeyMint device is current version ({:?}) for security level: {:?}", hal_version, @@ -359,7 +338,8 @@ pub fn get_keymint_device( if let Some((dev, hw_info, uuid)) = devices_map.dev_by_sec_level(security_level) { Ok((dev, hw_info, uuid)) } else { - let (dev, hw_info) = connect_keymint(security_level).context(ks_err!())?; + let (dev, hw_info) = + connect_keymint(security_level).context(ks_err!("Cannot connect to Keymint"))?; devices_map.insert(*security_level, dev, hw_info); // Unwrap must succeed because we just inserted it. Ok(devices_map.dev_by_sec_level(security_level).unwrap()) @@ -386,19 +366,17 @@ pub fn get_keymint_devices() -> Vec<Strong<dyn IKeyMintDevice>> { KEY_MINT_DEVICES.lock().unwrap().devices() } -static TIME_STAMP_SERVICE_NAME: &str = "android.hardware.security.secureclock.ISecureClock"; - /// Make a new connection to a secure clock service. /// If no native SecureClock device can be found brings up the compatibility service and attempts /// to connect to the legacy wrapper. fn connect_secureclock() -> Result<Strong<dyn ISecureClock>> { - let secureclock_instances = - get_aidl_instances("android.hardware.security.secureclock", 1, "ISecureClock"); + let secure_clock_descriptor: &str = <BpSecureClock as ISecureClock>::get_descriptor(); + let secureclock_instances = get_declared_instances(secure_clock_descriptor).unwrap(); let secure_clock_available = secureclock_instances.iter().any(|instance| *instance == "default"); - let default_time_stamp_service_name = format!("{}/default", TIME_STAMP_SERVICE_NAME); + let default_time_stamp_service_name = format!("{}/default", secure_clock_descriptor); let secureclock = if secure_clock_available { map_binder_status_code(binder::get_interface(&default_time_stamp_service_name)) @@ -419,7 +397,7 @@ fn connect_secureclock() -> Result<Strong<dyn ISecureClock>> { } e => e, }) - .context(ks_err!("Trying to get Legacy wrapper.")) + .context(ks_err!("Failed attempt to get legacy secure clock.")) }?; Ok(secureclock) @@ -438,25 +416,23 @@ pub fn get_timestamp_service() -> Result<Strong<dyn ISecureClock>> { } } -static REMOTE_PROVISIONING_HAL_SERVICE_NAME: &str = - "android.hardware.security.keymint.IRemotelyProvisionedComponent"; - /// Get the service name of a remotely provisioned component corresponding to given security level. pub fn get_remotely_provisioned_component_name(security_level: &SecurityLevel) -> Result<String> { - let remotely_prov_instances = - get_aidl_instances("android.hardware.security.keymint", 1, "IRemotelyProvisionedComponent"); + let remote_prov_descriptor: &str = + <BpRemotelyProvisionedComponent as IRemotelyProvisionedComponent>::get_descriptor(); + let remotely_prov_instances = get_declared_instances(remote_prov_descriptor).unwrap(); match *security_level { SecurityLevel::TRUSTED_ENVIRONMENT => { if remotely_prov_instances.iter().any(|instance| *instance == "default") { - Some(format!("{}/default", REMOTE_PROVISIONING_HAL_SERVICE_NAME)) + Some(format!("{}/default", remote_prov_descriptor)) } else { None } } SecurityLevel::STRONGBOX => { if remotely_prov_instances.iter().any(|instance| *instance == "strongbox") { - Some(format!("{}/strongbox", REMOTE_PROVISIONING_HAL_SERVICE_NAME)) + Some(format!("{}/strongbox", remote_prov_descriptor)) } else { None } @@ -464,31 +440,5 @@ pub fn get_remotely_provisioned_component_name(security_level: &SecurityLevel) - _ => None, } .ok_or(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) - .context(ks_err!()) -} - -fn connect_remotely_provisioned_component( - security_level: &SecurityLevel, -) -> Result<Strong<dyn IRemotelyProvisionedComponent>> { - let service_name = get_remotely_provisioned_component_name(security_level)?; - let rem_prov_hal: Strong<dyn IRemotelyProvisionedComponent> = - map_binder_status_code(binder::get_interface(&service_name)) - .context(ks_err!("Trying to connect to RemotelyProvisionedComponent service."))?; - Ok(rem_prov_hal) -} - -/// Get a remote provisiong component device for the given security level either from the cache or -/// by making a new connection. Returns the device. -pub fn get_remotely_provisioned_component( - security_level: &SecurityLevel, -) -> Result<Strong<dyn IRemotelyProvisionedComponent>> { - let mut devices_map = REMOTELY_PROVISIONED_COMPONENT_DEVICES.lock().unwrap(); - if let Some(dev) = devices_map.dev_by_sec_level(security_level) { - Ok(dev) - } else { - let dev = connect_remotely_provisioned_component(security_level).context(ks_err!())?; - devices_map.insert(*security_level, dev); - // Unwrap must succeed because we just inserted it. - Ok(devices_map.dev_by_sec_level(security_level).unwrap()) - } + .context(ks_err!("Failed to get rpc for sec level {:?}", *security_level)) } diff --git a/keystore2/src/vintf/Android.bp b/keystore2/src/hal_instance_names/Android.bp index 34719aaa..2f1d5c3d 100644 --- a/keystore2/src/vintf/Android.bp +++ b/keystore2/src/hal_instance_names/Android.bp @@ -22,41 +22,41 @@ package { } rust_library { - name: "libkeystore2_vintf_rust", - crate_name: "keystore2_vintf", + name: "libkeystore2_hal_names_rust", + crate_name: "keystore2_hal_names", srcs: ["lib.rs"], rustlibs: [ "libcxx", ], shared_libs: [ - "libvintf", + "libhidlbase", ], static_libs: [ - "libkeystore2_vintf_cpp", + "libkeystore2_hal_names_cpp", ], } cc_library_static { - name: "libkeystore2_vintf_cpp", - srcs: ["vintf.cpp"], + name: "libkeystore2_hal_names_cpp", + srcs: ["hal_names.cpp"], generated_headers: ["cxx-bridge-header"], - generated_sources: ["vintf_bridge_code"], + generated_sources: ["hal_names_bridge_code"], shared_libs: [ - "libvintf", + "libhidlbase", ], } genrule { - name: "vintf_bridge_code", + name: "hal_names_bridge_code", tools: ["cxxbridge"], cmd: "$(location cxxbridge) $(in) >> $(out)", srcs: ["lib.rs"], - out: ["vintf_cxx_generated.cc"], + out: ["hal_names_cxx_generated.cc"], } rust_test { - name: "keystore2_vintf_test", - crate_name: "keystore2_vintf_test", + name: "keystore2_hal_names_test", + crate_name: "keystore2_hal_names_test", srcs: ["lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -64,10 +64,10 @@ rust_test { "libcxx", ], static_libs: [ - "libkeystore2_vintf_cpp", + "libkeystore2_hal_names_cpp", ], shared_libs: [ "libc++", - "libvintf", + "libhidlbase", ], } diff --git a/keystore2/src/hal_instance_names/hal_names.cpp b/keystore2/src/hal_instance_names/hal_names.cpp new file mode 100644 index 00000000..316c26c2 --- /dev/null +++ b/keystore2/src/hal_instance_names/hal_names.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <hidl/ServiceManagement.h> + +#include "rust/cxx.h" + +rust::Vec<rust::String> convert(const std::vector<std::string>& names) { + rust::Vec<rust::String> result; + std::copy(names.begin(), names.end(), std::back_inserter(result)); + return result; +} + +rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_version, + size_t minor_version, rust::Str interfaceName) { + std::string version = std::to_string(major_version) + "." + std::to_string(minor_version); + std::string factoryName = static_cast<std::string>(package) + "@" + version + + "::" + static_cast<std::string>(interfaceName); + + const auto halNames = android::hardware::getAllHalInstanceNames(factoryName); + return convert(halNames); +} diff --git a/keystore2/src/vintf/vintf.hpp b/keystore2/src/hal_instance_names/hal_names.hpp index c4a7ef60..ef1e7882 100644 --- a/keystore2/src/vintf/vintf.hpp +++ b/keystore2/src/hal_instance_names/hal_names.hpp @@ -20,5 +20,3 @@ rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_version, size_t minor_version, rust::Str interfaceName); -rust::Vec<rust::String> get_aidl_instances(rust::Str package, size_t version, - rust::Str interfaceName); diff --git a/keystore2/src/vintf/lib.rs b/keystore2/src/hal_instance_names/lib.rs index 08384bd4..36a9c4f9 100644 --- a/keystore2/src/vintf/lib.rs +++ b/keystore2/src/hal_instance_names/lib.rs @@ -17,7 +17,7 @@ #[cxx::bridge] mod ffi { unsafe extern "C++" { - include!("vintf.hpp"); + include!("hal_names.hpp"); /// Gets the instances of the given package, version, and interface tuple. /// Note that this is not a zero-cost shim: it will make copies of the strings. @@ -27,10 +27,6 @@ mod ffi { minor_version: usize, interface_name: &str, ) -> Vec<String>; - - /// Gets the instances of the given package, version, and interface tuple. - /// Note that this is not a zero-cost shim: it will make copies of the strings. - fn get_aidl_instances(package: &str, version: usize, interface_name: &str) -> Vec<String>; } } diff --git a/keystore2/src/id_rotation.rs b/keystore2/src/id_rotation.rs index 460caa77..59040476 100644 --- a/keystore2/src/id_rotation.rs +++ b/keystore2/src/id_rotation.rs @@ -26,7 +26,7 @@ use anyhow::{Context, Result}; use std::fs; use std::io::ErrorKind; use std::path::{Path, PathBuf}; -use std::time::Duration; +use std::time::{Duration, SystemTime}; const ID_ROTATION_PERIOD: Duration = Duration::from_secs(30 * 24 * 60 * 60); // Thirty days. static TIMESTAMP_FILE_NAME: &str = "timestamp"; @@ -36,6 +36,8 @@ static TIMESTAMP_FILE_NAME: &str = "timestamp"; /// and passed down to the users of the feature which can then query the timestamp on demand. #[derive(Debug, Clone)] pub struct IdRotationState { + /// We consider the time of last factory reset to be the point in time when this timestamp file + /// is created. timestamp_path: PathBuf, } @@ -47,25 +49,41 @@ impl IdRotationState { Self { timestamp_path } } - /// Reads the metadata of or creates the timestamp file. It returns true if the timestamp - /// file is younger than `ID_ROTATION_PERIOD`, i.e., 30 days. - pub fn had_factory_reset_since_id_rotation(&self) -> Result<bool> { + /// Returns true iff a factory reset has occurred since the last ID rotation. + pub fn had_factory_reset_since_id_rotation( + &self, + creation_datetime: &SystemTime, + ) -> Result<bool> { match fs::metadata(&self.timestamp_path) { Ok(metadata) => { - let duration_since_factory_reset = metadata - .modified() - .context("File creation time not supported.")? - .elapsed() - .context("Failed to compute time elapsed since factory reset.")?; - Ok(duration_since_factory_reset < ID_ROTATION_PERIOD) + // For Tag::UNIQUE_ID, temporal counter value is defined as Tag::CREATION_DATETIME + // divided by 2592000000, dropping any remainder. Temporal counter value is + // effectively the index of the ID rotation period that we are currently in, with + // each ID rotation period being 30 days. + let temporal_counter_value = creation_datetime + .duration_since(SystemTime::UNIX_EPOCH) + .context(ks_err!("Failed to get epoch time"))? + .as_millis() + / ID_ROTATION_PERIOD.as_millis(); + + // Calculate the beginning of the current ID rotation period, which is also the + // last time ID was rotated. + let id_rotation_time: SystemTime = SystemTime::UNIX_EPOCH + .checked_add(ID_ROTATION_PERIOD * temporal_counter_value.try_into()?) + .context(ks_err!("Failed to get ID rotation time."))?; + + let factory_reset_time = + metadata.modified().context(ks_err!("File creation time not supported."))?; + + Ok(id_rotation_time <= factory_reset_time) } Err(e) => match e.kind() { ErrorKind::NotFound => { fs::File::create(&self.timestamp_path) - .context("Failed to create timestamp file.")?; + .context(ks_err!("Failed to create timestamp file."))?; Ok(true) } - _ => Err(e).context("Failed to open timestamp file."), + _ => Err(e).context(ks_err!("Failed to open timestamp file.")), }, } .context(ks_err!()) @@ -78,47 +96,75 @@ mod test { use keystore2_test_utils::TempDir; use nix::sys::stat::utimes; use nix::sys::time::{TimeVal, TimeValLike}; - use std::convert::TryInto; - use std::time::UNIX_EPOCH; + use std::thread::sleep; - #[test] - fn test_had_factory_reset_since_id_rotation() -> Result<()> { - let temp_dir = TempDir::new("test_had_factory_reset_since_id_rotation_") - .expect("Failed to create temp dir."); + static TEMP_DIR_NAME: &str = "test_had_factory_reset_since_id_rotation_"; + + fn set_up() -> (TempDir, PathBuf, IdRotationState) { + let temp_dir = TempDir::new(TEMP_DIR_NAME).expect("Failed to create temp dir."); + let mut timestamp_file_path = temp_dir.path().to_owned(); + timestamp_file_path.push(TIMESTAMP_FILE_NAME); let id_rotation_state = IdRotationState::new(temp_dir.path()); - let mut temp_file_path = temp_dir.path().to_owned(); - temp_file_path.push(TIMESTAMP_FILE_NAME); + (temp_dir, timestamp_file_path, id_rotation_state) + } + + #[test] + fn test_timestamp_creation() { + let (_temp_dir, timestamp_file_path, id_rotation_state) = set_up(); + let creation_datetime = SystemTime::now(); // The timestamp file should not exist. - assert!(!temp_file_path.exists()); + assert!(!timestamp_file_path.exists()); - // This should return true. - assert!(id_rotation_state.had_factory_reset_since_id_rotation()?); + // Trigger timestamp file creation one second later. + sleep(Duration::new(1, 0)); + assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap()); // Now the timestamp file should exist. - assert!(temp_file_path.exists()); - - // We should still return true because the timestamp file is young. - assert!(id_rotation_state.had_factory_reset_since_id_rotation()?); + assert!(timestamp_file_path.exists()); - // Now let's age the timestamp file by backdating the modification time. - let metadata = fs::metadata(&temp_file_path)?; - let mtime = metadata.modified()?; - let mtime = mtime.duration_since(UNIX_EPOCH)?; - let mtime = - mtime.checked_sub(ID_ROTATION_PERIOD).expect("Failed to subtract id rotation period"); - let mtime = TimeVal::seconds(mtime.as_secs().try_into().unwrap()); - - let atime = metadata.accessed()?; - let atime = atime.duration_since(UNIX_EPOCH)?; - let atime = TimeVal::seconds(atime.as_secs().try_into().unwrap()); - - utimes(&temp_file_path, &atime, &mtime)?; - - // Now that the file has aged we should see false. - assert!(!id_rotation_state.had_factory_reset_since_id_rotation()?); + let metadata = fs::metadata(×tamp_file_path).unwrap(); + assert!(metadata.modified().unwrap() > creation_datetime); + } - Ok(()) + #[test] + fn test_existing_timestamp() { + let (_temp_dir, timestamp_file_path, id_rotation_state) = set_up(); + + // Let's start with at a known point in time, so that it's easier to control which ID + // rotation period we're in. + let mut creation_datetime = SystemTime::UNIX_EPOCH; + + // Create timestamp file and backdate it back to Unix epoch. + fs::File::create(×tamp_file_path).unwrap(); + let mtime = TimeVal::seconds(0); + let atime = TimeVal::seconds(0); + utimes(×tamp_file_path, &atime, &mtime).unwrap(); + + // Timestamp file was backdated to the very beginning of the current ID rotation period. + // So, this should return true. + assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap()); + + // Move time forward, but stay in the same ID rotation period. + creation_datetime += Duration::from_millis(1); + + // We should still return true because we're in the same ID rotation period. + assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap()); + + // Move time to the next ID rotation period. + creation_datetime += ID_ROTATION_PERIOD; + + // Now we should see false. + assert!(!id_rotation_state + .had_factory_reset_since_id_rotation(&creation_datetime) + .unwrap()); + + // Move timestamp to the future. This shouldn't ever happen, but even in this edge case ID + // must be rotated. + let mtime = TimeVal::seconds((ID_ROTATION_PERIOD.as_secs() * 10).try_into().unwrap()); + let atime = TimeVal::seconds((ID_ROTATION_PERIOD.as_secs() * 10).try_into().unwrap()); + utimes(×tamp_file_path, &atime, &mtime).unwrap(); + assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap()); } } diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs index b3dcf45e..bd452073 100644 --- a/keystore2/src/key_parameter.rs +++ b/keystore2/src/key_parameter.rs @@ -837,6 +837,11 @@ pub enum KeyParameterValue { #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = DIGEST, field = Digest)] Digest(Digest), + /// Digest algorithms that can be used for MGF in RSA-OAEP. + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = RSA_OAEP_MGF_DIGEST, field = Digest)] + RsaOaepMgfDigest(Digest), /// Padding modes that may be used with the key. Relevant to RSA, AES and 3DES keys. #[serde(deserialize_with = "deserialize_primitive")] #[serde(serialize_with = "serialize_primitive")] @@ -907,7 +912,8 @@ pub enum KeyParameterValue { /// The time in seconds for which the key is authorized for use, after user authentication #[key_param(tag = AUTH_TIMEOUT, field = Integer)] AuthTimeout(i32), - /// The key may be used after authentication timeout if device is still on-body + /// The key's authentication timeout, if it has one, is automatically expired when the device is + /// removed from the user's body. No longer implemented; this tag is no longer enforced. #[key_param(tag = ALLOW_WHILE_ON_BODY, field = BoolValue)] AllowWhileOnBody, /// The key must be unusable except when the user has provided proof of physical presence @@ -1098,6 +1104,7 @@ mod generated_key_parameter_tests { Tag::BLOCK_MODE => return KmKeyParameterValue::BlockMode(Default::default()), Tag::PADDING => return KmKeyParameterValue::PaddingMode(Default::default()), Tag::DIGEST => return KmKeyParameterValue::Digest(Default::default()), + Tag::RSA_OAEP_MGF_DIGEST => return KmKeyParameterValue::Digest(Default::default()), Tag::EC_CURVE => return KmKeyParameterValue::EcCurve(Default::default()), Tag::ORIGIN => return KmKeyParameterValue::Origin(Default::default()), Tag::PURPOSE => return KmKeyParameterValue::KeyPurpose(Default::default()), @@ -1210,7 +1217,7 @@ mod storage_tests { use crate::key_parameter::*; use anyhow::Result; use rusqlite::types::ToSql; - use rusqlite::{params, Connection, NO_PARAMS}; + use rusqlite::{params, Connection}; /// Test initializing a KeyParameter (with key parameter value corresponding to an enum of i32) /// from a database table row. @@ -1417,7 +1424,7 @@ mod storage_tests { tag INTEGER, data ANY, security_level INTEGER);", - NO_PARAMS, + [], ) .context("Failed to initialize \"keyparameter\" table.")?; Ok(db) @@ -1453,7 +1460,7 @@ mod storage_tests { fn query_from_keyparameter(db: &Connection) -> Result<KeyParameter> { let mut stmt = db.prepare("SELECT tag, data, security_level FROM persistent.keyparameter")?; - let mut rows = stmt.query(NO_PARAMS)?; + let mut rows = stmt.query([])?; let row = rows.next()?.unwrap(); KeyParameter::new_from_sql( Tag(row.get(0)?), diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs index 8c5bf0f6..178b36c7 100644 --- a/keystore2/src/keystore2_main.rs +++ b/keystore2/src/keystore2_main.rs @@ -19,9 +19,6 @@ use keystore2::globals::ENFORCEMENTS; use keystore2::maintenance::Maintenance; use keystore2::metrics::Metrics; use keystore2::metrics_store; -use keystore2::remote_provisioning::{ - RemoteProvisioningService, RemotelyProvisionedKeyPoolService, -}; use keystore2::service::KeystoreService; use keystore2::{apc::ApcManager, shared_secret_negotiation}; use keystore2::{authorization::AuthorizationManager, id_rotation::IdRotationState}; @@ -34,9 +31,6 @@ static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/defau static APC_SERVICE_NAME: &str = "android.security.apc"; static AUTHORIZATION_SERVICE_NAME: &str = "android.security.authorization"; static METRICS_SERVICE_NAME: &str = "android.security.metrics"; -static REMOTE_PROVISIONING_SERVICE_NAME: &str = "android.security.remoteprovisioning"; -static REMOTELY_PROVISIONED_KEY_POOL_SERVICE_NAME: &str = - "android.security.remoteprovisioning.IRemotelyProvisionedKeyPool"; static USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance"; static LEGACY_KEYSTORE_SERVICE_NAME: &str = "android.security.legacykeystore"; @@ -46,8 +40,8 @@ fn main() { android_logger::init_once( android_logger::Config::default() .with_tag("keystore2") - .with_min_level(log::Level::Debug) - .with_log_id(android_logger::LogId::System) + .with_max_level(log::LevelFilter::Debug) + .with_log_buffer(android_logger::LogId::System) .format(|buf, record| { writeln!( buf, @@ -74,6 +68,8 @@ fn main() { fn sqlite_log_handler(err: c_int, message: &str) { log::error!("[SQLITE3] {}: {}", err, message); } + // SAFETY: There are no other threads yet, `sqlite_log_handler` is threadsafe, and it doesn't + // invoke any SQLite calls. unsafe { sqlite_trace::config_log(Some(sqlite_log_handler)) } .expect("Error setting sqlite log callback."); @@ -146,43 +142,6 @@ fn main() { panic!("Failed to register service {} because of {:?}.", METRICS_SERVICE_NAME, e); }); - // Devices with KS2 and KM 1.0 may not have any IRemotelyProvisionedComponent HALs at all. Do - // not panic if new_native_binder returns failure because it could not find the TEE HAL. - match RemoteProvisioningService::new_native_binder() { - Ok(remote_provisioning_service) => { - binder::add_service( - REMOTE_PROVISIONING_SERVICE_NAME, - remote_provisioning_service.as_binder(), - ) - .unwrap_or_else(|e| { - panic!( - "Failed to register service {} because of {:?}.", - REMOTE_PROVISIONING_SERVICE_NAME, e - ); - }); - } - Err(e) => log::info!("Not publishing {}: {:?}", REMOTE_PROVISIONING_SERVICE_NAME, e), - } - - // Even if the IRemotelyProvisionedComponent HAL is implemented, it doesn't mean that the keys - // may be fetched via the key pool. The HAL must be a new version that exports a unique id. If - // none of the HALs support this, then the key pool service is not published. - match RemotelyProvisionedKeyPoolService::new_native_binder() { - Ok(key_pool_service) => { - binder::add_service( - REMOTELY_PROVISIONED_KEY_POOL_SERVICE_NAME, - key_pool_service.as_binder(), - ) - .unwrap_or_else(|e| { - panic!( - "Failed to register service {} because of {:?}.", - REMOTELY_PROVISIONED_KEY_POOL_SERVICE_NAME, e - ); - }); - } - Err(e) => log::info!("Not publishing IRemotelyProvisionedKeyPool service: {:?}", e), - } - binder::add_service(LEGACY_KEYSTORE_SERVICE_NAME, legacykeystore.as_binder()).unwrap_or_else( |e| { panic!( diff --git a/keystore2/src/km_compat.rs b/keystore2/src/km_compat.rs index 035edd90..03c9d027 100644 --- a/keystore2/src/km_compat.rs +++ b/keystore2/src/km_compat.rs @@ -32,6 +32,16 @@ use android_security_compat::aidl::android::security::compat::IKeystoreCompatSer use anyhow::Context; use keystore2_crypto::{hmac_sha256, HMAC_SHA256_LEN}; +/// Magic prefix used by the km_compat C++ code to mark a key that is owned by an +/// underlying Keymaster hardware device that has been wrapped by km_compat. (The +/// final zero byte indicates that the blob is not software emulated.) +pub const KEYMASTER_BLOB_HW_PREFIX: &[u8] = b"pKMblob\x00"; + +/// Magic prefix used by the km_compat C++ code to mark a key that is owned by an +/// software emulation device that has been wrapped by km_compat. (The final one +/// byte indicates that the blob is software emulated.) +pub const KEYMASTER_BLOB_SW_PREFIX: &[u8] = b"pKMblob\x01"; + /// Key data associated with key generation/import. #[derive(Debug, PartialEq, Eq)] pub enum KeyImportData<'a> { @@ -89,7 +99,7 @@ fn wrap_keyblob(keyblob: &[u8]) -> anyhow::Result<Vec<u8>> { /// Return an unwrapped version of the provided `keyblob`, which may or may /// not be associated with the software emulation. -fn unwrap_keyblob(keyblob: &[u8]) -> KeyBlob { +pub fn unwrap_keyblob(keyblob: &[u8]) -> KeyBlob { if !keyblob.starts_with(KEYBLOB_PREFIX) { return KeyBlob::Raw(keyblob); } @@ -159,7 +169,7 @@ where } } -impl<T> binder::Interface for BacklevelKeyMintWrapper<T> where T: EmulationDetector {} +impl<T> binder::Interface for BacklevelKeyMintWrapper<T> where T: EmulationDetector + 'static {} impl<T> IKeyMintDevice for BacklevelKeyMintWrapper<T> where @@ -195,6 +205,15 @@ where let _ = self.soft.earlyBootEnded(); self.real.earlyBootEnded() } + fn getRootOfTrustChallenge(&self) -> binder::Result<[u8; 16]> { + self.real.getRootOfTrustChallenge() + } + fn getRootOfTrust(&self, challenge: &[u8; 16]) -> binder::Result<Vec<u8>> { + self.real.getRootOfTrust(challenge) + } + fn sendRootOfTrust(&self, root_of_trust: &[u8]) -> binder::Result<()> { + self.real.sendRootOfTrust(root_of_trust) + } // For methods that emit keyblobs, check whether the underlying real device // supports the relevant parameters, and forward to the appropriate device. @@ -299,15 +318,6 @@ where KeyBlob::Wrapped(keyblob) => self.soft.getKeyCharacteristics(keyblob, app_id, app_data), } } - fn getRootOfTrustChallenge(&self) -> binder::Result<[u8; 16]> { - self.real.getRootOfTrustChallenge() - } - fn getRootOfTrust(&self, challenge: &[u8; 16]) -> binder::Result<Vec<u8>> { - self.real.getRootOfTrust(challenge) - } - fn sendRootOfTrust(&self, root_of_trust: &[u8]) -> binder::Result<()> { - self.real.sendRootOfTrust(root_of_trust) - } fn convertStorageKeyToEphemeral(&self, storage_keyblob: &[u8]) -> binder::Result<Vec<u8>> { // Storage keys should never be associated with a software emulated device. self.real.convertStorageKeyToEphemeral(storage_keyblob) diff --git a/keystore2/src/km_compat/Android.bp b/keystore2/src/km_compat/Android.bp index 806f3dcf..36e18f0f 100644 --- a/keystore2/src/km_compat/Android.bp +++ b/keystore2/src/km_compat/Android.bp @@ -33,7 +33,7 @@ rust_library { ], shared_libs: [ "libkm_compat_service", - ] + ], } rust_test { @@ -91,9 +91,9 @@ cc_library { "android.security.compat-ndk", "libbinder_ndk", "libcrypto", - "libkm_compat", "libkeymaster4_1support", "libkeystore2_crypto", + "libkm_compat", ], } diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp index e27cd1c7..e9ff1fff 100644 --- a/keystore2/src/km_compat/km_compat.cpp +++ b/keystore2/src/km_compat/km_compat.cpp @@ -144,6 +144,11 @@ const size_t kKeyBlobPrefixSize = 8; // const uint8_t kKeyBlobMagic[7] = {'p', 'K', 'M', 'b', 'l', 'o', 'b'}; +// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set +// to 9999-12-31T23:59:59Z. +// +const uint64_t kUndefinedNotAfter = 253402300799000; + // Prefixes a keyblob returned by e.g. generateKey() with information on whether it // originated from the real underlying KeyMaster HAL or from soft-KeyMint. // @@ -260,6 +265,16 @@ extractNewAndKeystoreEnforceableParams(const std::vector<KMV1::KeyParameter>& pa return result; } +std::vector<KMV1::KeyParameter> +extractCombinedParams(const std::vector<KMV1::KeyCharacteristics>& characteristics) { + std::vector<KMV1::KeyParameter> result; + for (auto characteristic : characteristics) { + std::copy(characteristic.authorizations.begin(), characteristic.authorizations.end(), + std::back_inserter(result)); + } + return result; +} + ScopedAStatus convertErrorCode(KMV1::ErrorCode result) { if (result == KMV1::ErrorCode::OK) { return ScopedAStatus::ok(); @@ -587,6 +602,15 @@ KeyMintDevice::importWrappedKey(const std::vector<uint8_t>& in_inWrappedKeyData, LOG(ERROR) << __func__ << " transaction failed. " << result.description(); return convertErrorCode(KMV1::ErrorCode::UNKNOWN_ERROR); } + if (errorCode == KMV1::ErrorCode::OK) { + auto params = extractCombinedParams(out_creationResult->keyCharacteristics); + auto cert = getCertificate(params, out_creationResult->keyBlob, true /* isWrappedKey */); + // importWrappedKey used to not generate a certificate. Ignore the error to preserve + // backwards compatibility with clients that can't successfully generate a certificate. + if (std::holds_alternative<std::vector<Certificate>>(cert)) { + out_creationResult->certificateChain = std::get<std::vector<Certificate>>(cert); + } + } return convertErrorCode(errorCode); } @@ -1055,7 +1079,7 @@ getMaximum(const std::vector<KeyParameter>& keyParams, T tag, static std::variant<keystore::X509_Ptr, KMV1::ErrorCode> makeCert(::android::sp<Keymaster> mDevice, const std::vector<KeyParameter>& keyParams, - const std::vector<uint8_t>& keyBlob) { + const std::vector<uint8_t>& keyBlob, bool isWrappedKey) { // Start generating the certificate. // Get public key for makeCert. KMV1::ErrorCode errorCode; @@ -1097,15 +1121,21 @@ makeCert(::android::sp<Keymaster> mDevice, const std::vector<KeyParameter>& keyP serial = *blob; } + // There is no way to specify CERTIFICATE_NOT_BEFORE and CERTIFICATE_NOT_AFTER for wrapped keys. + // So we provide default values. int64_t activation; - if (auto date = getParam(keyParams, KMV1::TAG_CERTIFICATE_NOT_BEFORE)) { + if (isWrappedKey) { + activation = 0; + } else if (auto date = getParam(keyParams, KMV1::TAG_CERTIFICATE_NOT_BEFORE)) { activation = static_cast<int64_t>(*date); } else { return KMV1::ErrorCode::MISSING_NOT_BEFORE; } int64_t expiration; - if (auto date = getParam(keyParams, KMV1::TAG_CERTIFICATE_NOT_AFTER)) { + if (isWrappedKey) { + expiration = kUndefinedNotAfter; + } else if (auto date = getParam(keyParams, KMV1::TAG_CERTIFICATE_NOT_AFTER)) { expiration = static_cast<int64_t>(*date); } else { return KMV1::ErrorCode::MISSING_NOT_AFTER; @@ -1235,7 +1265,7 @@ KeyMintDevice::signCertificate(const std::vector<KeyParameter>& keyParams, std::variant<std::vector<Certificate>, KMV1::ErrorCode> KeyMintDevice::getCertificate(const std::vector<KeyParameter>& keyParams, - const std::vector<uint8_t>& prefixedKeyBlob) { + const std::vector<uint8_t>& prefixedKeyBlob, bool isWrappedKey) { const std::vector<uint8_t>& keyBlob = prefixedKeyBlobRemovePrefix(prefixedKeyBlob); // There are no certificates for symmetric keys. @@ -1278,7 +1308,7 @@ KeyMintDevice::getCertificate(const std::vector<KeyParameter>& keyParams, } // makeCert - auto certOrError = makeCert(mDevice, keyParams, keyBlob); + auto certOrError = makeCert(mDevice, keyParams, keyBlob, isWrappedKey); if (std::holds_alternative<KMV1::ErrorCode>(certOrError)) { return std::get<KMV1::ErrorCode>(certOrError); } @@ -1420,7 +1450,12 @@ KeymasterDevices enumerateKeymasterDevices(IServiceManager* serviceManager) { KeymasterDevices initializeKeymasters() { auto serviceManager = IServiceManager::getService(); - CHECK(serviceManager.get()) << "Failed to get ServiceManager"; + if (!serviceManager.get()) { + // New devices no longer have HIDL support, so failing to get hwservicemanager is + // expected behavior. + LOG(INFO) << "Skipping keymaster compat, this system is AIDL only."; + return KeymasterDevices(); + } auto result = enumerateKeymasterDevices<Keymaster4>(serviceManager.get()); auto softKeymaster = result[SecurityLevel::SOFTWARE]; if ((!result[SecurityLevel::TRUSTED_ENVIRONMENT]) && (!result[SecurityLevel::STRONGBOX])) { diff --git a/keystore2/src/km_compat/km_compat.h b/keystore2/src/km_compat/km_compat.h index 6654c4a6..c4bcdaa9 100644 --- a/keystore2/src/km_compat/km_compat.h +++ b/keystore2/src/km_compat/km_compat.h @@ -150,7 +150,8 @@ class KeyMintDevice : public aidl::android::hardware::security::keymint::BnKeyMi // These are public to allow testing code to use them directly. // This class should not be used publicly anyway. std::variant<std::vector<Certificate>, KMV1_ErrorCode> - getCertificate(const std::vector<KeyParameter>& keyParams, const std::vector<uint8_t>& keyBlob); + getCertificate(const std::vector<KeyParameter>& keyParams, const std::vector<uint8_t>& keyBlob, + bool isWrappedKey = false); void setNumFreeSlots(uint8_t numFreeSlots); diff --git a/keystore2/src/km_compat/lib.rs b/keystore2/src/km_compat/lib.rs index 2632ec49..e61a13a7 100644 --- a/keystore2/src/km_compat/lib.rs +++ b/keystore2/src/km_compat/lib.rs @@ -21,6 +21,7 @@ extern "C" { #[allow(missing_docs)] // TODO remove this pub fn add_keymint_device_service() -> i32 { + // SAFETY: This is always safe to call. unsafe { addKeyMintDeviceService() } } diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs index 2ffcc711..2bb7f27b 100644 --- a/keystore2/src/legacy_blob.rs +++ b/keystore2/src/legacy_blob.rs @@ -1313,7 +1313,7 @@ impl LegacyBlobLoader { Blob { flags, value: BlobValue::PwEncrypted { iv, tag, data, salt, key_size } } => { if (flags & flags::ENCRYPTED) != 0 { let key = pw - .derive_key(&salt, key_size) + .derive_key_pbkdf2(&salt, key_size) .context(ks_err!("Failed to derive key from password."))?; let blob = aes_gcm_decrypt(&data, &iv, &tag, &key) .context(ks_err!("while trying to decrypt legacy super key blob."))?; @@ -1953,7 +1953,7 @@ mod test { std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); + let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); let super_key = Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); @@ -2040,7 +2040,7 @@ mod test { std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); + let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); let super_key = Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); @@ -2128,7 +2128,7 @@ mod test { std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); + let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); let super_key = Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); diff --git a/keystore2/src/legacy_importer.rs b/keystore2/src/legacy_importer.rs index 9eb702dc..7dcb98d6 100644 --- a/keystore2/src/legacy_importer.rs +++ b/keystore2/src/legacy_importer.rs @@ -22,7 +22,7 @@ use crate::error::{map_km_error, Error}; use crate::key_parameter::{KeyParameter, KeyParameterValue}; use crate::ks_err; use crate::legacy_blob::{self, Blob, BlobValue, LegacyKeyCharacteristics}; -use crate::super_key::USER_SUPER_KEY; +use crate::super_key::USER_AFTER_FIRST_UNLOCK_SUPER_KEY; use crate::utils::{ key_characteristics_to_internal, uid_to_android_user, upgrade_keyblob_if_required_with, watchdog as wd, AesGcm, @@ -102,6 +102,11 @@ impl LegacyImporter { } } + #[cfg(test)] + pub fn set_empty(&mut self) { + self.state = AtomicU8::new(Self::STATE_EMPTY); + } + /// The legacy importer must be initialized deferred, because keystore starts very early. /// At this time the data partition may not be mounted. So we cannot open database connections /// until we get actual key load requests. This sets the function that the legacy loader @@ -445,7 +450,7 @@ impl LegacyImporterState { match self .db - .load_super_key(&USER_SUPER_KEY, user_id) + .load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, user_id) .context(ks_err!("Failed to load super key"))? { Some((_, entry)) => Ok(entry.id()), @@ -724,7 +729,7 @@ impl LegacyImporterState { self.db .store_super_key( user_id, - &USER_SUPER_KEY, + &USER_AFTER_FIRST_UNLOCK_SUPER_KEY, &blob, &blob_metadata, &KeyMetaData::new(), @@ -767,7 +772,7 @@ impl LegacyImporterState { let super_key_id = self .db - .load_super_key(&USER_SUPER_KEY, user_id) + .load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, user_id) .context(ks_err!("Failed to load super key"))? .map(|(_, entry)| entry.id()); @@ -909,11 +914,12 @@ fn get_key_characteristics_without_app_data( uuid: &Uuid, blob: &[u8], ) -> Result<(Vec<KeyParameter>, Option<Vec<u8>>)> { - let (km_dev, _) = crate::globals::get_keymint_dev_by_uuid(uuid) + let (km_dev, info) = crate::globals::get_keymint_dev_by_uuid(uuid) .with_context(|| ks_err!("Trying to get km device for id {:?}", uuid))?; let (characteristics, upgraded_blob) = upgrade_keyblob_if_required_with( &*km_dev, + info.versionNumber, blob, &[], |blob| { diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs index 97948899..c0eecd89 100644 --- a/keystore2/src/lib.rs +++ b/keystore2/src/lib.rs @@ -28,7 +28,6 @@ pub mod globals; pub mod id_rotation; /// Internal Representation of Key Parameter and convenience functions. pub mod key_parameter; -pub mod ks_err; pub mod legacy_blob; pub mod legacy_importer; pub mod maintenance; @@ -38,7 +37,6 @@ pub mod operation; pub mod permission; pub mod raw_device; pub mod remote_provisioning; -pub mod rkpd_client; pub mod security_level; pub mod service; pub mod shared_secret_negotiation; @@ -49,6 +47,7 @@ mod audit_log; mod gc; mod km_compat; mod super_key; +mod sw_keyblob; +mod watchdog_helper; -#[cfg(feature = "watchdog")] -mod watchdog; +use message_macro::source_location_msg as ks_err; diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs index 5efb798d..8780e9e9 100644 --- a/keystore2/src/maintenance.rs +++ b/keystore2/src/maintenance.rs @@ -14,7 +14,7 @@ //! This module implements IKeystoreMaintenance AIDL interface. -use crate::database::{KeyEntryLoadBits, KeyType, MonotonicRawTime}; +use crate::database::{KeyEntryLoadBits, KeyType}; use crate::error::map_km_error; use crate::error::map_or_log_err; use crate::error::Error; @@ -24,14 +24,14 @@ use crate::ks_err; use crate::permission::{KeyPerm, KeystorePerm}; use crate::super_key::{SuperKeyManager, UserState}; use crate::utils::{ - check_key_permission, check_keystore_permission, uid_to_android_user, watchdog as wd, + check_get_app_uids_affected_by_sid_permissions, check_key_permission, + check_keystore_permission, uid_to_android_user, watchdog as wd, }; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel, }; -use android_security_maintenance::aidl::android::security::maintenance::{ - IKeystoreMaintenance::{BnKeystoreMaintenance, IKeystoreMaintenance}, - UserState::UserState as AidlUserState, +use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::{ + BnKeystoreMaintenance, IKeystoreMaintenance, }; use android_security_maintenance::binder::{ BinderFeatures, Interface, Result as BinderResult, Strong, ThreadState, @@ -78,31 +78,29 @@ impl Maintenance { if let Some(pw) = password.as_ref() { DB.with(|db| { - skm.unlock_screen_lock_bound_key(&mut db.borrow_mut(), user_id as u32, pw) + skm.unlock_unlocked_device_required_keys(&mut db.borrow_mut(), user_id as u32, pw) }) - .context(ks_err!("unlock_screen_lock_bound_key failed"))?; + .context(ks_err!("unlock_unlocked_device_required_keys failed"))?; } - match DB - .with(|db| { - skm.reset_or_init_user_and_get_user_state( - &mut db.borrow_mut(), - &LEGACY_IMPORTER, - user_id as u32, - password.as_ref(), - ) - }) - .context(ks_err!())? + if let UserState::BeforeFirstUnlock = DB + .with(|db| skm.get_user_state(&mut db.borrow_mut(), &LEGACY_IMPORTER, user_id as u32)) + .context(ks_err!("Could not get user state while changing password!"))? { - UserState::LskfLocked => { - // Error - password can not be changed when the device is locked - Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked.")) + // Error - password can not be changed when the device is locked + return Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked.")); + } + + DB.with(|db| match password { + Some(pass) => { + skm.init_user(&mut db.borrow_mut(), &LEGACY_IMPORTER, user_id as u32, &pass) } - _ => { - // LskfLocked is the only error case for password change - Ok(()) + None => { + // User transitioned to swipe. + skm.reset_user(&mut db.borrow_mut(), &LEGACY_IMPORTER, user_id as u32) } - } + }) + .context(ks_err!("Failed to change user password!")) } fn add_or_remove_user(&self, user_id: i32) -> Result<()> { @@ -111,11 +109,10 @@ impl Maintenance { check_keystore_permission(KeystorePerm::ChangeUser).context(ks_err!())?; DB.with(|db| { - SUPER_KEY.write().unwrap().reset_user( + SUPER_KEY.write().unwrap().remove_user( &mut db.borrow_mut(), &LEGACY_IMPORTER, user_id as u32, - false, ) }) .context(ks_err!("Trying to delete keys from db."))?; @@ -124,6 +121,41 @@ impl Maintenance { .context(ks_err!("While invoking the delete listener.")) } + fn init_user_super_keys( + &self, + user_id: i32, + password: Password, + allow_existing: bool, + ) -> Result<()> { + // Permission check. Must return on error. Do not touch the '?'. + check_keystore_permission(KeystorePerm::ChangeUser).context(ks_err!())?; + + let mut skm = SUPER_KEY.write().unwrap(); + DB.with(|db| { + skm.initialize_user( + &mut db.borrow_mut(), + &LEGACY_IMPORTER, + user_id as u32, + &password, + allow_existing, + ) + }) + .context(ks_err!("Failed to initialize user super keys")) + } + + // Deletes all auth-bound keys when the user's LSKF is removed. + fn on_user_lskf_removed(user_id: i32) -> Result<()> { + // Permission check. Must return on error. Do not touch the '?'. + check_keystore_permission(KeystorePerm::ChangePassword).context(ks_err!())?; + + LEGACY_IMPORTER + .bulk_delete_user(user_id as u32, true) + .context(ks_err!("Failed to delete legacy keys."))?; + + DB.with(|db| db.borrow_mut().unbind_auth_bound_keys_for_user(user_id as u32)) + .context(ks_err!("Failed to delete auth-bound keys.")) + } + fn clear_namespace(&self, domain: Domain, nspace: i64) -> Result<()> { // Permission check. Must return on error. Do not touch the '?'. check_keystore_permission(KeystorePerm::ClearUID).context("In clear_namespace.")?; @@ -138,27 +170,6 @@ impl Maintenance { .context(ks_err!("While invoking the delete listener.")) } - fn get_state(user_id: i32) -> Result<AidlUserState> { - // Check permission. Function should return if this failed. Therefore having '?' at the end - // is very important. - check_keystore_permission(KeystorePerm::GetState).context("In get_state.")?; - let state = DB - .with(|db| { - SUPER_KEY.read().unwrap().get_user_state( - &mut db.borrow_mut(), - &LEGACY_IMPORTER, - user_id as u32, - ) - }) - .context(ks_err!("Trying to get UserState."))?; - - match state { - UserState::Uninitialized => Ok(AidlUserState::UNINITIALIZED), - UserState::LskfUnlocked(_) => Ok(AidlUserState::LSKF_UNLOCKED), - UserState::LskfLocked => Ok(AidlUserState::LSKF_LOCKED), - } - } - fn call_with_watchdog<F>(sec_level: SecurityLevel, name: &'static str, op: &F) -> Result<()> where F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>, @@ -181,7 +192,7 @@ impl Maintenance { (SecurityLevel::TRUSTED_ENVIRONMENT, "TRUSTED_ENVIRONMENT"), (SecurityLevel::STRONGBOX, "STRONGBOX"), ]; - sec_levels.iter().fold(Ok(()), move |result, (sec_level, sec_level_string)| { + sec_levels.iter().try_fold((), |_result, (sec_level, sec_level_string)| { let curr_result = Maintenance::call_with_watchdog(*sec_level, name, &op); match curr_result { Ok(()) => log::info!( @@ -196,7 +207,7 @@ impl Maintenance { e ), } - result.and(curr_result) + curr_result }) } @@ -213,14 +224,6 @@ impl Maintenance { Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded()) } - fn on_device_off_body() -> Result<()> { - // Security critical permission check. This statement must return on fail. - check_keystore_permission(KeystorePerm::ReportOffBody).context(ks_err!())?; - - DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now())); - Ok(()) - } - fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> { let calling_uid = ThreadState::get_calling_uid(); @@ -242,7 +245,7 @@ impl Maintenance { let user_id = uid_to_android_user(calling_uid); - let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id); + let super_key = SUPER_KEY.read().unwrap().get_after_first_unlock_key_by_user_id(user_id); DB.with(|db| { let (key_id_guard, _) = LEGACY_IMPORTER @@ -276,57 +279,97 @@ impl Maintenance { Maintenance::call_on_all_security_levels("deleteAllKeys", |dev| dev.deleteAllKeys()) } + + fn get_app_uids_affected_by_sid( + user_id: i32, + secure_user_id: i64, + ) -> Result<std::vec::Vec<i64>> { + // This method is intended to be called by Settings and discloses a list of apps + // associated with a user, so it requires the "android.permission.MANAGE_USERS" + // permission (to avoid leaking list of apps to unauthorized callers). + check_get_app_uids_affected_by_sid_permissions().context(ks_err!())?; + DB.with(|db| db.borrow_mut().get_app_uids_affected_by_sid(user_id, secure_user_id)) + .context(ks_err!("Failed to get app UIDs affected by SID")) + } } impl Interface for Maintenance {} impl IKeystoreMaintenance for Maintenance { fn onUserPasswordChanged(&self, user_id: i32, password: Option<&[u8]>) -> BinderResult<()> { + log::info!( + "onUserPasswordChanged(user={}, password.is_some()={})", + user_id, + password.is_some() + ); let _wp = wd::watch_millis("IKeystoreMaintenance::onUserPasswordChanged", 500); map_or_log_err(Self::on_user_password_changed(user_id, password.map(|pw| pw.into())), Ok) } fn onUserAdded(&self, user_id: i32) -> BinderResult<()> { + log::info!("onUserAdded(user={user_id})"); let _wp = wd::watch_millis("IKeystoreMaintenance::onUserAdded", 500); map_or_log_err(self.add_or_remove_user(user_id), Ok) } + fn initUserSuperKeys( + &self, + user_id: i32, + password: &[u8], + allow_existing: bool, + ) -> BinderResult<()> { + log::info!("initUserSuperKeys(user={user_id}, allow_existing={allow_existing})"); + let _wp = wd::watch_millis("IKeystoreMaintenance::initUserSuperKeys", 500); + map_or_log_err(self.init_user_super_keys(user_id, password.into(), allow_existing), Ok) + } + fn onUserRemoved(&self, user_id: i32) -> BinderResult<()> { + log::info!("onUserRemoved(user={user_id})"); let _wp = wd::watch_millis("IKeystoreMaintenance::onUserRemoved", 500); map_or_log_err(self.add_or_remove_user(user_id), Ok) } + fn onUserLskfRemoved(&self, user_id: i32) -> BinderResult<()> { + log::info!("onUserLskfRemoved(user={user_id})"); + let _wp = wd::watch_millis("IKeystoreMaintenance::onUserLskfRemoved", 500); + map_or_log_err(Self::on_user_lskf_removed(user_id), Ok) + } + fn clearNamespace(&self, domain: Domain, nspace: i64) -> BinderResult<()> { + log::info!("clearNamespace({domain:?}, nspace={nspace})"); let _wp = wd::watch_millis("IKeystoreMaintenance::clearNamespace", 500); map_or_log_err(self.clear_namespace(domain, nspace), Ok) } - fn getState(&self, user_id: i32) -> BinderResult<AidlUserState> { - let _wp = wd::watch_millis("IKeystoreMaintenance::getState", 500); - map_or_log_err(Self::get_state(user_id), Ok) - } - fn earlyBootEnded(&self) -> BinderResult<()> { + log::info!("earlyBootEnded()"); let _wp = wd::watch_millis("IKeystoreMaintenance::earlyBootEnded", 500); map_or_log_err(Self::early_boot_ended(), Ok) } - fn onDeviceOffBody(&self) -> BinderResult<()> { - let _wp = wd::watch_millis("IKeystoreMaintenance::onDeviceOffBody", 500); - map_or_log_err(Self::on_device_off_body(), Ok) - } - fn migrateKeyNamespace( &self, source: &KeyDescriptor, destination: &KeyDescriptor, ) -> BinderResult<()> { + log::info!("migrateKeyNamespace(src={source:?}, dest={destination:?})"); let _wp = wd::watch_millis("IKeystoreMaintenance::migrateKeyNamespace", 500); map_or_log_err(Self::migrate_key_namespace(source, destination), Ok) } fn deleteAllKeys(&self) -> BinderResult<()> { + log::warn!("deleteAllKeys()"); let _wp = wd::watch_millis("IKeystoreMaintenance::deleteAllKeys", 500); map_or_log_err(Self::delete_all_keys(), Ok) } + + fn getAppUidsAffectedBySid( + &self, + user_id: i32, + secure_user_id: i64, + ) -> BinderResult<std::vec::Vec<i64>> { + log::info!("getAppUidsAffectedBySid(secure_user_id={secure_user_id:?})"); + let _wp = wd::watch_millis("IKeystoreMaintenance::getAppUidsAffectedBySid", 500); + map_or_log_err(Self::get_app_uids_affected_by_sid(user_id, secure_user_id), Ok) + } } diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs index 6043612c..5a76d04e 100644 --- a/keystore2/src/metrics_store.rs +++ b/keystore2/src/metrics_store.rs @@ -17,12 +17,11 @@ //! stores them in an in-memory store. //! 2. Returns the collected metrics when requested by the statsd proxy. -use crate::error::{get_error_code, Error}; +use crate::error::anyhow_error_to_serialized_error; use crate::globals::DB; use crate::key_parameter::KeyParameterValue as KsKeyParamValue; use crate::ks_err; use crate::operation::Outcome; -use crate::remote_provisioning::get_pool_status; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin, @@ -42,16 +41,12 @@ use android_security_metrics::aidl::android::security::metrics::{ KeystoreAtom::KeystoreAtom, KeystoreAtomPayload::KeystoreAtomPayload, Outcome::Outcome as MetricsOutcome, Purpose::Purpose as MetricsPurpose, RkpError::RkpError as MetricsRkpError, RkpErrorStats::RkpErrorStats, - RkpPoolStats::RkpPoolStats, SecurityLevel::SecurityLevel as MetricsSecurityLevel, - Storage::Storage as MetricsStorage, + SecurityLevel::SecurityLevel as MetricsSecurityLevel, Storage::Storage as MetricsStorage, }; -use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use lazy_static::lazy_static; -use rustutils::system_properties::PropertyWatcherError; use std::collections::HashMap; use std::sync::Mutex; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; // Note: Crash events are recorded at keystore restarts, based on the assumption that keystore only // gets restarted after a crash, during a boot cycle. @@ -95,19 +90,17 @@ impl MetricsStore { return pull_storage_stats(); } - // Process and return RKP pool stats. - if AtomID::RKP_POOL_STATS == atom_id { - return pull_attestation_pool_stats(); - } - // Process keystore crash stats. if AtomID::CRASH_STATS == atom_id { - return Ok(vec![KeystoreAtom { - payload: KeystoreAtomPayload::CrashStats(CrashStats { - count_of_crash_events: read_keystore_crash_count()?, - }), - ..Default::default() - }]); + return match read_keystore_crash_count()? { + Some(count) => Ok(vec![KeystoreAtom { + payload: KeystoreAtomPayload::CrashStats(CrashStats { + count_of_crash_events: count, + }), + ..Default::default() + }]), + None => Err(anyhow!("Crash count property is not set")), + }; } // It is safe to call unwrap here since the lock can not be poisoned based on its usage @@ -126,15 +119,14 @@ impl MetricsStore { // It is ok to unwrap here since the mutex cannot be poisoned according to the way it is // used in this module. And the lock is not acquired by this thread before. let mut metrics_store_guard = self.metrics_store.lock().unwrap(); - let atom_count_map = metrics_store_guard.entry(atom_id).or_insert_with(HashMap::new); + let atom_count_map = metrics_store_guard.entry(atom_id).or_default(); if atom_count_map.len() < MetricsStore::SINGLE_ATOM_STORE_MAX_SIZE { let atom_count = atom_count_map.entry(atom).or_insert(0); *atom_count += 1; } else { // Insert an overflow atom - let overflow_atom_count_map = metrics_store_guard - .entry(AtomID::KEYSTORE2_ATOM_WITH_OVERFLOW) - .or_insert_with(HashMap::new); + let overflow_atom_count_map = + metrics_store_guard.entry(AtomID::KEYSTORE2_ATOM_WITH_OVERFLOW).or_default(); if overflow_atom_count_map.len() < MetricsStore::SINGLE_ATOM_STORE_MAX_SIZE { let overflow_atom = Keystore2AtomWithOverflow { atom_id }; @@ -209,7 +201,7 @@ fn process_key_creation_event_stats<U>( }; if let Err(ref e) = result { - key_creation_with_general_info.error_code = get_error_code(e); + key_creation_with_general_info.error_code = anyhow_error_to_serialized_error(e).0; } key_creation_with_auth_info.security_level = process_security_level(sec_level); @@ -560,45 +552,6 @@ fn pull_storage_stats() -> Result<Vec<KeystoreAtom>> { Ok(atom_vec) } -fn pull_attestation_pool_stats() -> Result<Vec<KeystoreAtom>> { - let mut atoms = Vec::<KeystoreAtom>::new(); - for sec_level in &[SecurityLevel::TRUSTED_ENVIRONMENT, SecurityLevel::STRONGBOX] { - // set the expired_by date to be three days from now - let expired_by = SystemTime::now() - .checked_add(Duration::from_secs(60 * 60 * 24 * 3)) - .ok_or(Error::Rc(ResponseCode::SYSTEM_ERROR)) - .context(ks_err!("Failed to compute expired by system time."))? - .duration_since(UNIX_EPOCH) - .context(ks_err!("Failed to compute expired by duration."))? - .as_millis() as i64; - - let result = get_pool_status(expired_by, *sec_level); - - if let Ok(pool_status) = result { - let rkp_pool_stats = RkpPoolStats { - security_level: process_security_level(*sec_level), - expiring: pool_status.expiring, - unassigned: pool_status.unassigned, - attested: pool_status.attested, - total: pool_status.total, - }; - atoms.push(KeystoreAtom { - payload: KeystoreAtomPayload::RkpPoolStats(rkp_pool_stats), - ..Default::default() - }); - } else { - log::error!( - concat!( - "In pull_attestation_pool_stats: Failed to retrieve pool status", - " for security level: {:?}" - ), - sec_level - ); - } - } - Ok(atoms) -} - /// Log error events related to Remote Key Provisioning (RKP). pub fn log_rkp_error_stats(rkp_error: MetricsRkpError, sec_level: &SecurityLevel) { let rkp_error_stats = KeystoreAtomPayload::RkpErrorStats(RkpErrorStats { @@ -612,27 +565,21 @@ pub fn log_rkp_error_stats(rkp_error: MetricsRkpError, sec_level: &SecurityLevel /// If the property is absent, it sets the property with value 0. If the property is present, it /// increments the value. This helps tracking keystore crashes internally. pub fn update_keystore_crash_sysprop() { - let crash_count = read_keystore_crash_count(); - let new_count = match crash_count { - Ok(count) => count + 1, + let new_count = match read_keystore_crash_count() { + Ok(Some(count)) => count + 1, + // If the property is absent, then this is the first start up during the boot. + // Proceed to write the system property with value 0. + Ok(None) => 0, Err(error) => { - // If the property is absent, this is the first start up during the boot. - // Proceed to write the system property with value 0. Otherwise, log and return. - if !matches!( - error.root_cause().downcast_ref::<PropertyWatcherError>(), - Some(PropertyWatcherError::SystemPropertyAbsent) - ) { - log::warn!( - concat!( - "In update_keystore_crash_sysprop: ", - "Failed to read the existing system property due to: {:?}.", - "Therefore, keystore crashes will not be logged." - ), - error - ); - return; - } - 0 + log::warn!( + concat!( + "In update_keystore_crash_sysprop: ", + "Failed to read the existing system property due to: {:?}.", + "Therefore, keystore crashes will not be logged." + ), + error + ); + return; } }; @@ -650,12 +597,12 @@ pub fn update_keystore_crash_sysprop() { } /// Read the system property: keystore.crash_count. -pub fn read_keystore_crash_count() -> Result<i32> { - rustutils::system_properties::read("keystore.crash_count") - .context(ks_err!("Failed read property."))? - .context(ks_err!("Property not set."))? - .parse::<i32>() - .map_err(std::convert::Into::into) +pub fn read_keystore_crash_count() -> Result<Option<i32>> { + match rustutils::system_properties::read("keystore.crash_count") { + Ok(Some(count)) => count.parse::<i32>().map(Some).map_err(std::convert::Into::into), + Ok(None) => Ok(None), + Err(e) => Err(e).context(ks_err!("Failed to read crash count property.")), + } } /// Enum defining the bit position for each padding mode. Since padding mode can be repeatable, it diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs index 2034a8a0..11eaf17a 100644 --- a/keystore2/src/operation.rs +++ b/keystore2/src/operation.rs @@ -126,7 +126,10 @@ //! Either way, we have to revaluate the pruning scores. use crate::enforcements::AuthInfo; -use crate::error::{map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, ResponseCode}; +use crate::error::{ + error_to_serialized_error, map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, + ResponseCode, SerializedError, +}; use crate::ks_err; use crate::metrics_store::log_key_operation_event_stats; use crate::utils::watchdog as wd; @@ -162,7 +165,7 @@ pub enum Outcome { /// Operation is pruned. Pruned, /// Operation is failed with the error code. - ErrorCode(ErrorCode), + ErrorCode(SerializedError), } /// Operation bundles all of the operation related resources and tracks the operation's @@ -287,7 +290,7 @@ impl Operation { // We abort the operation. If there was an error we log it but ignore it. if let Err(e) = map_km_error(self.km_op.abort()) { - log::error!("In prune: KeyMint::abort failed with {:?}.", e); + log::warn!("In prune: KeyMint::abort failed with {:?}.", e); } Ok(()) @@ -305,8 +308,7 @@ impl Operation { err: Result<T, Error>, ) -> Result<T, Error> { match &err { - Err(Error::Km(e)) => *locked_outcome = Outcome::ErrorCode(*e), - Err(_) => *locked_outcome = Outcome::ErrorCode(ErrorCode::UNKNOWN_ERROR), + Err(e) => *locked_outcome = Outcome::ErrorCode(error_to_serialized_error(e)), Ok(_) => (), } err diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs index d9bdf791..982bc821 100644 --- a/keystore2/src/permission.rs +++ b/keystore2/src/permission.rs @@ -109,9 +109,6 @@ implement_class!( /// Checked when an app is uninstalled or wiped. #[selinux(name = clear_ns)] ClearNs, - /// Checked when the user state is queried from Keystore 2.0. - #[selinux(name = get_state)] - GetState, /// Checked when Keystore 2.0 is asked to list a namespace that the caller /// does not have the get_info permission for. #[selinux(name = list)] @@ -140,10 +137,7 @@ implement_class!( /// Checked when earlyBootEnded() is called. #[selinux(name = early_boot_ended)] EarlyBootEnded, - /// Checked when IKeystoreMaintenance::onDeviceOffBody is called. - #[selinux(name = report_off_body)] - ReportOffBody, - /// Checked when IkeystoreMetrics::pullMetrics is called. + /// Checked when IKeystoreMetrics::pullMetrics is called. #[selinux(name = pull_metrics)] PullMetrics, /// Checked when IKeystoreMaintenance::deleteAllKeys is called. @@ -152,6 +146,9 @@ implement_class!( /// Checked on calls to IRemotelyProvisionedKeyPool::getAttestationKey #[selinux(name = get_attestation_key)] GetAttestationKey, + /// Checked on IKeystoreAuthorization::getLastAuthTime() is called. + #[selinux(name = get_last_auth_time)] + GetLastAuthTime, } ); @@ -500,7 +497,6 @@ mod tests { let system_server_ctx = Context::new("u:r:system_server:s0")?; assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok()); assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::GetState).is_ok()); assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Lock).is_ok()); assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Reset).is_ok()); assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Unlock).is_ok()); @@ -510,7 +506,6 @@ mod tests { let shell_ctx = Context::new("u:r:shell:s0")?; assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth)); assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs)); - assert!(check_keystore_permission(&shell_ctx, KeystorePerm::GetState).is_ok()); assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::List)); assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Lock)); assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Reset)); diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs index fa9872a7..44d805c3 100644 --- a/keystore2/src/raw_device.rs +++ b/keystore2/src/raw_device.rs @@ -263,35 +263,31 @@ impl KeyMintDevice { where F: Fn(&[u8]) -> Result<T, Error>, { - match f(&key_blob) { - Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => { - let upgraded_blob = map_km_error({ - let _wp = wd::watch_millis( - "In KeyMintDevice::upgrade_keyblob_if_required_with: calling upgradeKey.", - 500, - ); - self.km_dev.upgradeKey(&key_blob, &[]) - }) - .context(ks_err!("Upgrade failed"))?; - + let (f_result, upgraded_blob) = crate::utils::upgrade_keyblob_if_required_with( + &*self.km_dev, + self.version(), + &key_blob, + &[], + f, + |upgraded_blob| { let mut new_blob_metadata = BlobMetaData::new(); new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid)); db.set_blob( key_id_guard, SubComponentType::KEY_BLOB, - Some(&upgraded_blob), + Some(upgraded_blob), Some(&new_blob_metadata), ) .context(ks_err!("Failed to insert upgraded blob into the database"))?; - - Ok(( - f(&upgraded_blob).context(ks_err!("Closure failed after upgrade"))?, - KeyBlob::NonSensitive(upgraded_blob), - )) - } - result => Ok((result.context(ks_err!("Closure failed"))?, key_blob)), - } + Ok(()) + }, + )?; + let returned_blob = match upgraded_blob { + None => key_blob, + Some(upgraded_blob) => KeyBlob::NonSensitive(upgraded_blob), + }; + Ok((f_result, returned_blob)) } /// Use the created key in an operation that can be done with diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs index 1a833391..0ef8c953 100644 --- a/keystore2/src/remote_provisioning.rs +++ b/keystore2/src/remote_provisioning.rs @@ -19,42 +19,24 @@ //! certificate chains signed by some root authority and stored in a keystore SQLite //! DB. -use std::collections::HashMap; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag, }; -use android_hardware_security_rkp::aidl::android::hardware::security::keymint::{ - DeviceInfo::DeviceInfo, IRemotelyProvisionedComponent::IRemotelyProvisionedComponent, - MacedPublicKey::MacedPublicKey, ProtectedData::ProtectedData, -}; -use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{ - AttestationPoolStatus::AttestationPoolStatus, IRemoteProvisioning::BnRemoteProvisioning, - IRemoteProvisioning::IRemoteProvisioning, - IRemotelyProvisionedKeyPool::BnRemotelyProvisionedKeyPool, - IRemotelyProvisionedKeyPool::IRemotelyProvisionedKeyPool, ImplInfo::ImplInfo, - RemotelyProvisionedKey::RemotelyProvisionedKey, -}; -use android_security_remoteprovisioning::binder::{BinderFeatures, Strong}; +use android_security_rkp_aidl::aidl::android::security::rkp::RemotelyProvisionedKey::RemotelyProvisionedKey; use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, + Domain::Domain, KeyDescriptor::KeyDescriptor, }; use anyhow::{Context, Result}; use keystore2_crypto::parse_subject_from_certificate; -use serde_cbor::Value; -use std::collections::BTreeMap; -use std::sync::atomic::{AtomicBool, Ordering}; -use crate::database::{CertificateChain, KeyIdGuard, KeystoreDB, Uuid}; -use crate::error::{self, map_or_log_err, map_rem_prov_error, Error}; -use crate::globals::{get_keymint_device, get_remotely_provisioned_component, DB}; +use crate::database::Uuid; +use crate::error::wrapped_rkpd_error_to_ks_error; +use crate::globals::get_remotely_provisioned_component_name; use crate::ks_err; use crate::metrics_store::log_rkp_error_stats; -use crate::permission::KeystorePerm; -use crate::rkpd_client::get_rkpd_attestation_key; -use crate::utils::{check_keystore_permission, watchdog as wd}; +use crate::watchdog_helper::watchdog as wd; use android_security_metrics::aidl::android::security::metrics::RkpError::RkpError as MetricsRkpError; /// Contains helper functions to check if remote provisioning is enabled on the system and, if so, @@ -63,18 +45,12 @@ use android_security_metrics::aidl::android::security::metrics::RkpError::RkpErr pub struct RemProvState { security_level: SecurityLevel, km_uuid: Uuid, - is_hal_present: AtomicBool, } -static COSE_KEY_XCOORD: Value = Value::Integer(-2); -static COSE_KEY_YCOORD: Value = Value::Integer(-3); -static COSE_MAC0_LEN: usize = 4; -static COSE_MAC0_PAYLOAD: usize = 2; - impl RemProvState { /// Creates a RemProvState struct. pub fn new(security_level: SecurityLevel, km_uuid: Uuid) -> Self { - Self { security_level, km_uuid, is_hal_present: AtomicBool::new(true) } + Self { security_level, km_uuid } } /// Returns the uuid for the KM instance attached to this RemProvState struct. @@ -95,30 +71,6 @@ impl RemProvState { .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() - { - self.is_hal_present.store(false, Ordering::Relaxed); - return Ok(false); - } - // To check if remote provisioning is enabled on a system that supports both remote - // provisioning and factory provisioned keys, we only need to check if there are any - // keys at all generated to indicate if the app has gotten the signal to begin filling - // the key pool from the server. - let pool_status = db - .get_attestation_pool_status(0 /* date */, &self.km_uuid) - .context("In check_rem_prov_enabled: failed to get attestation pool status.")?; - Ok(pool_status.total != 0) - } - fn is_asymmetric_key(&self, params: &[KeyParameter]) -> bool { params.iter().any(|kp| { matches!( @@ -134,58 +86,6 @@ impl RemProvState { }) } - /// Checks to see (1) if the key in question should be attested to based on the algorithm and - /// (2) if remote provisioning is present and enabled on the system. If these conditions are - /// met, it makes an attempt to fetch the attestation key assigned to the `caller_uid`. - /// - /// It returns the ResponseCode `OUT_OF_KEYS_TRANSIENT_ERROR` if there is not one key currently - /// assigned to the `caller_uid` and there are none available to assign. - pub fn get_remotely_provisioned_attestation_key_and_certs( - &self, - key: &KeyDescriptor, - caller_uid: u32, - params: &[KeyParameter], - db: &mut KeystoreDB, - ) -> Result<Option<(KeyIdGuard, AttestationKey, Certificate)>> { - if !self.is_asymmetric_key(params) || !self.check_rem_prov_enabled(db)? { - // There is no remote provisioning component for this security level on the - // device. Return None so the underlying KM instance knows to use its - // factory provisioned key instead. Alternatively, it's not an asymmetric key - // and therefore will not be attested. - Ok(None) - } else { - match get_rem_prov_attest_key(key.domain, caller_uid, db, &self.km_uuid) { - Err(e) => { - if self.is_rkp_only() { - log::error!("Error occurred: {:?}", e); - return Err(e); - } - log::warn!("Error occurred: {:?}", e); - log_rkp_error_stats( - MetricsRkpError::FALL_BACK_DURING_HYBRID, - &self.security_level, - ); - Ok(None) - } - Ok(v) => match v { - Some((guard, cert_chain)) => Ok(Some(( - guard, - AttestationKey { - keyBlob: cert_chain.private_key.to_vec(), - attestKeyParams: vec![], - issuerSubjectName: parse_subject_from_certificate( - &cert_chain.batch_cert, - ) - .context(ks_err!("Failed to parse subject."))?, - }, - Certificate { encodedCertificate: cert_chain.cert_chain }, - ))), - None => Ok(None), - }, - } - } - } - /// Fetches attestation key and corresponding certificates from RKPD. pub fn get_rkpd_attestation_key_and_certs( &self, @@ -200,7 +100,7 @@ impl RemProvState { Err(e) => { if self.is_rkp_only() { log::error!("Error occurred: {:?}", e); - return Err(e); + return Err(wrapped_rkpd_error_to_ks_error(&e)).context(format!("{e:?}")); } log::warn!("Error occurred: {:?}", e); log_rkp_error_stats( @@ -226,895 +126,16 @@ impl RemProvState { } } -/// Implementation of the IRemoteProvisioning service. -#[derive(Default)] -pub struct RemoteProvisioningService { - device_by_sec_level: HashMap<SecurityLevel, Strong<dyn IRemotelyProvisionedComponent>>, - curve_by_sec_level: HashMap<SecurityLevel, i32>, -} - -impl RemoteProvisioningService { - fn get_dev_by_sec_level( - &self, - sec_level: &SecurityLevel, - ) -> Result<&dyn IRemotelyProvisionedComponent> { - if let Some(dev) = self.device_by_sec_level.get(sec_level) { - Ok(dev.as_ref()) - } else { - Err(error::Error::sys()).context(ks_err!( - "Remote instance for requested security level \ - not found.", - )) - } - } - - /// Creates a new instance of the remote provisioning service - pub fn new_native_binder() -> Result<Strong<dyn IRemoteProvisioning>> { - let mut result: Self = Default::default(); - let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT) - .context(ks_err!("Failed to get TEE Remote Provisioner instance."))?; - result.curve_by_sec_level.insert( - SecurityLevel::TRUSTED_ENVIRONMENT, - dev.getHardwareInfo() - .context(ks_err!("Failed to get hardware info for the TEE."))? - .supportedEekCurve, - ); - result.device_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, dev); - if let Ok(dev) = get_remotely_provisioned_component(&SecurityLevel::STRONGBOX) { - result.curve_by_sec_level.insert( - SecurityLevel::STRONGBOX, - dev.getHardwareInfo() - .context(ks_err!("Failed to get hardware info for StrongBox."))? - .supportedEekCurve, - ); - result.device_by_sec_level.insert(SecurityLevel::STRONGBOX, dev); - } - Ok(BnRemoteProvisioning::new_binder(result, BinderFeatures::default())) - } - - fn extract_payload_from_cose_mac(data: &[u8]) -> Result<Value> { - let cose_mac0: Vec<Value> = serde_cbor::from_slice(data) - .context(ks_err!("COSE_Mac0 returned from IRPC cannot be parsed"))?; - if cose_mac0.len() != COSE_MAC0_LEN { - return Err(error::Error::sys()).context(ks_err!( - "COSE_Mac0 has improper length. \ - Expected: {}, Actual: {}", - COSE_MAC0_LEN, - cose_mac0.len(), - )); - } - match &cose_mac0[COSE_MAC0_PAYLOAD] { - Value::Bytes(key) => { - Ok(serde_cbor::from_slice(key) - .context(ks_err!("COSE_Mac0 payload is malformed."))?) - } - _ => { - Err(error::Error::sys()).context(ks_err!("COSE_Mac0 payload is the wrong type."))? - } - } - } - - /// Generates a CBOR blob which will be assembled by the calling code into a larger - /// CBOR blob intended for delivery to a provisioning serever. This blob will contain - /// `num_csr` certificate signing requests for attestation keys generated in the TEE, - /// along with a server provided `eek` and `challenge`. The endpoint encryption key will - /// be used to encrypt the sensitive contents being transmitted to the server, and the - /// challenge will ensure freshness. A `test_mode` flag will instruct the remote provisioning - /// HAL if it is okay to accept EEKs that aren't signed by something that chains back to the - /// baked in root of trust in the underlying IRemotelyProvisionedComponent instance. - #[allow(clippy::too_many_arguments)] - pub fn generate_csr( - &self, - test_mode: bool, - num_csr: i32, - eek: &[u8], - challenge: &[u8], - sec_level: SecurityLevel, - protected_data: &mut ProtectedData, - device_info: &mut DeviceInfo, - ) -> Result<Vec<u8>> { - let dev = self.get_dev_by_sec_level(&sec_level)?; - let (_, _, uuid) = get_keymint_device(&sec_level)?; - let keys_to_sign = DB.with::<_, Result<Vec<MacedPublicKey>>>(|db| { - let mut db = db.borrow_mut(); - Ok(db - .fetch_unsigned_attestation_keys(num_csr, &uuid)? - .iter() - .map(|key| MacedPublicKey { macedKey: key.to_vec() }) - .collect()) - })?; - let mac = map_rem_prov_error(dev.generateCertificateRequest( - test_mode, - &keys_to_sign, - eek, - challenge, - device_info, - protected_data, - )) - .context(ks_err!("Failed to generate csr"))?; - let mut mac_and_keys: Vec<Value> = vec![Value::from(mac)]; - for maced_public_key in keys_to_sign { - mac_and_keys.push( - Self::extract_payload_from_cose_mac(&maced_public_key.macedKey) - .context(ks_err!("Failed to get the payload from the COSE_Mac0"))?, - ) - } - let cbor_array: Value = Value::Array(mac_and_keys); - serde_cbor::to_vec(&cbor_array) - .context(ks_err!("Failed to serialize the mac and keys array")) - } - - /// Provisions a certificate chain for a key whose CSR was included in generate_csr. The - /// `public_key` is used to index into the SQL database in order to insert the `certs` blob - /// which represents a PEM encoded X.509 certificate chain. The `expiration_date` is provided - /// as a convenience from the caller to avoid having to parse the certificates semantically - /// here. - pub fn provision_cert_chain( - &self, - db: &mut KeystoreDB, - public_key: &[u8], - batch_cert: &[u8], - certs: &[u8], - expiration_date: i64, - sec_level: SecurityLevel, - ) -> Result<()> { - let (_, _, uuid) = get_keymint_device(&sec_level)?; - db.store_signed_attestation_certificate_chain( - public_key, - batch_cert, - certs, /* DER encoded certificate chain */ - expiration_date, - &uuid, - ) - } - - fn parse_cose_mac0_for_coords(data: &[u8]) -> Result<Vec<u8>> { - let cose_mac0: Vec<Value> = serde_cbor::from_slice(data) - .context(ks_err!("COSE_Mac0 returned from IRPC cannot be parsed"))?; - if cose_mac0.len() != COSE_MAC0_LEN { - return Err(error::Error::sys()).context(ks_err!( - "COSE_Mac0 has improper length. \ - Expected: {}, Actual: {}", - COSE_MAC0_LEN, - cose_mac0.len(), - )); - } - let cose_key: BTreeMap<Value, Value> = match &cose_mac0[COSE_MAC0_PAYLOAD] { - Value::Bytes(key) => { - serde_cbor::from_slice(key).context(ks_err!("COSE_Key is malformed."))? - } - _ => { - Err(error::Error::sys()).context(ks_err!("COSE_Mac0 payload is the wrong type."))? - } - }; - if !cose_key.contains_key(&COSE_KEY_XCOORD) || !cose_key.contains_key(&COSE_KEY_YCOORD) { - return Err(error::Error::sys()) - .context(ks_err!("COSE_Key returned from IRPC is lacking required fields")); - } - let mut raw_key: Vec<u8> = vec![0; 64]; - match &cose_key[&COSE_KEY_XCOORD] { - Value::Bytes(x_coord) if x_coord.len() == 32 => { - raw_key[0..32].clone_from_slice(x_coord) - } - Value::Bytes(x_coord) => { - return Err(error::Error::sys()).context(ks_err!( - "COSE_Key X-coordinate is not the right length. \ - Expected: 32; Actual: {}", - x_coord.len() - )); - } - _ => { - return Err(error::Error::sys()) - .context(ks_err!("COSE_Key X-coordinate is not a bstr")); - } - } - match &cose_key[&COSE_KEY_YCOORD] { - Value::Bytes(y_coord) if y_coord.len() == 32 => { - raw_key[32..64].clone_from_slice(y_coord) - } - Value::Bytes(y_coord) => { - return Err(error::Error::sys()).context(ks_err!( - "COSE_Key Y-coordinate is not the right length. \ - Expected: 32; Actual: {}", - y_coord.len() - )); - } - _ => { - return Err(error::Error::sys()) - .context(ks_err!("COSE_Key Y-coordinate is not a bstr")); - } - } - Ok(raw_key) - } - - /// Submits a request to the Remote Provisioner HAL to generate a signing key pair. - /// `is_test_mode` indicates whether or not the returned public key should be marked as being - /// for testing in order to differentiate them from private keys. If the call is successful, - /// the key pair is then added to the database. - pub fn generate_key_pair( - &self, - db: &mut KeystoreDB, - is_test_mode: bool, - sec_level: SecurityLevel, - ) -> Result<()> { - let (_, _, uuid) = get_keymint_device(&sec_level)?; - let dev = self - .get_dev_by_sec_level(&sec_level) - .context(ks_err!("Failed to get device for security level {:?}", sec_level))?; - let mut maced_key = MacedPublicKey { macedKey: Vec::new() }; - let priv_key = - map_rem_prov_error(dev.generateEcdsaP256KeyPair(is_test_mode, &mut maced_key)) - .context(ks_err!("Failed to generated ECDSA keypair."))?; - let raw_key = Self::parse_cose_mac0_for_coords(&maced_key.macedKey) - .context(ks_err!("Failed to parse raw key"))?; - db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid) - .context(ks_err!("Failed to insert attestation key entry")) - } - - /// Checks the security level of each available IRemotelyProvisionedComponent hal and returns - /// all levels in an array to the caller. - pub fn get_implementation_info(&self) -> Result<Vec<ImplInfo>> { - Ok(self - .curve_by_sec_level - .iter() - .map(|(sec_level, curve)| ImplInfo { secLevel: *sec_level, supportedCurve: *curve }) - .collect()) - } - - /// Deletes all attestation keys generated by the IRemotelyProvisionedComponent from the device, - /// regardless of what state of the attestation key lifecycle they were in. - pub fn delete_all_keys(&self) -> Result<i64> { - DB.with::<_, Result<i64>>(|db| { - let mut db = db.borrow_mut(); - db.delete_all_attestation_keys() - }) - } -} - -/// Populates the AttestationPoolStatus parcelable with information about how many -/// certs will be expiring by the date provided in `expired_by` along with how many -/// keys have not yet been assigned. -pub fn get_pool_status(expired_by: i64, sec_level: SecurityLevel) -> Result<AttestationPoolStatus> { - let (_, _, uuid) = get_keymint_device(&sec_level)?; - DB.with::<_, Result<AttestationPoolStatus>>(|db| { - let mut db = db.borrow_mut(); - // delete_expired_attestation_keys is always safe to call, and will remove anything - // older than the date at the time of calling. No work should be done on the - // attestation keys unless the pool status is checked first, so this call should be - // enough to routinely clean out expired keys. - db.delete_expired_attestation_keys()?; - db.get_attestation_pool_status(expired_by, &uuid) - }) -} - -/// Fetches a remote provisioning attestation key and certificate chain inside of the -/// returned `CertificateChain` struct if one exists for the given caller_uid. If one has not -/// been assigned, this function will assign it. If there are no signed attestation keys -/// available to be assigned, it will return the ResponseCode `OUT_OF_KEYS_TRANSIENT_ERROR` -fn get_rem_prov_attest_key( - domain: Domain, - caller_uid: u32, - db: &mut KeystoreDB, - km_uuid: &Uuid, -) -> Result<Option<(KeyIdGuard, CertificateChain)>> { - match domain { - Domain::APP => { - // Attempt to get an Attestation Key once. If it fails, then the app doesn't - // have a valid chain assigned to it. The helper function will return None after - // attempting to assign a key. An error will be thrown if the pool is simply out - // of usable keys. Then another attempt to fetch the just-assigned key will be - // made. If this fails too, something is very wrong. - get_rem_prov_attest_key_helper(domain, caller_uid, db, km_uuid) - .context("In get_rem_prov_attest_key: Failed to get a key")? - .map_or_else( - || get_rem_prov_attest_key_helper(domain, caller_uid, db, km_uuid), - |v| Ok(Some(v)), - ) - .context(ks_err!( - "Failed to get a key after \ - attempting to assign one.", - ))? - .map_or_else( - || { - Err(Error::sys()).context(ks_err!( - "Attempted to assign a \ - key and failed silently. Something is very wrong.", - )) - }, - |(guard, cert_chain)| Ok(Some((guard, cert_chain))), - ) - } - _ => Ok(None), - } -} - -/// Returns None if an AttestationKey fails to be assigned. Errors if no keys are available. -fn get_rem_prov_attest_key_helper( - domain: Domain, +fn get_rkpd_attestation_key( + security_level: &SecurityLevel, caller_uid: u32, - db: &mut KeystoreDB, - km_uuid: &Uuid, -) -> Result<Option<(KeyIdGuard, CertificateChain)>> { - let guard_and_chain = db - .retrieve_attestation_key_and_cert_chain(domain, caller_uid as i64, km_uuid) - .context(ks_err!("Failed to retrieve a key + cert chain"))?; - match guard_and_chain { - Some((guard, cert_chain)) => Ok(Some((guard, cert_chain))), - // Either this app needs to be assigned a key, or the pool is empty. An error will - // be thrown if there is no key available to assign. This will indicate that the app - // should be nudged to provision more keys so keystore can retry. - None => { - db.assign_attestation_key(domain, caller_uid as i64, km_uuid) - .context(ks_err!("Failed to assign a key"))?; - Ok(None) - } - } -} - -impl binder::Interface for RemoteProvisioningService {} - -// Implementation of IRemoteProvisioning. See AIDL spec at -// :aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl -impl IRemoteProvisioning for RemoteProvisioningService { - fn getPoolStatus( - &self, - expired_by: i64, - sec_level: SecurityLevel, - ) -> binder::Result<AttestationPoolStatus> { - let _wp = wd::watch_millis("IRemoteProvisioning::getPoolStatus", 500); - map_or_log_err(get_pool_status(expired_by, sec_level), Ok) - } - - fn generateCsr( - &self, - test_mode: bool, - num_csr: i32, - eek: &[u8], - challenge: &[u8], - sec_level: SecurityLevel, - protected_data: &mut ProtectedData, - device_info: &mut DeviceInfo, - ) -> binder::Result<Vec<u8>> { - let _wp = wd::watch_millis("IRemoteProvisioning::generateCsr", 500); - map_or_log_err( - self.generate_csr( - test_mode, - num_csr, - eek, - challenge, - sec_level, - protected_data, - device_info, - ), - Ok, - ) - } - - fn provisionCertChain( - &self, - public_key: &[u8], - batch_cert: &[u8], - certs: &[u8], - expiration_date: i64, - sec_level: SecurityLevel, - ) -> binder::Result<()> { - let _wp = wd::watch_millis("IRemoteProvisioning::provisionCertChain", 500); - DB.with::<_, binder::Result<()>>(|db| { - map_or_log_err( - self.provision_cert_chain( - &mut db.borrow_mut(), - public_key, - batch_cert, - certs, - expiration_date, - sec_level, - ), - Ok, - ) - }) - } - - fn generateKeyPair(&self, is_test_mode: bool, sec_level: SecurityLevel) -> binder::Result<()> { - let _wp = wd::watch_millis("IRemoteProvisioning::generateKeyPair", 500); - DB.with::<_, binder::Result<()>>(|db| { - map_or_log_err( - self.generate_key_pair(&mut db.borrow_mut(), is_test_mode, sec_level), - Ok, - ) - }) - } - - fn getImplementationInfo(&self) -> binder::Result<Vec<ImplInfo>> { - let _wp = wd::watch_millis("IRemoteProvisioning::getSecurityLevels", 500); - map_or_log_err(self.get_implementation_info(), Ok) - } - - fn deleteAllKeys(&self) -> binder::Result<i64> { - let _wp = wd::watch_millis("IRemoteProvisioning::deleteAllKeys", 500); - map_or_log_err(self.delete_all_keys(), Ok) - } -} - -/// Implementation of the IRemotelyProvisionedKeyPool service. -#[derive(Default)] -pub struct RemotelyProvisionedKeyPoolService { - unique_id_to_sec_level: HashMap<String, SecurityLevel>, -} - -impl RemotelyProvisionedKeyPoolService { - /// Fetches a remotely provisioned certificate chain and key for the given client uid that - /// was provisioned using the IRemotelyProvisionedComponent with the given id. The same key - /// will be returned for a given caller_uid on every request. If there are no attestation keys - /// available, `OUT_OF_KEYS_TRANSIENT_ERROR` is returned. - fn get_attestation_key( - &self, - db: &mut KeystoreDB, - caller_uid: i32, - irpc_id: &str, - ) -> Result<RemotelyProvisionedKey> { - log::info!("get_attestation_key(self, {}, {}", caller_uid, irpc_id); - - let sec_level = self - .unique_id_to_sec_level - .get(irpc_id) - .ok_or(Error::Rc(ResponseCode::INVALID_ARGUMENT)) - .context(format!("In get_attestation_key: unknown irpc id '{}'", irpc_id))?; - let (_, _, km_uuid) = get_keymint_device(sec_level)?; - - let guard_and_cert_chain = - get_rem_prov_attest_key(Domain::APP, caller_uid as u32, db, &km_uuid) - .context(ks_err!())?; - match guard_and_cert_chain { - Some((_, chain)) => Ok(RemotelyProvisionedKey { - keyBlob: chain.private_key.to_vec(), - encodedCertChain: chain.cert_chain, - }), - // It should be impossible to get `None`, but handle it just in case as a - // precaution against future behavioral changes in `get_rem_prov_attest_key`. - None => Err(error::Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR)) - .context(ks_err!("No available attestation keys")), - } - } - - /// Creates a new instance of the remotely provisioned key pool service, used for fetching - /// remotely provisioned attestation keys. - pub fn new_native_binder() -> Result<Strong<dyn IRemotelyProvisionedKeyPool>> { - let mut result: Self = Default::default(); - - let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT) - .context(ks_err!("Failed to get TEE Remote Provisioner instance."))?; - if let Some(id) = dev.getHardwareInfo()?.uniqueId { - result.unique_id_to_sec_level.insert(id, SecurityLevel::TRUSTED_ENVIRONMENT); - } - - if let Ok(dev) = get_remotely_provisioned_component(&SecurityLevel::STRONGBOX) { - if let Some(id) = dev.getHardwareInfo()?.uniqueId { - if result.unique_id_to_sec_level.contains_key(&id) { - anyhow::bail!("In new_native_binder: duplicate irpc id found: '{}'", id) - } - result.unique_id_to_sec_level.insert(id, SecurityLevel::STRONGBOX); - } - } - - // If none of the remotely provisioned components have unique ids, then we shouldn't - // bother publishing the service, as it's impossible to match keys with their backends. - if result.unique_id_to_sec_level.is_empty() { - anyhow::bail!( - "In new_native_binder: No remotely provisioned components have unique ids" - ) - } - - Ok(BnRemotelyProvisionedKeyPool::new_binder( - result, - BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, - )) - } -} - -impl binder::Interface for RemotelyProvisionedKeyPoolService {} - -// Implementation of IRemotelyProvisionedKeyPool. See AIDL spec at -// :aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl -impl IRemotelyProvisionedKeyPool for RemotelyProvisionedKeyPoolService { - fn getAttestationKey( - &self, - caller_uid: i32, - irpc_id: &str, - ) -> binder::Result<RemotelyProvisionedKey> { - let _wp = wd::watch_millis("IRemotelyProvisionedKeyPool::getAttestationKey", 500); - map_or_log_err(check_keystore_permission(KeystorePerm::GetAttestationKey), Ok)?; - DB.with::<_, binder::Result<RemotelyProvisionedKey>>(|db| { - map_or_log_err(self.get_attestation_key(&mut db.borrow_mut(), caller_uid, irpc_id), Ok) - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_cbor::Value; - use std::collections::BTreeMap; - use std::sync::{Arc, Mutex}; - use android_hardware_security_rkp::aidl::android::hardware::security::keymint::{ - RpcHardwareInfo::RpcHardwareInfo, - }; - - #[derive(Default)] - struct MockRemotelyProvisionedComponentValues { - hw_info: RpcHardwareInfo, - private_key: Vec<u8>, - maced_public_key: Vec<u8>, - } - - // binder::Interface requires the Send trait, so we have to use a Mutex even though the test - // is single threaded. - #[derive(Default)] - struct MockRemotelyProvisionedComponent(Arc<Mutex<MockRemotelyProvisionedComponentValues>>); - - impl binder::Interface for MockRemotelyProvisionedComponent {} - - impl IRemotelyProvisionedComponent for MockRemotelyProvisionedComponent { - fn getHardwareInfo(&self) -> binder::Result<RpcHardwareInfo> { - Ok(self.0.lock().unwrap().hw_info.clone()) - } - - fn generateEcdsaP256KeyPair( - &self, - test_mode: bool, - maced_public_key: &mut MacedPublicKey, - ) -> binder::Result<Vec<u8>> { - assert!(test_mode); - maced_public_key.macedKey = self.0.lock().unwrap().maced_public_key.clone(); - Ok(self.0.lock().unwrap().private_key.clone()) - } - - fn generateCertificateRequest( - &self, - _test_mode: bool, - _keys_to_sign: &[MacedPublicKey], - _eek: &[u8], - _challenge: &[u8], - _device_info: &mut DeviceInfo, - _protected_data: &mut ProtectedData, - ) -> binder::Result<Vec<u8>> { - Err(binder::StatusCode::INVALID_OPERATION.into()) - } - - fn generateCertificateRequestV2( - &self, - _keys_to_sign: &[MacedPublicKey], - _challenge: &[u8], - ) -> binder::Result<Vec<u8>> { - Err(binder::StatusCode::INVALID_OPERATION.into()) - } - } - - // Hard coded cert that can be parsed -- the content doesn't matter for testing, only that it's valid. - fn get_fake_cert() -> Vec<u8> { - vec![ - 0x30, 0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0x61, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, - 0x14, 0x3a, 0xd5, 0x67, 0xce, 0xfe, 0x93, 0xe1, 0xea, 0xb7, 0xe4, 0xbf, 0x64, 0x19, - 0xa4, 0x11, 0xe1, 0x87, 0x40, 0x20, 0x37, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, - 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x54, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, - 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x32, 0x31, 0x30, 0x32, 0x32, - 0x30, 0x38, 0x35, 0x32, 0x5a, 0x17, 0x0d, 0x34, 0x39, 0x30, 0x34, 0x32, 0x36, 0x32, - 0x32, 0x30, 0x38, 0x35, 0x32, 0x5a, 0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x54, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, - 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, - 0x00, 0x04, 0x1e, 0xac, 0x0c, 0xe0, 0x0d, 0xc5, 0x25, 0x84, 0x1b, 0xd2, 0x77, 0x2d, - 0xe7, 0xba, 0xf1, 0xde, 0xa7, 0xf6, 0x39, 0x7f, 0x38, 0x91, 0xbf, 0xa4, 0x58, 0xf5, - 0x62, 0x6b, 0xce, 0x06, 0xcf, 0xb9, 0x73, 0x91, 0x0d, 0x8a, 0x60, 0xa0, 0xc6, 0xa2, - 0x22, 0xe6, 0x51, 0x2e, 0x58, 0xd6, 0x43, 0x02, 0x80, 0x43, 0x44, 0x29, 0x38, 0x9a, - 0x99, 0xf3, 0xa4, 0xdd, 0xd0, 0xb4, 0x6f, 0x8b, 0x44, 0x2d, 0xa3, 0x53, 0x30, 0x51, - 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xdb, 0x13, 0x68, - 0xe0, 0x0e, 0x47, 0x10, 0xf8, 0xcb, 0x88, 0x83, 0xfe, 0x42, 0x3c, 0xd9, 0x3f, 0x1a, - 0x33, 0xe9, 0xaa, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xdb, 0x13, 0x68, 0xe0, 0x0e, 0x47, 0x10, 0xf8, 0xcb, 0x88, 0x83, 0xfe, - 0x42, 0x3c, 0xd9, 0x3f, 0x1a, 0x33, 0xe9, 0xaa, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, - 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, - 0x02, 0x20, 0x10, 0xdf, 0x40, 0xc3, 0x20, 0x54, 0x36, 0xb5, 0xc9, 0x3c, 0x70, 0xe3, - 0x55, 0x37, 0xd2, 0x04, 0x51, 0xeb, 0x0f, 0x18, 0x83, 0xd0, 0x58, 0xa1, 0x08, 0x77, - 0x8d, 0x4d, 0xa4, 0x20, 0xee, 0x33, 0x02, 0x21, 0x00, 0x8d, 0xe3, 0xa6, 0x6c, 0x0d, - 0x86, 0x25, 0xdc, 0x59, 0x0d, 0x21, 0x43, 0x22, 0x3a, 0xb9, 0xa1, 0x73, 0x28, 0xc9, - 0x16, 0x9e, 0x91, 0x15, 0xc4, 0xc3, 0xd7, 0xeb, 0xe5, 0xce, 0xdc, 0x1c, 0x1b, - ] - } - - // Generate a fake COSE_Mac0 with a key that's just `byte` repeated - fn generate_maced_pubkey(byte: u8) -> Vec<u8> { - vec![ - 0x84, 0x43, 0xA1, 0x01, 0x05, 0xA0, 0x58, 0x4D, 0xA5, 0x01, 0x02, 0x03, 0x26, 0x20, - 0x01, 0x21, 0x58, 0x20, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, 0x22, 0x58, 0x20, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, 0x58, 0x20, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, - byte, byte, byte, byte, byte, byte, byte, - ] - } - - #[test] - fn test_parse_cose_mac0_for_coords_raw_bytes() -> Result<()> { - let cose_mac0: Vec<u8> = vec![ - 0x84, 0x01, 0x02, 0x58, 0x4D, 0xA5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, - 0x20, 0x1A, 0xFB, 0xB2, 0xD9, 0x9D, 0xF6, 0x2D, 0xF0, 0xC3, 0xA8, 0xFC, 0x7E, 0xC9, - 0x21, 0x26, 0xED, 0xB5, 0x4A, 0x98, 0x9B, 0xF3, 0x0D, 0x91, 0x3F, 0xC6, 0x42, 0x5C, - 0x43, 0x22, 0xC8, 0xEE, 0x03, 0x22, 0x58, 0x20, 0x40, 0xB3, 0x9B, 0xFC, 0x47, 0x95, - 0x90, 0xA7, 0x5C, 0x5A, 0x16, 0x31, 0x34, 0xAF, 0x0C, 0x5B, 0xF2, 0xB2, 0xD8, 0x2A, - 0xA3, 0xB3, 0x1A, 0xB4, 0x4C, 0xA6, 0x3B, 0xE7, 0x22, 0xEC, 0x41, 0xDC, 0x03, - ]; - let raw_key = RemoteProvisioningService::parse_cose_mac0_for_coords(&cose_mac0)?; - assert_eq!( - raw_key, - vec![ - 0x1A, 0xFB, 0xB2, 0xD9, 0x9D, 0xF6, 0x2D, 0xF0, 0xC3, 0xA8, 0xFC, 0x7E, 0xC9, 0x21, - 0x26, 0xED, 0xB5, 0x4A, 0x98, 0x9B, 0xF3, 0x0D, 0x91, 0x3F, 0xC6, 0x42, 0x5C, 0x43, - 0x22, 0xC8, 0xEE, 0x03, 0x40, 0xB3, 0x9B, 0xFC, 0x47, 0x95, 0x90, 0xA7, 0x5C, 0x5A, - 0x16, 0x31, 0x34, 0xAF, 0x0C, 0x5B, 0xF2, 0xB2, 0xD8, 0x2A, 0xA3, 0xB3, 0x1A, 0xB4, - 0x4C, 0xA6, 0x3B, 0xE7, 0x22, 0xEC, 0x41, 0xDC, - ] - ); - Ok(()) - } - - #[test] - fn test_parse_cose_mac0_for_coords_constructed_mac() -> Result<()> { - let x_coord: Vec<u8> = vec![0; 32]; - let y_coord: Vec<u8> = vec![1; 32]; - let mut expected_key: Vec<u8> = Vec::new(); - expected_key.extend(&x_coord); - expected_key.extend(&y_coord); - let key_map: BTreeMap<Value, Value> = BTreeMap::from([ - (Value::Integer(1), Value::Integer(2)), - (Value::Integer(3), Value::Integer(-7)), - (Value::Integer(-1), Value::Integer(1)), - (Value::Integer(-2), Value::Bytes(x_coord)), - (Value::Integer(-3), Value::Bytes(y_coord)), - ]); - let cose_mac0: Vec<Value> = vec![ - Value::Integer(0), - Value::Integer(1), - Value::from(serde_cbor::to_vec(&key_map)?), - Value::Integer(2), - ]; - let raw_key = RemoteProvisioningService::parse_cose_mac0_for_coords(&serde_cbor::to_vec( - &Value::from(cose_mac0), - )?)?; - assert_eq!(expected_key, raw_key); - Ok(()) - } - - #[test] - fn test_extract_payload_from_cose_mac() -> Result<()> { - let key_map = Value::Map(BTreeMap::from([(Value::Integer(1), Value::Integer(2))])); - let payload = Value::Bytes(serde_cbor::to_vec(&key_map)?); - let cose_mac0 = - Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]); - let extracted_map = RemoteProvisioningService::extract_payload_from_cose_mac( - &serde_cbor::to_vec(&cose_mac0)?, - )?; - assert_eq!(key_map, extracted_map); - Ok(()) - } - - #[test] - fn test_extract_payload_from_cose_mac_fails_malformed_payload() -> Result<()> { - let payload = Value::Bytes(vec![5; 10]); - let cose_mac0 = - Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]); - let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac( - &serde_cbor::to_vec(&cose_mac0)?, - ); - assert!(extracted_payload.is_err()); - Ok(()) - } - - #[test] - fn test_extract_payload_from_cose_mac_fails_type() -> Result<()> { - let payload = Value::Integer(1); - let cose_mac0 = - Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]); - let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac( - &serde_cbor::to_vec(&cose_mac0)?, - ); - assert!(extracted_payload.is_err()); - Ok(()) - } - - #[test] - fn test_extract_payload_from_cose_mac_fails_length() -> Result<()> { - let cose_mac0 = Value::Array(vec![Value::Integer(0), Value::Integer(1)]); - let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac( - &serde_cbor::to_vec(&cose_mac0)?, - ); - assert!(extracted_payload.is_err()); - Ok(()) - } - - #[test] - #[ignore] // b/215746308 - fn test_get_attestation_key_no_keys_provisioned() { - let mut db = crate::database::tests::new_test_db().unwrap(); - let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default(); - mock_rpc.0.lock().unwrap().hw_info.uniqueId = Some(String::from("mallory")); - - let mut service: RemotelyProvisionedKeyPoolService = Default::default(); - service - .unique_id_to_sec_level - .insert(String::from("mallory"), SecurityLevel::TRUSTED_ENVIRONMENT); - - assert_eq!( - service - .get_attestation_key(&mut db, 0, "mallory") - .unwrap_err() - .downcast::<error::Error>() - .unwrap(), - error::Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) - ); - } - - #[test] - #[ignore] // b/215746308 - fn test_get_attestation_key() { - let mut db = crate::database::tests::new_test_db().unwrap(); - let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; - let irpc_id = "paul"; - let caller_uid = 0; - - let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default(); - let mock_values = mock_rpc.0.clone(); - let mut remote_provisioning: RemoteProvisioningService = Default::default(); - remote_provisioning.device_by_sec_level.insert(sec_level, Strong::new(mock_rpc)); - let mut key_pool: RemotelyProvisionedKeyPoolService = Default::default(); - key_pool.unique_id_to_sec_level.insert(String::from(irpc_id), sec_level); - - mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id)); - mock_values.lock().unwrap().private_key = vec![8, 6, 7, 5, 3, 0, 9]; - mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x11); - remote_provisioning.generate_key_pair(&mut db, true, sec_level).unwrap(); - - let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords( - mock_values.lock().unwrap().maced_public_key.as_slice(), - ) - .unwrap(); - let batch_cert = get_fake_cert(); - let certs = &[5, 6, 7, 8]; - assert!(remote_provisioning - .provision_cert_chain( - &mut db, - public_key.as_slice(), - batch_cert.as_slice(), - certs, - 0, - sec_level - ) - .is_ok()); - - // ensure we got the key we expected - let first_key = key_pool - .get_attestation_key(&mut db, caller_uid, irpc_id) - .context("get first key") - .unwrap(); - assert_eq!(first_key.keyBlob, mock_values.lock().unwrap().private_key); - assert_eq!(first_key.encodedCertChain, certs); - - // ensure that multiple calls get the same key - assert_eq!( - first_key, - key_pool - .get_attestation_key(&mut db, caller_uid, irpc_id) - .context("get second key") - .unwrap() - ); - - // no more keys for new clients - assert_eq!( - key_pool - .get_attestation_key(&mut db, caller_uid + 1, irpc_id) - .unwrap_err() - .downcast::<error::Error>() - .unwrap(), - error::Error::Rc(ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR) - ); - } - - #[test] - #[ignore] // b/215746308 - fn test_get_attestation_key_gets_different_key_for_different_client() { - let mut db = crate::database::tests::new_test_db().unwrap(); - let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT; - let irpc_id = "ringo"; - let first_caller = 0; - let second_caller = first_caller + 1; - - let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default(); - let mock_values = mock_rpc.0.clone(); - let mut remote_provisioning: RemoteProvisioningService = Default::default(); - remote_provisioning.device_by_sec_level.insert(sec_level, Strong::new(mock_rpc)); - let mut key_pool: RemotelyProvisionedKeyPoolService = Default::default(); - key_pool.unique_id_to_sec_level.insert(String::from(irpc_id), sec_level); - - // generate two distinct keys and provision them with certs - mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id)); - mock_values.lock().unwrap().private_key = vec![3, 1, 4, 1, 5]; - mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x11); - assert!(remote_provisioning.generate_key_pair(&mut db, true, sec_level).is_ok()); - let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords( - mock_values.lock().unwrap().maced_public_key.as_slice(), - ) - .unwrap(); - assert!(remote_provisioning - .provision_cert_chain( - &mut db, - public_key.as_slice(), - get_fake_cert().as_slice(), - &[1], - 0, - sec_level - ) - .is_ok()); - - mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id)); - mock_values.lock().unwrap().private_key = vec![9, 0, 2, 1, 0]; - mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x22); - assert!(remote_provisioning.generate_key_pair(&mut db, true, sec_level).is_ok()); - let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords( - mock_values.lock().unwrap().maced_public_key.as_slice(), - ) - .unwrap(); - assert!(remote_provisioning - .provision_cert_chain( - &mut db, - public_key.as_slice(), - get_fake_cert().as_slice(), - &[2], - 0, - sec_level - ) - .is_ok()); - - // make sure each caller gets a distinct key - assert_ne!( - key_pool - .get_attestation_key(&mut db, first_caller, irpc_id) - .context("get first key") - .unwrap(), - key_pool - .get_attestation_key(&mut db, second_caller, irpc_id) - .context("get second key") - .unwrap() - ); - - // repeated calls should return the same key for a given caller - assert_eq!( - key_pool - .get_attestation_key(&mut db, first_caller, irpc_id) - .context("first caller a") - .unwrap(), - key_pool - .get_attestation_key(&mut db, first_caller, irpc_id) - .context("first caller b") - .unwrap(), - ); - - assert_eq!( - key_pool - .get_attestation_key(&mut db, second_caller, irpc_id) - .context("second caller a") - .unwrap(), - key_pool - .get_attestation_key(&mut db, second_caller, irpc_id) - .context("second caller b") - .unwrap() - ); - } +) -> Result<RemotelyProvisionedKey> { + // Depending on the Android release, RKP may not have been mandatory for the + // TEE or StrongBox KM instances. In such cases, lookup failure for the IRPC + // HAL service is WAI and should not cause a failure. The error should be caught + // by the calling function and allow for natural fallback to the factory key. + let rpc_name = get_remotely_provisioned_component_name(security_level) + .context(ks_err!("Trying to get IRPC name."))?; + let _wd = wd::watch_millis("Calling get_rkpd_attestation_key()", 500); + rkpd_client::get_rkpd_attestation_key(&rpc_name, caller_uid) } diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs index cc1f816c..5f9745f0 100644 --- a/keystore2/src/security_level.rs +++ b/keystore2/src/security_level.rs @@ -19,19 +19,22 @@ use crate::audit_log::{ log_key_deleted, log_key_generated, log_key_imported, log_key_integrity_violation, }; use crate::database::{BlobInfo, CertificateInfo, KeyIdGuard}; -use crate::error::{self, map_km_error, map_or_log_err, Error, ErrorCode}; -use crate::globals::{DB, ENFORCEMENTS, LEGACY_IMPORTER, SUPER_KEY}; +use crate::error::{ + self, map_km_error, map_or_log_err, wrapped_rkpd_error_to_ks_error, Error, ErrorCode, +}; +use crate::globals::{ + get_remotely_provisioned_component_name, DB, ENFORCEMENTS, LEGACY_IMPORTER, SUPER_KEY, +}; use crate::key_parameter::KeyParameter as KsKeyParam; use crate::key_parameter::KeyParameterValue as KsKeyParamValue; use crate::ks_err; use crate::metrics_store::log_key_creation_event_stats; use crate::remote_provisioning::RemProvState; -use crate::rkpd_client::store_rkpd_attestation_key; use crate::super_key::{KeyBlob, SuperKeyManager}; use crate::utils::{ 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, + key_characteristics_to_internal, uid_to_android_user, watchdog as wd, UNDEFINED_NOT_AFTER, }; use crate::{ database::{ @@ -60,6 +63,7 @@ use android_system_keystore2::aidl::android::system::keystore2::{ KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode, }; use anyhow::{anyhow, Context, Result}; +use rkpd_client::store_rkpd_attestation_key; use std::convert::TryInto; use std::time::SystemTime; @@ -77,10 +81,6 @@ pub struct KeystoreSecurityLevel { // Blob of 32 zeroes used as empty masking key. static ZERO_BLOB_32: &[u8] = &[0; 32]; -// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to GeneralizedTime -// 999912312359559, which is 253402300799000 ms from Jan 1, 1970. -const UNDEFINED_NOT_AFTER: i64 = 253402300799000i64; - impl KeystoreSecurityLevel { /// Creates a new security level instance wrapped in a /// BnKeystoreSecurityLevel proxy object. It also enables @@ -247,7 +247,7 @@ impl KeystoreSecurityLevel { let super_key = SUPER_KEY .read() .unwrap() - .get_per_boot_key_by_user_id(uid_to_android_user(caller_uid)); + .get_after_first_unlock_key_by_user_id(uid_to_android_user(caller_uid)); let (key_id_guard, mut key_entry) = DB .with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| { LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { @@ -317,7 +317,6 @@ impl KeystoreSecurityLevel { let (begin_result, upgraded_blob) = self .upgrade_keyblob_if_required_with( - &*self.keymint, key_id_guard, &km_blob, blob_metadata.km_uuid().copied(), @@ -405,8 +404,7 @@ impl KeystoreSecurityLevel { ) -> Result<Vec<KeyParameter>> { let mut result = params.to_vec(); - // Unconditionally add the CREATION_DATETIME tag and prevent callers from - // specifying it. + // Prevent callers from specifying the CREATION_DATETIME tag. if params.iter().any(|kp| kp.tag == Tag::CREATION_DATETIME) { return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!( "KeystoreSecurityLevel::add_required_parameters: \ @@ -414,12 +412,16 @@ impl KeystoreSecurityLevel { )); } + // Use this variable to refer to notion of "now". This eliminates discrepancies from + // quering the clock multiple times. + let creation_datetime = SystemTime::now(); + // Add CREATION_DATETIME only if the backend version Keymint V1 (100) or newer. if self.hw_info.versionNumber >= 100 { result.push(KeyParameter { tag: Tag::CREATION_DATETIME, value: KeyParameterValue::DateTime( - SystemTime::now() + creation_datetime .duration_since(SystemTime::UNIX_EPOCH) .context(ks_err!( "KeystoreSecurityLevel::add_required_parameters: \ @@ -462,7 +464,7 @@ impl KeystoreSecurityLevel { } if self .id_rotation_state - .had_factory_reset_since_id_rotation() + .had_factory_reset_since_id_rotation(&creation_datetime) .context(ks_err!("Call to had_factory_reset_since_id_rotation failed."))? { result.push(KeyParameter { @@ -558,7 +560,6 @@ impl KeystoreSecurityLevel { issuer_subject, }) => self .upgrade_keyblob_if_required_with( - &*self.keymint, Some(key_id_guard), &KeyBlob::Ref(&blob), blob_metadata.km_uuid().copied(), @@ -583,40 +584,6 @@ impl KeystoreSecurityLevel { ) .context(ks_err!("Using user generated attestation key.")) .map(|(result, _)| result), - Some(AttestationKeyInfo::RemoteProvisioned { - key_id_guard, - attestation_key, - attestation_certs, - }) => self - .upgrade_keyblob_if_required_with( - &*self.keymint, - Some(key_id_guard), - &KeyBlob::Ref(&attestation_key.keyBlob), - Some(self.rem_prov_state.get_uuid()), - &[], - |blob| { - map_km_error({ - let _wp = self.watch_millis( - concat!( - "In KeystoreSecurityLevel::generate_key (RemoteProvisioned): ", - "calling generate_key.", - ), - 5000, // Generate can take a little longer. - ); - let dynamic_attest_key = Some(AttestationKey { - keyBlob: blob.to_vec(), - attestKeyParams: vec![], - issuerSubjectName: attestation_key.issuerSubjectName.clone(), - }); - self.keymint.generateKey(¶ms, dynamic_attest_key.as_ref()) - }) - }, - ) - .context(ks_err!("While generating Key with remote provisioned attestation key.")) - .map(|(mut result, _)| { - result.certificateChain.push(attestation_certs); - result - }), Some(AttestationKeyInfo::RkpdProvisioned { attestation_key, attestation_certs }) => { self.upgrade_rkpd_keyblob_if_required_with(&attestation_key.keyBlob, &[], |blob| { map_km_error({ @@ -766,7 +733,7 @@ impl KeystoreSecurityLevel { // Import_wrapped_key requires the rebind permission for the new key. check_key_permission(KeyPerm::Rebind, &key, &None).context(ks_err!())?; - let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id); + let super_key = SUPER_KEY.read().unwrap().get_after_first_unlock_key_by_user_id(user_id); let (wrapping_key_id_guard, mut wrapping_key_entry) = DB .with(|db| { @@ -817,7 +784,6 @@ impl KeystoreSecurityLevel { let (creation_result, _) = self .upgrade_keyblob_if_required_with( - &*self.keymint, Some(wrapping_key_id_guard), &wrapping_key_blob, wrapping_blob_metadata.km_uuid().copied(), @@ -873,7 +839,6 @@ impl KeystoreSecurityLevel { fn upgrade_keyblob_if_required_with<T, F>( &self, - km_dev: &dyn IKeyMintDevice, mut key_id_guard: Option<KeyIdGuard>, key_blob: &KeyBlob, km_uuid: Option<Uuid>, @@ -884,7 +849,8 @@ impl KeystoreSecurityLevel { F: Fn(&[u8]) -> Result<T, Error>, { let (v, upgraded_blob) = crate::utils::upgrade_keyblob_if_required_with( - km_dev, + &*self.keymint, + self.hw_info.versionNumber, key_blob, params, f, @@ -922,14 +888,21 @@ impl KeystoreSecurityLevel { where F: Fn(&[u8]) -> Result<T, Error>, { + let rpc_name = get_remotely_provisioned_component_name(&self.security_level) + .context(ks_err!("Trying to get IRPC name."))?; crate::utils::upgrade_keyblob_if_required_with( &*self.keymint, + self.hw_info.versionNumber, key_blob, params, f, |upgraded_blob| { - store_rkpd_attestation_key(&self.security_level, key_blob, upgraded_blob) - .context(ks_err!("Failed store_rkpd_attestation_key().")) + let _wp = wd::watch_millis("Calling store_rkpd_attestation_key()", 500); + if let Err(e) = store_rkpd_attestation_key(&rpc_name, key_blob, upgraded_blob) { + Err(wrapped_rkpd_error_to_ks_error(&e)).context(format!("{e:?}")) + } else { + Ok(()) + } }, ) .context(ks_err!()) @@ -954,7 +927,7 @@ impl KeystoreSecurityLevel { .context(ks_err!("Check permission"))?; let km_dev = &self.keymint; - match { + let res = { let _wp = self.watch_millis( concat!( "In IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ", @@ -963,7 +936,8 @@ impl KeystoreSecurityLevel { 500, ); map_km_error(km_dev.convertStorageKeyToEphemeral(key_blob)) - } { + }; + match res { Ok(result) => { Ok(EphemeralStorageKeyResponse { ephemeralKey: result, upgradedBlob: None }) } @@ -1090,3 +1064,83 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { map_or_log_err(result, Ok) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::error::map_km_error; + use crate::globals::get_keymint_device; + use crate::utils::upgrade_keyblob_if_required_with; + use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, AttestationKey::AttestationKey, KeyParameter::KeyParameter, + KeyParameterValue::KeyParameterValue, Tag::Tag, + }; + use keystore2_crypto::parse_subject_from_certificate; + use rkpd_client::get_rkpd_attestation_key; + + #[test] + // This is a helper for a manual test. We want to check that after a system upgrade RKPD + // attestation keys can also be upgraded and stored again with RKPD. The steps are: + // 1. Run this test and check in stdout that no key upgrade happened. + // 2. Perform a system upgrade. + // 3. Run this test and check in stdout that key upgrade did happen. + // + // Note that this test must be run with that same UID every time. Running as root, i.e. UID 0, + // should do the trick. Also, use "--nocapture" flag to get stdout. + fn test_rkpd_attestation_key_upgrade() { + binder::ProcessState::start_thread_pool(); + let security_level = SecurityLevel::TRUSTED_ENVIRONMENT; + let (keymint, info, _) = get_keymint_device(&security_level).unwrap(); + let key_id = 0; + let mut key_upgraded = false; + + let rpc_name = get_remotely_provisioned_component_name(&security_level).unwrap(); + let key = get_rkpd_attestation_key(&rpc_name, key_id).unwrap(); + assert!(!key.keyBlob.is_empty()); + assert!(!key.encodedCertChain.is_empty()); + + upgrade_keyblob_if_required_with( + &*keymint, + info.versionNumber, + &key.keyBlob, + /*upgrade_params=*/ &[], + /*km_op=*/ + |blob| { + let params = vec![ + KeyParameter { + tag: Tag::ALGORITHM, + value: KeyParameterValue::Algorithm(Algorithm::AES), + }, + KeyParameter { + tag: Tag::ATTESTATION_CHALLENGE, + value: KeyParameterValue::Blob(vec![0; 16]), + }, + KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) }, + ]; + let attestation_key = AttestationKey { + keyBlob: blob.to_vec(), + attestKeyParams: vec![], + issuerSubjectName: parse_subject_from_certificate(&key.encodedCertChain) + .unwrap(), + }; + + map_km_error(keymint.generateKey(¶ms, Some(&attestation_key))) + }, + /*new_blob_handler=*/ + |new_blob| { + // This handler is only executed if a key upgrade was performed. + key_upgraded = true; + let _wp = wd::watch_millis("Calling store_rkpd_attestation_key()", 500); + store_rkpd_attestation_key(&rpc_name, &key.keyBlob, new_blob).unwrap(); + Ok(()) + }, + ) + .unwrap(); + + if key_upgraded { + println!("RKPD key was upgraded and stored with RKPD."); + } else { + println!("RKPD key was NOT upgraded."); + } + } +} diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs index 10402286..1459254e 100644 --- a/keystore2/src/service.rs +++ b/keystore2/src/service.rs @@ -22,7 +22,7 @@ use crate::ks_err; use crate::permission::{KeyPerm, KeystorePerm}; use crate::security_level::KeystoreSecurityLevel; use crate::utils::{ - check_grant_permission, check_key_permission, check_keystore_permission, + check_grant_permission, check_key_permission, check_keystore_permission, count_key_entries, key_parameters_to_authorizations, list_key_entries, uid_to_android_user, watchdog as wd, }; use crate::{ @@ -126,8 +126,10 @@ impl KeystoreService { fn get_key_entry(&self, key: &KeyDescriptor) -> Result<KeyEntryResponse> { let caller_uid = ThreadState::get_calling_uid(); - let super_key = - SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid)); + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(uid_to_android_user(caller_uid)); let (key_id_guard, mut key_entry) = DB .with(|db| { @@ -181,8 +183,10 @@ impl KeystoreService { certificate_chain: Option<&[u8]>, ) -> Result<()> { let caller_uid = ThreadState::get_calling_uid(); - let super_key = - SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid)); + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(uid_to_android_user(caller_uid)); DB.with::<_, Result<()>>(|db| { let entry = match LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { @@ -251,7 +255,11 @@ impl KeystoreService { .context(ks_err!()) } - fn list_entries(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> { + fn get_key_descriptor_for_lookup( + &self, + domain: Domain, + namespace: i64, + ) -> Result<KeyDescriptor> { let mut k = match domain { Domain::APP => KeyDescriptor { domain, @@ -284,14 +292,37 @@ impl KeystoreService { return Err(e).context(ks_err!("While checking key permission."))?; } } + Ok(k) + } + + fn list_entries(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + + DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace, None)) + } - DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace)) + fn count_num_entries(&self, domain: Domain, namespace: i64) -> Result<i32> { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + + DB.with(|db| count_key_entries(&mut db.borrow_mut(), k.domain, k.nspace)) + } + + fn list_entries_batched( + &self, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, + ) -> Result<Vec<KeyDescriptor>> { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace, start_past_alias)) } fn delete_key(&self, key: &KeyDescriptor) -> Result<()> { let caller_uid = ThreadState::get_calling_uid(); - let super_key = - SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid)); + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(uid_to_android_user(caller_uid)); DB.with(|db| { LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { @@ -312,8 +343,10 @@ impl KeystoreService { access_vector: permission::KeyPermSet, ) -> Result<KeyDescriptor> { let caller_uid = ThreadState::get_calling_uid(); - let super_key = - SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid)); + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(uid_to_android_user(caller_uid)); DB.with(|db| { LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { @@ -389,4 +422,18 @@ impl IKeystoreService for KeystoreService { let _wp = wd::watch_millis("IKeystoreService::ungrant", 500); map_or_log_err(self.ungrant(key, grantee_uid), Ok) } + fn listEntriesBatched( + &self, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, + ) -> binder::Result<Vec<KeyDescriptor>> { + let _wp = wd::watch_millis("IKeystoreService::listEntriesBatched", 500); + map_or_log_err(self.list_entries_batched(domain, namespace, start_past_alias), Ok) + } + + fn getNumberOfEntries(&self, domain: Domain, namespace: i64) -> binder::Result<i32> { + let _wp = wd::watch_millis("IKeystoreService::getNumberOfEntries", 500); + map_or_log_err(self.count_num_entries(domain, namespace), Ok) + } } diff --git a/keystore2/src/shared_secret_negotiation.rs b/keystore2/src/shared_secret_negotiation.rs index 42d38d29..ff0ddf8a 100644 --- a/keystore2/src/shared_secret_negotiation.rs +++ b/keystore2/src/shared_secret_negotiation.rs @@ -19,19 +19,21 @@ use crate::globals::get_keymint_device; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel; use android_hardware_security_keymint::binder::Strong; use android_hardware_security_sharedsecret::aidl::android::hardware::security::sharedsecret::{ - ISharedSecret::ISharedSecret, SharedSecretParameters::SharedSecretParameters, + ISharedSecret::BpSharedSecret, ISharedSecret::ISharedSecret, + SharedSecretParameters::SharedSecretParameters, }; use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; use anyhow::Result; -use keystore2_vintf::{get_aidl_instances, get_hidl_instances}; +use binder::get_declared_instances; +use keystore2_hal_names::get_hidl_instances; use std::fmt::{self, Display, Formatter}; use std::time::Duration; /// This function initiates the shared secret negotiation. It starts a thread and then returns -/// immediately. The thread consults the vintf manifest to enumerate expected negotiation -/// participants. It then attempts to connect to all of these participants. If any connection -/// fails the thread will retry once per second to connect to the failed instance(s) until all of -/// the instances are connected. It then performs the negotiation. +/// immediately. The thread gets hal names from the android ServiceManager. It then attempts +/// to connect to all of these participants. If any connection fails the thread will retry once +/// per second to connect to the failed instance(s) until all of the instances are connected. +/// It then performs the negotiation. /// /// During the first phase of the negotiation it will again try every second until /// all instances have responded successfully to account for instances that register early but @@ -62,11 +64,9 @@ enum SharedSecretParticipant { impl Display for SharedSecretParticipant { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Aidl(instance) => write!( - f, - "{}.{}/{}", - SHARED_SECRET_PACKAGE_NAME, SHARED_SECRET_INTERFACE_NAME, instance - ), + Self::Aidl(instance) => { + write!(f, "{}/{}", <BpSharedSecret as ISharedSecret>::get_descriptor(), instance) + } Self::Hidl { is_strongbox, version: (ma, mi) } => write!( f, "{}@V{}.{}::{}/{}", @@ -109,8 +109,6 @@ fn filter_map_legacy_km_instances( static KEYMASTER_PACKAGE_NAME: &str = "android.hardware.keymaster"; static KEYMASTER_INTERFACE_NAME: &str = "IKeymasterDevice"; -static SHARED_SECRET_PACKAGE_NAME: &str = "android.hardware.security.sharedsecret"; -static SHARED_SECRET_INTERFACE_NAME: &str = "ISharedSecret"; static COMPAT_PACKAGE_NAME: &str = "android.security.compat"; /// Lists participants. @@ -121,11 +119,11 @@ fn list_participants() -> Result<Vec<SharedSecretParticipant>> { let mut legacy_strongbox_found: bool = false; Ok([(4, 1), (4, 0)] .iter() - .map(|(ma, mi)| { + .flat_map(|(ma, mi)| { get_hidl_instances(KEYMASTER_PACKAGE_NAME, *ma, *mi, KEYMASTER_INTERFACE_NAME) - .into_iter() + .iter() .filter_map(|name| { - filter_map_legacy_km_instances(name, (*ma, *mi)).and_then(|sp| { + filter_map_legacy_km_instances(name.to_string(), (*ma, *mi)).and_then(|sp| { if let SharedSecretParticipant::Hidl { is_strongbox: true, .. } = &sp { if !legacy_strongbox_found { legacy_strongbox_found = true; @@ -140,10 +138,9 @@ fn list_participants() -> Result<Vec<SharedSecretParticipant>> { }) .collect::<Vec<SharedSecretParticipant>>() }) - .into_iter() - .flatten() .chain({ - get_aidl_instances(SHARED_SECRET_PACKAGE_NAME, 1, SHARED_SECRET_INTERFACE_NAME) + get_declared_instances(<BpSharedSecret as ISharedSecret>::get_descriptor()) + .unwrap() .into_iter() .map(SharedSecretParticipant::Aidl) .collect::<Vec<_>>() @@ -164,8 +161,9 @@ fn connect_participants( match e { SharedSecretParticipant::Aidl(instance_name) => { let service_name = format!( - "{}.{}/{}", - SHARED_SECRET_PACKAGE_NAME, SHARED_SECRET_INTERFACE_NAME, instance_name + "{}/{}", + <BpSharedSecret as ISharedSecret>::get_descriptor(), + instance_name ); match map_binder_status_code(binder::get_interface(&service_name)) { Err(e) => { diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs index f0002131..11ab734f 100644 --- a/keystore2/src/super_key.rs +++ b/keystore2/src/super_key.rs @@ -26,7 +26,6 @@ use crate::{ error::ResponseCode, key_parameter::{KeyParameter, KeyParameterValue}, ks_err, - legacy_blob::LegacyBlobLoader, legacy_importer::LegacyImporter, raw_device::KeyMintDevice, utils::{watchdog as wd, AesGcm, AID_KEYSTORE}, @@ -79,25 +78,35 @@ pub struct SuperKeyType<'a> { pub alias: &'a str, /// Encryption algorithm pub algorithm: SuperEncryptionAlgorithm, + /// What to call this key in log messages. Not used for anything else. + pub name: &'a str, } -/// Key used for LskfLocked keys; the corresponding superencryption key is loaded in memory -/// when the user first unlocks, and remains in memory until the device reboots. -pub const USER_SUPER_KEY: SuperKeyType = - SuperKeyType { alias: "USER_SUPER_KEY", algorithm: SuperEncryptionAlgorithm::Aes256Gcm }; -/// Key used for ScreenLockBound keys; the corresponding superencryption key is loaded in memory -/// each time the user enters their LSKF, and cleared from memory each time the device is locked. -/// Symmetric. -pub const USER_SCREEN_LOCK_BOUND_KEY: SuperKeyType = SuperKeyType { +/// The user's AfterFirstUnlock super key. This super key is loaded into memory when the user first +/// unlocks the device, and it remains in memory until the device reboots. This is used to encrypt +/// keys that require user authentication but not an unlocked device. +pub const USER_AFTER_FIRST_UNLOCK_SUPER_KEY: SuperKeyType = SuperKeyType { + alias: "USER_SUPER_KEY", + algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + name: "AfterFirstUnlock super key", +}; + +/// The user's UnlockedDeviceRequired symmetric super key. This super key is loaded into memory each +/// time the user unlocks the device, and it is cleared from memory each time the user locks the +/// device. This is used to encrypt keys that use the UnlockedDeviceRequired key parameter. +pub const USER_UNLOCKED_DEVICE_REQUIRED_SYMMETRIC_SUPER_KEY: SuperKeyType = SuperKeyType { alias: "USER_SCREEN_LOCK_BOUND_KEY", algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + name: "UnlockedDeviceRequired symmetric super key", }; -/// Key used for ScreenLockBound keys; the corresponding superencryption key is loaded in memory -/// each time the user enters their LSKF, and cleared from memory each time the device is locked. -/// Asymmetric, so keys can be encrypted when the device is locked. -pub const USER_SCREEN_LOCK_BOUND_P521_KEY: SuperKeyType = SuperKeyType { + +/// The user's UnlockedDeviceRequired asymmetric super key. This is used to allow, while the device +/// is locked, the creation of keys that use the UnlockedDeviceRequired key parameter. The private +/// part of this key is loaded and cleared when the symmetric key is loaded and cleared. +pub const USER_UNLOCKED_DEVICE_REQUIRED_P521_SUPER_KEY: SuperKeyType = SuperKeyType { alias: "USER_SCREEN_LOCK_BOUND_P521_KEY", algorithm: SuperEncryptionAlgorithm::EcdhP521, + name: "UnlockedDeviceRequired asymmetric super key", }; /// Superencryption to apply to a new key. @@ -105,10 +114,10 @@ pub const USER_SCREEN_LOCK_BOUND_P521_KEY: SuperKeyType = SuperKeyType { pub enum SuperEncryptionType { /// Do not superencrypt this key. None, - /// Superencrypt with a key that remains in memory from first unlock to reboot. - LskfBound, - /// Superencrypt with a key cleared from memory when the device is locked. - ScreenLockBound, + /// Superencrypt with the AfterFirstUnlock super key. + AfterFirstUnlock, + /// Superencrypt with an UnlockedDeviceRequired super key. + UnlockedDeviceRequired, /// Superencrypt with a key based on the desired boot level BootLevel(i32), } @@ -225,33 +234,33 @@ impl LockedKey { } } -/// Keys for unlocking UNLOCKED_DEVICE_REQUIRED keys, as LockedKeys, complete with -/// a database descriptor for the encrypting key and the sids for the auth tokens -/// that can be used to decrypt it. +/// A user's UnlockedDeviceRequired super keys, encrypted with a biometric-bound key, and +/// information about that biometric-bound key. struct BiometricUnlock { - /// List of auth token SIDs that can be used to unlock these keys. + /// List of auth token SIDs that are accepted by the encrypting biometric-bound key. sids: Vec<i64>, - /// Database descriptor of key to use to unlock. + /// Key descriptor of the encrypting biometric-bound key. key_desc: KeyDescriptor, - /// Locked versions of the matching UserSuperKeys fields - screen_lock_bound: LockedKey, - screen_lock_bound_private: LockedKey, + /// The UnlockedDeviceRequired super keys, encrypted with a biometric-bound key. + symmetric: LockedKey, + private: LockedKey, } #[derive(Default)] struct UserSuperKeys { - /// The per boot key is used for LSKF binding of authentication bound keys. There is one - /// key per android user. The key is stored on flash encrypted with a key derived from a - /// secret, that is itself derived from the user's lock screen knowledge factor (LSKF). - /// When the user unlocks the device for the first time, this key is unlocked, i.e., decrypted, - /// and stays memory resident until the device reboots. - per_boot: Option<Arc<SuperKey>>, - /// The screen lock key works like the per boot key with the distinction that it is cleared - /// from memory when the screen lock is engaged. - screen_lock_bound: Option<Arc<SuperKey>>, - /// When the device is locked, screen-lock-bound keys can still be encrypted, using - /// ECDH public-key encryption. This field holds the decryption private key. - screen_lock_bound_private: Option<Arc<SuperKey>>, + /// The AfterFirstUnlock super key is used for synthetic password binding of authentication + /// bound keys. There is one key per android user. The key is stored on flash encrypted with a + /// key derived from a secret, that is itself derived from the user's synthetic password. (In + /// most cases, the user's synthetic password can, in turn, only be decrypted using the user's + /// Lock Screen Knowledge Factor or LSKF.) When the user unlocks the device for the first time, + /// this key is unlocked, i.e., decrypted, and stays memory resident until the device reboots. + after_first_unlock: Option<Arc<SuperKey>>, + /// The UnlockedDeviceRequired symmetric super key works like the AfterFirstUnlock super key + /// with the distinction that it is cleared from memory when the device is locked. + unlocked_device_required_symmetric: Option<Arc<SuperKey>>, + /// When the device is locked, keys that use the UnlockedDeviceRequired key parameter can still + /// be created, using ECDH public-key encryption. This field holds the decryption private key. + unlocked_device_required_private: Option<Arc<SuperKey>>, /// Versions of the above two keys, locked behind a biometric. biometric_unlock: Option<BiometricUnlock>, } @@ -336,7 +345,7 @@ impl SuperKeyManager { break; } } - w.wait().context(ks_err!("property wait failed"))?; + w.wait(None).context(ks_err!("property wait failed"))?; } Ok(()) } @@ -352,7 +361,7 @@ impl SuperKeyManager { self.data.user_keys.remove(&user); } - fn install_per_boot_key_for_user( + fn install_after_first_unlock_key_for_user( &mut self, user: UserId, super_key: Arc<SuperKey>, @@ -360,7 +369,7 @@ impl SuperKeyManager { self.data .add_key_to_key_index(&super_key) .context(ks_err!("add_key_to_key_index failed"))?; - self.data.user_keys.entry(user).or_default().per_boot = Some(super_key); + self.data.user_keys.entry(user).or_default().after_first_unlock = Some(super_key); Ok(()) } @@ -388,61 +397,21 @@ impl SuperKeyManager { }) } - pub fn get_per_boot_key_by_user_id( + /// Returns the AfterFirstUnlock superencryption key for the given user ID, or None if the user + /// has not yet unlocked the device since boot. + pub fn get_after_first_unlock_key_by_user_id( &self, user_id: UserId, ) -> Option<Arc<dyn AesGcm + Send + Sync>> { - self.get_per_boot_key_by_user_id_internal(user_id) + self.get_after_first_unlock_key_by_user_id_internal(user_id) .map(|sk| -> Arc<dyn AesGcm + Send + Sync> { sk }) } - fn get_per_boot_key_by_user_id_internal(&self, user_id: UserId) -> Option<Arc<SuperKey>> { - self.data.user_keys.get(&user_id).and_then(|e| e.per_boot.as_ref().cloned()) - } - - /// This function unlocks the super keys for a given user. - /// This means the key is loaded from the database, decrypted and placed in the - /// super key cache. If there is no such key a new key is created, encrypted with - /// a key derived from the given password and stored in the database. - pub fn unlock_user_key( - &mut self, - db: &mut KeystoreDB, - user: UserId, - pw: &Password, - legacy_blob_loader: &LegacyBlobLoader, - ) -> Result<()> { - let (_, entry) = db - .get_or_create_key_with( - Domain::APP, - user as u64 as i64, - USER_SUPER_KEY.alias, - crate::database::KEYSTORE_UUID, - || { - // For backward compatibility we need to check if there is a super key present. - let super_key = legacy_blob_loader - .load_super_key(user, pw) - .context(ks_err!("Failed to load legacy key blob."))?; - let super_key = match super_key { - None => { - // No legacy file was found. So we generate a new key. - generate_aes256_key() - .context(ks_err!("Failed to generate AES 256 key."))? - } - Some(key) => key, - }; - // Regardless of whether we loaded an old AES128 key or generated a new AES256 - // key as the super key, we derive a AES256 key from the password and re-encrypt - // the super key before we insert it in the database. The length of the key is - // preserved by the encryption so we don't need any extra flags to inform us - // which algorithm to use it with. - Self::encrypt_with_password(&super_key, pw).context("In create_new_key.") - }, - ) - .context(ks_err!("Failed to get key id."))?; - - self.populate_cache_from_super_key_blob(user, USER_SUPER_KEY.algorithm, entry, pw) - .context(ks_err!())?; - Ok(()) + fn get_after_first_unlock_key_by_user_id_internal( + &self, + user_id: UserId, + ) -> Option<Arc<SuperKey>> { + self.data.user_keys.get(&user_id).and_then(|e| e.after_first_unlock.as_ref().cloned()) } /// Check if a given key is super-encrypted, from its metadata. If so, unwrap the key using @@ -506,7 +475,7 @@ impl SuperKeyManager { } } - /// Checks if user has setup LSKF, even when super key cache is empty for the user. + /// Checks if the user's AfterFirstUnlock super key exists in the database (or legacy database). /// The reference to self is unused but it is required to prevent calling this function /// concurrently with skm state database changes. fn super_key_exists_in_db_for_user( @@ -516,7 +485,12 @@ impl SuperKeyManager { user_id: UserId, ) -> Result<bool> { let key_in_db = db - .key_exists(Domain::APP, user_id as u64 as i64, USER_SUPER_KEY.alias, KeyType::Super) + .key_exists( + Domain::APP, + user_id as u64 as i64, + USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias, + KeyType::Super, + ) .context(ks_err!())?; if key_in_db { @@ -526,83 +500,6 @@ impl SuperKeyManager { } } - /// Checks if user has already setup LSKF (i.e. a super key is persisted in the database or the - /// legacy database). If not, return Uninitialized state. - /// Otherwise, decrypt the super key from the password and return LskfUnlocked state. - pub fn check_and_unlock_super_key( - &mut self, - db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, - user_id: UserId, - pw: &Password, - ) -> Result<UserState> { - let alias = &USER_SUPER_KEY; - let result = legacy_importer - .with_try_import_super_key(user_id, pw, || db.load_super_key(alias, user_id)) - .context(ks_err!("Failed to load super key"))?; - - match result { - Some((_, entry)) => { - let super_key = self - .populate_cache_from_super_key_blob(user_id, alias.algorithm, entry, pw) - .context(ks_err!())?; - Ok(UserState::LskfUnlocked(super_key)) - } - None => Ok(UserState::Uninitialized), - } - } - - /// Checks if user has already setup LSKF (i.e. a super key is persisted in the database or the - /// legacy database). If so, return LskfLocked state. - /// If the password is provided, generate a new super key, encrypt with the password, - /// store in the database and populate the super key cache for the new user - /// and return LskfUnlocked state. - /// If the password is not provided, return Uninitialized state. - pub fn check_and_initialize_super_key( - &mut self, - db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, - user_id: UserId, - pw: Option<&Password>, - ) -> Result<UserState> { - let super_key_exists_in_db = self - .super_key_exists_in_db_for_user(db, legacy_importer, user_id) - .context(ks_err!("Failed to check if super key exists."))?; - if super_key_exists_in_db { - Ok(UserState::LskfLocked) - } else if let Some(pw) = pw { - // Generate a new super key. - let super_key = - generate_aes256_key().context(ks_err!("Failed to generate AES 256 key."))?; - // Derive an AES256 key from the password and re-encrypt the super key - // before we insert it in the database. - let (encrypted_super_key, blob_metadata) = - Self::encrypt_with_password(&super_key, pw).context(ks_err!())?; - - let key_entry = db - .store_super_key( - user_id, - &USER_SUPER_KEY, - &encrypted_super_key, - &blob_metadata, - &KeyMetaData::new(), - ) - .context(ks_err!("Failed to store super key."))?; - - let super_key = self - .populate_cache_from_super_key_blob( - user_id, - USER_SUPER_KEY.algorithm, - key_entry, - pw, - ) - .context(ks_err!())?; - Ok(UserState::LskfUnlocked(super_key)) - } else { - Ok(UserState::Uninitialized) - } - } - // Helper function to populate super key cache from the super key blob loaded from the database. fn populate_cache_from_super_key_blob( &mut self, @@ -613,7 +510,8 @@ impl SuperKeyManager { ) -> Result<Arc<SuperKey>> { let super_key = Self::extract_super_key_from_key_entry(algorithm, entry, pw, None) .context(ks_err!("Failed to extract super key from key entry"))?; - self.install_per_boot_key_for_user(user_id, super_key.clone())?; + self.install_after_first_unlock_key_for_user(user_id, super_key.clone()) + .context(ks_err!("Failed to install AfterFirstUnlock super key for user!"))?; Ok(super_key) } @@ -634,11 +532,17 @@ impl SuperKeyManager { (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => { // Note that password encryption is AES no matter the value of algorithm. let key = pw - .derive_key(salt, AES_256_KEY_LENGTH) - .context(ks_err!("Failed to generate key from password."))?; - - aes_gcm_decrypt(blob, iv, tag, &key) - .context(ks_err!("Failed to decrypt key blob."))? + .derive_key_hkdf(salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive key from password."))?; + + aes_gcm_decrypt(blob, iv, tag, &key).or_else(|_e| { + // Handle old key stored before the switch to HKDF. + let key = pw + .derive_key_pbkdf2(salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive key from password (PBKDF2)."))?; + aes_gcm_decrypt(blob, iv, tag, &key) + .context(ks_err!("Failed to decrypt key blob.")) + })? } (enc_by, salt, iv, tag) => { return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!( @@ -665,14 +569,20 @@ impl SuperKeyManager { } /// Encrypts the super key from a key derived from the password, before storing in the database. + /// This does not stretch the password; i.e., it assumes that the password is a high-entropy + /// synthetic password, not a low-entropy user provided password. pub fn encrypt_with_password( super_key: &[u8], pw: &Password, ) -> Result<(Vec<u8>, BlobMetaData)> { let salt = generate_salt().context("In encrypt_with_password: Failed to generate salt.")?; - let derived_key = pw - .derive_key(&salt, AES_256_KEY_LENGTH) - .context(ks_err!("Failed to derive password."))?; + let derived_key = if android_security_flags::fix_unlocked_device_required_keys_v2() { + pw.derive_key_hkdf(&salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive key from password."))? + } else { + pw.derive_key_pbkdf2(&salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive password."))? + }; let mut metadata = BlobMetaData::new(); metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); metadata.add(BlobMetaEntry::Salt(salt)); @@ -683,33 +593,6 @@ impl SuperKeyManager { Ok((encrypted_key, metadata)) } - // Encrypt the given key blob with the user's super key, if the super key exists and the device - // is unlocked. If the super key exists and the device is locked, or LSKF is not setup, - // return error. Note that it is out of the scope of this function to check if super encryption - // is required. Such check should be performed before calling this function. - fn super_encrypt_on_key_init( - &self, - db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, - user_id: UserId, - key_blob: &[u8], - ) -> Result<(Vec<u8>, BlobMetaData)> { - match self - .get_user_state(db, legacy_importer, user_id) - .context(ks_err!("Failed to get user state."))? - { - UserState::LskfUnlocked(super_key) => { - Self::encrypt_with_aes_super_key(key_blob, &super_key) - .context(ks_err!("Failed to encrypt the key.")) - } - UserState::LskfLocked => { - Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked.")) - } - UserState::Uninitialized => Err(Error::Rc(ResponseCode::UNINITIALIZED)) - .context(ks_err!("LSKF is not setup for the user.")), - } - } - // Helper function to encrypt a key with the given super key. Callers should select which super // key to be used. This is called when a key is super encrypted at its creation as well as at // its upgrade. @@ -729,6 +612,52 @@ impl SuperKeyManager { Ok((encrypted_key, metadata)) } + // Encrypts a given key_blob using a hybrid approach, which can either use the symmetric super + // key or the public super key depending on which is available. + // + // If the symmetric_key is available, the key_blob is encrypted using symmetric encryption with + // the provided symmetric super key. Otherwise, the function loads the public super key from + // the KeystoreDB and encrypts the key_blob using ECDH encryption and marks the keyblob to be + // re-encrypted with the symmetric super key on the first use. + // + // This hybrid scheme allows keys that use the UnlockedDeviceRequired key parameter to be + // created while the device is locked. + fn encrypt_with_hybrid_super_key( + key_blob: &[u8], + symmetric_key: Option<&SuperKey>, + public_key_type: &SuperKeyType, + db: &mut KeystoreDB, + user_id: UserId, + ) -> Result<(Vec<u8>, BlobMetaData)> { + if let Some(super_key) = symmetric_key { + Self::encrypt_with_aes_super_key(key_blob, super_key).context(ks_err!( + "Failed to encrypt with UnlockedDeviceRequired symmetric super key." + )) + } else { + // Symmetric key is not available, use public key encryption + let loaded = db + .load_super_key(public_key_type, user_id) + .context(ks_err!("load_super_key failed."))?; + let (key_id_guard, key_entry) = + loaded.ok_or_else(Error::sys).context(ks_err!("User ECDH super key missing."))?; + let public_key = key_entry + .metadata() + .sec1_public_key() + .ok_or_else(Error::sys) + .context(ks_err!("sec1_public_key missing."))?; + let mut metadata = BlobMetaData::new(); + let (ephem_key, salt, iv, encrypted_key, aead_tag) = + ECDHPrivateKey::encrypt_message(public_key, key_blob) + .context(ks_err!("ECDHPrivateKey::encrypt_message failed."))?; + metadata.add(BlobMetaEntry::PublicKey(ephem_key)); + metadata.add(BlobMetaEntry::Salt(salt)); + metadata.add(BlobMetaEntry::Iv(iv)); + metadata.add(BlobMetaEntry::AeadTag(aead_tag)); + SuperKeyIdentifier::DatabaseId(key_id_guard.id()).add_to_metadata(&mut metadata); + Ok((encrypted_key, metadata)) + } + } + /// Check if super encryption is required and if so, super-encrypt the key to be stored in /// the database. #[allow(clippy::too_many_arguments)] @@ -744,40 +673,42 @@ impl SuperKeyManager { ) -> Result<(Vec<u8>, BlobMetaData)> { match Enforcements::super_encryption_required(domain, key_parameters, flags) { SuperEncryptionType::None => Ok((key_blob.to_vec(), BlobMetaData::new())), - SuperEncryptionType::LskfBound => self - .super_encrypt_on_key_init(db, legacy_importer, user_id, key_blob) - .context(ks_err!("Failed to super encrypt with LskfBound key.")), - SuperEncryptionType::ScreenLockBound => { - let entry = - self.data.user_keys.get(&user_id).and_then(|e| e.screen_lock_bound.as_ref()); - if let Some(super_key) = entry { - Self::encrypt_with_aes_super_key(key_blob, super_key) - .context(ks_err!("Failed to encrypt with ScreenLockBound key.")) - } else { - // Symmetric key is not available, use public key encryption - let loaded = db - .load_super_key(&USER_SCREEN_LOCK_BOUND_P521_KEY, user_id) - .context(ks_err!("load_super_key failed."))?; - let (key_id_guard, key_entry) = - loaded.ok_or_else(Error::sys).context(ks_err!("User ECDH key missing."))?; - let public_key = key_entry - .metadata() - .sec1_public_key() - .ok_or_else(Error::sys) - .context(ks_err!("sec1_public_key missing."))?; - let mut metadata = BlobMetaData::new(); - let (ephem_key, salt, iv, encrypted_key, aead_tag) = - ECDHPrivateKey::encrypt_message(public_key, key_blob) - .context(ks_err!("ECDHPrivateKey::encrypt_message failed."))?; - metadata.add(BlobMetaEntry::PublicKey(ephem_key)); - metadata.add(BlobMetaEntry::Salt(salt)); - metadata.add(BlobMetaEntry::Iv(iv)); - metadata.add(BlobMetaEntry::AeadTag(aead_tag)); - SuperKeyIdentifier::DatabaseId(key_id_guard.id()) - .add_to_metadata(&mut metadata); - Ok((encrypted_key, metadata)) + SuperEncryptionType::AfterFirstUnlock => { + // Encrypt the given key blob with the user's AfterFirstUnlock super key. If the + // user has not unlocked the device since boot or the super keys were never + // initialized for the user for some reason, an error is returned. + match self + .get_user_state(db, legacy_importer, user_id) + .context(ks_err!("Failed to get user state for user {user_id}"))? + { + UserState::AfterFirstUnlock(super_key) => { + Self::encrypt_with_aes_super_key(key_blob, &super_key).context(ks_err!( + "Failed to encrypt with AfterFirstUnlock super key for user {user_id}" + )) + } + UserState::BeforeFirstUnlock => { + Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked.")) + } + UserState::Uninitialized => Err(Error::Rc(ResponseCode::UNINITIALIZED)) + .context(ks_err!("User {user_id} does not have super keys")), } } + SuperEncryptionType::UnlockedDeviceRequired => { + let symmetric_key = self + .data + .user_keys + .get(&user_id) + .and_then(|e| e.unlocked_device_required_symmetric.as_ref()) + .map(|arc| arc.as_ref()); + Self::encrypt_with_hybrid_super_key( + key_blob, + symmetric_key, + &USER_UNLOCKED_DEVICE_REQUIRED_P521_SUPER_KEY, + db, + user_id, + ) + .context(ks_err!("Failed to encrypt with UnlockedDeviceRequired hybrid scheme.")) + } SuperEncryptionType::BootLevel(level) => { let key_id = SuperKeyIdentifier::BootLevel(level); let super_key = self @@ -809,6 +740,47 @@ impl SuperKeyManager { } } + fn create_super_key( + &mut self, + db: &mut KeystoreDB, + user_id: UserId, + key_type: &SuperKeyType, + password: &Password, + reencrypt_with: Option<Arc<SuperKey>>, + ) -> Result<Arc<SuperKey>> { + log::info!("Creating {} for user {}", key_type.name, user_id); + let (super_key, public_key) = match key_type.algorithm { + SuperEncryptionAlgorithm::Aes256Gcm => { + (generate_aes256_key().context(ks_err!("Failed to generate AES-256 key."))?, None) + } + SuperEncryptionAlgorithm::EcdhP521 => { + let key = + ECDHPrivateKey::generate().context(ks_err!("Failed to generate ECDH key"))?; + ( + key.private_key().context(ks_err!("private_key failed"))?, + Some(key.public_key().context(ks_err!("public_key failed"))?), + ) + } + }; + // Derive an AES-256 key from the password and re-encrypt the super key before we insert it + // in the database. + let (encrypted_super_key, blob_metadata) = + Self::encrypt_with_password(&super_key, password).context(ks_err!())?; + let mut key_metadata = KeyMetaData::new(); + if let Some(pk) = public_key { + key_metadata.add(KeyMetaEntry::Sec1PublicKey(pk)); + } + let key_entry = db + .store_super_key(user_id, key_type, &encrypted_super_key, &blob_metadata, &key_metadata) + .context(ks_err!("Failed to store super key."))?; + Ok(Arc::new(SuperKey { + algorithm: key_type.algorithm, + key: super_key, + id: SuperKeyIdentifier::DatabaseId(key_entry.id()), + reencrypt_with, + })) + } + /// Fetch a superencryption key from the database, or create it if it doesn't already exist. /// When this is called, the caller must hold the lock on the SuperKeyManager. /// So it's OK that the check and creation are different DB transactions. @@ -829,83 +801,59 @@ impl SuperKeyManager { reencrypt_with, )?) } else { - let (super_key, public_key) = match key_type.algorithm { - SuperEncryptionAlgorithm::Aes256Gcm => ( - generate_aes256_key().context(ks_err!("Failed to generate AES 256 key."))?, - None, - ), - SuperEncryptionAlgorithm::EcdhP521 => { - let key = ECDHPrivateKey::generate() - .context(ks_err!("Failed to generate ECDH key"))?; - ( - key.private_key().context(ks_err!("private_key failed"))?, - Some(key.public_key().context(ks_err!("public_key failed"))?), - ) - } - }; - // Derive an AES256 key from the password and re-encrypt the super key - // before we insert it in the database. - let (encrypted_super_key, blob_metadata) = - Self::encrypt_with_password(&super_key, password).context(ks_err!())?; - let mut key_metadata = KeyMetaData::new(); - if let Some(pk) = public_key { - key_metadata.add(KeyMetaEntry::Sec1PublicKey(pk)); - } - let key_entry = db - .store_super_key( - user_id, - key_type, - &encrypted_super_key, - &blob_metadata, - &key_metadata, - ) - .context(ks_err!("Failed to store super key."))?; - Ok(Arc::new(SuperKey { - algorithm: key_type.algorithm, - key: super_key, - id: SuperKeyIdentifier::DatabaseId(key_entry.id()), - reencrypt_with, - })) + self.create_super_key(db, user_id, key_type, password, reencrypt_with) } } - /// Decrypt the screen-lock bound keys for this user using the password and store in memory. - pub fn unlock_screen_lock_bound_key( + /// Decrypt the UnlockedDeviceRequired super keys for this user using the password and store + /// them in memory. If these keys don't exist yet, create them. + pub fn unlock_unlocked_device_required_keys( &mut self, db: &mut KeystoreDB, user_id: UserId, password: &Password, ) -> Result<()> { - let (screen_lock_bound, screen_lock_bound_private) = self + let (symmetric, private) = self .data .user_keys .get(&user_id) - .map(|e| (e.screen_lock_bound.clone(), e.screen_lock_bound_private.clone())) + .map(|e| { + ( + e.unlocked_device_required_symmetric.clone(), + e.unlocked_device_required_private.clone(), + ) + }) .unwrap_or((None, None)); - if screen_lock_bound.is_some() && screen_lock_bound_private.is_some() { + if symmetric.is_some() && private.is_some() { // Already unlocked. return Ok(()); } - let aes = if let Some(screen_lock_bound) = screen_lock_bound { - // This is weird. If this point is reached only one of the screen locked keys was - // initialized. This should never happen. - screen_lock_bound + let aes = if let Some(symmetric) = symmetric { + // This is weird. If this point is reached only one of the UnlockedDeviceRequired super + // keys was initialized. This should never happen. + symmetric } else { - self.get_or_create_super_key(db, user_id, &USER_SCREEN_LOCK_BOUND_KEY, password, None) - .context(ks_err!("Trying to get or create symmetric key."))? + self.get_or_create_super_key( + db, + user_id, + &USER_UNLOCKED_DEVICE_REQUIRED_SYMMETRIC_SUPER_KEY, + password, + None, + ) + .context(ks_err!("Trying to get or create symmetric key."))? }; - let ecdh = if let Some(screen_lock_bound_private) = screen_lock_bound_private { - // This is weird. If this point is reached only one of the screen locked keys was - // initialized. This should never happen. - screen_lock_bound_private + let ecdh = if let Some(private) = private { + // This is weird. If this point is reached only one of the UnlockedDeviceRequired super + // keys was initialized. This should never happen. + private } else { self.get_or_create_super_key( db, user_id, - &USER_SCREEN_LOCK_BOUND_P521_KEY, + &USER_UNLOCKED_DEVICE_REQUIRED_P521_SUPER_KEY, password, Some(aes.clone()), ) @@ -915,86 +863,119 @@ impl SuperKeyManager { self.data.add_key_to_key_index(&aes)?; self.data.add_key_to_key_index(&ecdh)?; let entry = self.data.user_keys.entry(user_id).or_default(); - entry.screen_lock_bound = Some(aes); - entry.screen_lock_bound_private = Some(ecdh); + entry.unlocked_device_required_symmetric = Some(aes); + entry.unlocked_device_required_private = Some(ecdh); Ok(()) } - /// Wipe the screen-lock bound keys for this user from memory. - pub fn lock_screen_lock_bound_key( + /// Protects the user's UnlockedDeviceRequired super keys in a way such that they can only be + /// unlocked by the enabled unlock methods. + pub fn lock_unlocked_device_required_keys( &mut self, db: &mut KeystoreDB, user_id: UserId, unlocking_sids: &[i64], + weak_unlock_enabled: bool, ) { - log::info!("Locking screen bound for user {} sids {:?}", user_id, unlocking_sids); - let mut entry = self.data.user_keys.entry(user_id).or_default(); - if !unlocking_sids.is_empty() { - if let (Some(aes), Some(ecdh)) = ( - entry.screen_lock_bound.as_ref().cloned(), - entry.screen_lock_bound_private.as_ref().cloned(), - ) { - let res = (|| -> Result<()> { - let key_desc = KeyMintDevice::internal_descriptor(format!( - "biometric_unlock_key_{}", - user_id - )); - let encrypting_key = generate_aes256_key()?; - let km_dev: KeyMintDevice = - KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) - .context(ks_err!("KeyMintDevice::get failed"))?; - let mut key_params = vec![ - KeyParameterValue::Algorithm(Algorithm::AES), - KeyParameterValue::KeySize(256), - KeyParameterValue::BlockMode(BlockMode::GCM), - KeyParameterValue::PaddingMode(PaddingMode::NONE), - KeyParameterValue::CallerNonce, - KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT), - KeyParameterValue::MinMacLength(128), - KeyParameterValue::AuthTimeout(BIOMETRIC_AUTH_TIMEOUT_S), - KeyParameterValue::HardwareAuthenticatorType( - HardwareAuthenticatorType::FINGERPRINT, - ), - ]; - for sid in unlocking_sids { - key_params.push(KeyParameterValue::UserSecureID(*sid)); - } - let key_params: Vec<KmKeyParameter> = - key_params.into_iter().map(|x| x.into()).collect(); - km_dev.create_and_store_key( - db, - &key_desc, - KeyType::Client, /* TODO Should be Super b/189470584 */ - |dev| { - let _wp = wd::watch_millis( - "In lock_screen_lock_bound_key: calling importKey.", - 500, - ); - dev.importKey( - key_params.as_slice(), - KeyFormat::RAW, - &encrypting_key, - None, - ) - }, - )?; - entry.biometric_unlock = Some(BiometricUnlock { - sids: unlocking_sids.into(), - key_desc, - screen_lock_bound: LockedKey::new(&encrypting_key, &aes)?, - screen_lock_bound_private: LockedKey::new(&encrypting_key, &ecdh)?, - }); - Ok(()) - })(); - // There is no reason to propagate an error here upwards. We must discard - // entry.screen_lock_bound* in any case. - if let Err(e) = res { - log::error!("Error setting up biometric unlock: {:#?}", e); + let entry = self.data.user_keys.entry(user_id).or_default(); + if unlocking_sids.is_empty() { + if android_security_flags::fix_unlocked_device_required_keys_v2() { + entry.biometric_unlock = None; + } + } else if let (Some(aes), Some(ecdh)) = ( + entry.unlocked_device_required_symmetric.as_ref().cloned(), + entry.unlocked_device_required_private.as_ref().cloned(), + ) { + // If class 3 biometric unlock methods are enabled, create a biometric-encrypted copy of + // the keys. Do this even if weak unlock methods are enabled too; in that case we'll + // also retain a plaintext copy of the keys, but that copy will be wiped later if weak + // unlock methods expire. So we need the biometric-encrypted copy too just in case. + let res = (|| -> Result<()> { + let key_desc = + KeyMintDevice::internal_descriptor(format!("biometric_unlock_key_{}", user_id)); + let encrypting_key = generate_aes256_key()?; + let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) + .context(ks_err!("KeyMintDevice::get failed"))?; + let mut key_params = vec![ + KeyParameterValue::Algorithm(Algorithm::AES), + KeyParameterValue::KeySize(256), + KeyParameterValue::BlockMode(BlockMode::GCM), + KeyParameterValue::PaddingMode(PaddingMode::NONE), + KeyParameterValue::CallerNonce, + KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT), + KeyParameterValue::MinMacLength(128), + KeyParameterValue::AuthTimeout(BIOMETRIC_AUTH_TIMEOUT_S), + KeyParameterValue::HardwareAuthenticatorType( + HardwareAuthenticatorType::FINGERPRINT, + ), + ]; + for sid in unlocking_sids { + key_params.push(KeyParameterValue::UserSecureID(*sid)); } + let key_params: Vec<KmKeyParameter> = + key_params.into_iter().map(|x| x.into()).collect(); + km_dev.create_and_store_key( + db, + &key_desc, + KeyType::Client, /* TODO Should be Super b/189470584 */ + |dev| { + let _wp = wd::watch_millis( + "In lock_unlocked_device_required_keys: calling importKey.", + 500, + ); + dev.importKey(key_params.as_slice(), KeyFormat::RAW, &encrypting_key, None) + }, + )?; + entry.biometric_unlock = Some(BiometricUnlock { + sids: unlocking_sids.into(), + key_desc, + symmetric: LockedKey::new(&encrypting_key, &aes)?, + private: LockedKey::new(&encrypting_key, &ecdh)?, + }); + Ok(()) + })(); + if let Err(e) = res { + log::error!("Error setting up biometric unlock: {:#?}", e); + // The caller can't do anything about the error, and for security reasons we still + // wipe the keys (unless a weak unlock method is enabled). So just log the error. } } - entry.screen_lock_bound = None; - entry.screen_lock_bound_private = None; + // Wipe the plaintext copy of the keys, unless a weak unlock method is enabled. + if !weak_unlock_enabled { + entry.unlocked_device_required_symmetric = None; + entry.unlocked_device_required_private = None; + } + Self::log_status_of_unlocked_device_required_keys(user_id, entry); + } + + pub fn wipe_plaintext_unlocked_device_required_keys(&mut self, user_id: UserId) { + let entry = self.data.user_keys.entry(user_id).or_default(); + entry.unlocked_device_required_symmetric = None; + entry.unlocked_device_required_private = None; + Self::log_status_of_unlocked_device_required_keys(user_id, entry); + } + + pub fn wipe_all_unlocked_device_required_keys(&mut self, user_id: UserId) { + let entry = self.data.user_keys.entry(user_id).or_default(); + entry.unlocked_device_required_symmetric = None; + entry.unlocked_device_required_private = None; + entry.biometric_unlock = None; + Self::log_status_of_unlocked_device_required_keys(user_id, entry); + } + + fn log_status_of_unlocked_device_required_keys(user_id: UserId, entry: &UserSuperKeys) { + let status = match ( + // Note: the status of the symmetric and private keys should always be in sync. + // So we only check one here. + entry.unlocked_device_required_symmetric.is_some(), + entry.biometric_unlock.is_some(), + ) { + (false, false) => "fully protected", + (false, true) => "biometric-encrypted", + (true, false) => "retained in plaintext", + (true, true) => "retained in plaintext, with biometric-encrypted copy too", + }; + log::info!("UnlockedDeviceRequired super keys for user {user_id} are {status}."); } /// User has unlocked, not using a password. See if any of our stored auth tokens can be used @@ -1004,7 +985,17 @@ impl SuperKeyManager { db: &mut KeystoreDB, user_id: UserId, ) -> Result<()> { - let mut entry = self.data.user_keys.entry(user_id).or_default(); + let entry = self.data.user_keys.entry(user_id).or_default(); + if android_security_flags::fix_unlocked_device_required_keys_v2() + && entry.unlocked_device_required_symmetric.is_some() + && entry.unlocked_device_required_private.is_some() + { + // If the keys are already cached in plaintext, then there is no need to decrypt the + // biometric-encrypted copy. Both copies can be present here if the user has both + // class 3 biometric and weak unlock methods enabled, and the device was unlocked before + // the weak unlock methods expired. + return Ok(()); + } if let Some(biometric) = entry.biometric_unlock.as_ref() { let (key_id_guard, key_entry) = db .load_key_entry( @@ -1017,12 +1008,14 @@ impl SuperKeyManager { .context(ks_err!("load_key_entry failed"))?; let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) .context(ks_err!("KeyMintDevice::get failed"))?; + let mut errs = vec![]; for sid in &biometric.sids { - if let Some((auth_token_entry, _)) = db.find_auth_token_entry(|entry| { - entry.auth_token().userId == *sid || entry.auth_token().authenticatorId == *sid + let sid = *sid; + if let Some(auth_token_entry) = db.find_auth_token_entry(|entry| { + entry.auth_token().userId == sid || entry.auth_token().authenticatorId == sid }) { let res: Result<(Arc<SuperKey>, Arc<SuperKey>)> = (|| { - let slb = biometric.screen_lock_bound.decrypt( + let symmetric = biometric.symmetric.decrypt( db, &km_dev, &key_id_guard, @@ -1030,31 +1023,38 @@ impl SuperKeyManager { auth_token_entry.auth_token(), None, )?; - let slbp = biometric.screen_lock_bound_private.decrypt( + let private = biometric.private.decrypt( db, &km_dev, &key_id_guard, &key_entry, auth_token_entry.auth_token(), - Some(slb.clone()), + Some(symmetric.clone()), )?; - Ok((slb, slbp)) + Ok((symmetric, private)) })(); match res { - Ok((slb, slbp)) => { - entry.screen_lock_bound = Some(slb.clone()); - entry.screen_lock_bound_private = Some(slbp.clone()); - self.data.add_key_to_key_index(&slb)?; - self.data.add_key_to_key_index(&slbp)?; - log::info!("Successfully unlocked with biometric"); + Ok((symmetric, private)) => { + entry.unlocked_device_required_symmetric = Some(symmetric.clone()); + entry.unlocked_device_required_private = Some(private.clone()); + self.data.add_key_to_key_index(&symmetric)?; + self.data.add_key_to_key_index(&private)?; + log::info!("Successfully unlocked user {user_id} with biometric {sid}",); return Ok(()); } Err(e) => { - log::warn!("attempt failed: {:?}", e) + // Don't log an error yet, as some other biometric SID might work. + errs.push((sid, e)); } } } } + if !errs.is_empty() { + log::warn!("biometric unlock failed for all SIDs, with errors:"); + for (sid, err) in errs { + log::warn!(" biometric {sid}: {err}"); + } + } } Ok(()) } @@ -1068,8 +1068,8 @@ impl SuperKeyManager { legacy_importer: &LegacyImporter, user_id: UserId, ) -> Result<UserState> { - match self.get_per_boot_key_by_user_id_internal(user_id) { - Some(super_key) => Ok(UserState::LskfUnlocked(super_key)), + match self.get_after_first_unlock_key_by_user_id_internal(user_id) { + Some(super_key) => Ok(UserState::AfterFirstUnlock(super_key)), None => { // Check if a super key exists in the database or legacy database. // If so, return locked user state. @@ -1077,7 +1077,7 @@ impl SuperKeyManager { .super_key_exists_in_db_for_user(db, legacy_importer, user_id) .context(ks_err!())? { - Ok(UserState::LskfLocked) + Ok(UserState::BeforeFirstUnlock) } else { Ok(UserState::Uninitialized) } @@ -1085,108 +1085,200 @@ impl SuperKeyManager { } } - /// If the given user is unlocked: - /// * and `password` is None, the user is reset, all authentication bound keys are deleted and - /// `Ok(UserState::Uninitialized)` is returned. - /// * and `password` is Some, `Ok(UserState::LskfUnlocked)` is returned. - /// If the given user is locked: - /// * and the user was initialized before, `Ok(UserState::Locked)` is returned. - /// * and the user was not initialized before: - /// * and `password` is None, `Ok(Uninitialized)` is returned. - /// * and `password` is Some, super keys are generated and `Ok(UserState::LskfUnlocked)` is - /// returned. - pub fn reset_or_init_user_and_get_user_state( + /// Deletes all keys and super keys for the given user. + /// This is called when a user is deleted. + pub fn remove_user( &mut self, db: &mut KeystoreDB, legacy_importer: &LegacyImporter, user_id: UserId, - password: Option<&Password>, - ) -> Result<UserState> { - match self.get_per_boot_key_by_user_id_internal(user_id) { - Some(_) if password.is_none() => { - // Transitioning to swiping, delete only the super key in database and cache, - // and super-encrypted keys in database (and in KM). - self.reset_user(db, legacy_importer, user_id, true) - .context(ks_err!("Trying to delete keys from the db."))?; - // Lskf is now removed in Keystore. - Ok(UserState::Uninitialized) + ) -> Result<()> { + log::info!("remove_user(user={user_id})"); + // Mark keys created on behalf of the user as unreferenced. + legacy_importer + .bulk_delete_user(user_id, false) + .context(ks_err!("Trying to delete legacy keys."))?; + db.unbind_keys_for_user(user_id, false).context(ks_err!("Error in unbinding keys."))?; + + // Delete super key in cache, if exists. + self.forget_all_keys_for_user(user_id); + Ok(()) + } + + /// Deletes all authentication bound keys and super keys for the given user. The user must be + /// unlocked before this function is called. This function is used to transition a user to + /// swipe. + pub fn reset_user( + &mut self, + db: &mut KeystoreDB, + legacy_importer: &LegacyImporter, + user_id: UserId, + ) -> Result<()> { + log::info!("reset_user(user={user_id})"); + match self.get_user_state(db, legacy_importer, user_id)? { + UserState::Uninitialized => { + Err(Error::sys()).context(ks_err!("Tried to reset an uninitialized user!")) } - Some(super_key) => { - // Keystore won't be notified when changing to a new password when LSKF is - // already setup. Therefore, ideally this path wouldn't be reached. - Ok(UserState::LskfUnlocked(super_key)) + UserState::BeforeFirstUnlock => { + Err(Error::sys()).context(ks_err!("Tried to reset a locked user's password!")) } - None => { - // Check if a super key exists in the database or legacy database. - // If so, return LskfLocked state. - // Otherwise, i) if the password is provided, initialize the super key and return - // LskfUnlocked state ii) if password is not provided, return Uninitialized state. - self.check_and_initialize_super_key(db, legacy_importer, user_id, password) + UserState::AfterFirstUnlock(_) => { + // Mark keys created on behalf of the user as unreferenced. + legacy_importer + .bulk_delete_user(user_id, true) + .context(ks_err!("Trying to delete legacy keys."))?; + db.unbind_keys_for_user(user_id, true) + .context(ks_err!("Error in unbinding keys."))?; + + // Delete super key in cache, if exists. + self.forget_all_keys_for_user(user_id); + Ok(()) } } } - /// Unlocks the given user with the given password. If the key was already unlocked or unlocking - /// was successful, `Ok(UserState::LskfUnlocked)` is returned. - /// If the user was never initialized `Ok(UserState::Uninitialized)` is returned. - pub fn unlock_and_get_user_state( + /// If the user hasn't been initialized yet, then this function generates the user's + /// AfterFirstUnlock super key and sets the user's state to AfterFirstUnlock. Otherwise this + /// function returns an error. + pub fn init_user( &mut self, db: &mut KeystoreDB, legacy_importer: &LegacyImporter, user_id: UserId, password: &Password, - ) -> Result<UserState> { - match self.get_per_boot_key_by_user_id_internal(user_id) { - Some(super_key) => { - log::info!("Trying to unlock when already unlocked."); - Ok(UserState::LskfUnlocked(super_key)) + ) -> Result<()> { + log::info!("init_user(user={user_id})"); + match self.get_user_state(db, legacy_importer, user_id)? { + UserState::AfterFirstUnlock(_) | UserState::BeforeFirstUnlock => { + Err(Error::sys()).context(ks_err!("Tried to re-init an initialized user!")) } - None => { - // Check if a super key exists in the database or legacy database. - // If not, return Uninitialized state. - // Otherwise, try to unlock the super key and if successful, - // return LskfUnlocked. - self.check_and_unlock_super_key(db, legacy_importer, user_id, password) - .context(ks_err!("Failed to unlock super key.")) + UserState::Uninitialized => { + // Generate a new super key. + let super_key = + generate_aes256_key().context(ks_err!("Failed to generate AES 256 key."))?; + // Derive an AES256 key from the password and re-encrypt the super key + // before we insert it in the database. + let (encrypted_super_key, blob_metadata) = + Self::encrypt_with_password(&super_key, password) + .context(ks_err!("Failed to encrypt super key with password!"))?; + + let key_entry = db + .store_super_key( + user_id, + &USER_AFTER_FIRST_UNLOCK_SUPER_KEY, + &encrypted_super_key, + &blob_metadata, + &KeyMetaData::new(), + ) + .context(ks_err!("Failed to store super key."))?; + + self.populate_cache_from_super_key_blob( + user_id, + USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm, + key_entry, + password, + ) + .context(ks_err!("Failed to initialize user!"))?; + Ok(()) } } } - /// Delete all the keys created on behalf of the user. - /// If 'keep_non_super_encrypted_keys' is set to true, delete only the super key and super - /// encrypted keys. - pub fn reset_user( + /// Initializes the given user by creating their super keys, both AfterFirstUnlock and + /// UnlockedDeviceRequired. If allow_existing is true, then the user already being initialized + /// is not considered an error. + pub fn initialize_user( &mut self, db: &mut KeystoreDB, legacy_importer: &LegacyImporter, user_id: UserId, - keep_non_super_encrypted_keys: bool, + password: &Password, + allow_existing: bool, ) -> Result<()> { - // Mark keys created on behalf of the user as unreferenced. - legacy_importer - .bulk_delete_user(user_id, keep_non_super_encrypted_keys) - .context(ks_err!("Trying to delete legacy keys."))?; - db.unbind_keys_for_user(user_id, keep_non_super_encrypted_keys) - .context(ks_err!("Error in unbinding keys."))?; + // Create the AfterFirstUnlock super key. + if self.super_key_exists_in_db_for_user(db, legacy_importer, user_id)? { + log::info!("AfterFirstUnlock super key already exists"); + if !allow_existing { + return Err(Error::sys()).context(ks_err!("Tried to re-init an initialized user!")); + } + } else { + let super_key = self + .create_super_key(db, user_id, &USER_AFTER_FIRST_UNLOCK_SUPER_KEY, password, None) + .context(ks_err!("Failed to create AfterFirstUnlock super key"))?; - // Delete super key in cache, if exists. - self.forget_all_keys_for_user(user_id); - Ok(()) + self.install_after_first_unlock_key_for_user(user_id, super_key) + .context(ks_err!("Failed to install AfterFirstUnlock super key for user"))?; + } + + // Create the UnlockedDeviceRequired super keys. + self.unlock_unlocked_device_required_keys(db, user_id, password) + .context(ks_err!("Failed to create UnlockedDeviceRequired super keys")) + } + + /// Unlocks the given user with the given password. + /// + /// If the user state is BeforeFirstUnlock: + /// - Unlock the user's AfterFirstUnlock super key + /// - Unlock the user's UnlockedDeviceRequired super keys + /// + /// If the user state is AfterFirstUnlock: + /// - Unlock the user's UnlockedDeviceRequired super keys only + /// + pub fn unlock_user( + &mut self, + db: &mut KeystoreDB, + legacy_importer: &LegacyImporter, + user_id: UserId, + password: &Password, + ) -> Result<()> { + log::info!("unlock_user(user={user_id})"); + match self.get_user_state(db, legacy_importer, user_id)? { + UserState::AfterFirstUnlock(_) => { + self.unlock_unlocked_device_required_keys(db, user_id, password) + } + UserState::Uninitialized => { + Err(Error::sys()).context(ks_err!("Tried to unlock an uninitialized user!")) + } + UserState::BeforeFirstUnlock => { + let alias = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY; + let result = legacy_importer + .with_try_import_super_key(user_id, password, || { + db.load_super_key(alias, user_id) + }) + .context(ks_err!("Failed to load super key"))?; + + match result { + Some((_, entry)) => { + self.populate_cache_from_super_key_blob( + user_id, + alias.algorithm, + entry, + password, + ) + .context(ks_err!("Failed when unlocking user."))?; + self.unlock_unlocked_device_required_keys(db, user_id, password) + } + None => { + Err(Error::sys()).context(ks_err!("Locked user does not have a super key!")) + } + } + } + } } } /// This enum represents different states of the user's life cycle in the device. /// For now, only three states are defined. More states may be added later. pub enum UserState { - // The user has registered LSKF and has unlocked the device by entering PIN/Password, - // and hence the per-boot super key is available in the cache. - LskfUnlocked(Arc<SuperKey>), - // The user has registered LSKF, but has not unlocked the device using password, after reboot. - // Hence the per-boot super-key(s) is not available in the cache. - // However, the encrypted super key is available in the database. - LskfLocked, - // There's no user in the device for the given user id, or the user with the user id has not - // setup LSKF. + // The user's super keys exist, and the user has unlocked the device at least once since boot. + // Hence, the AfterFirstUnlock super key is available in the cache. + AfterFirstUnlock(Arc<SuperKey>), + // The user's super keys exist, but the user hasn't unlocked the device at least once since + // boot. Hence, the AfterFirstUnlock and UnlockedDeviceRequired super keys are not available in + // the cache. However, they exist in the database in encrypted form. + BeforeFirstUnlock, + // The user's super keys don't exist. I.e., there's no user with the given user ID, or the user + // is in the process of being created or destroyed. Uninitialized, } @@ -1233,3 +1325,390 @@ impl<'a> Deref for KeyBlob<'a> { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::database::tests::make_bootlevel_key_entry; + use crate::database::tests::make_test_key_entry; + use crate::database::tests::new_test_db; + use rand::prelude::*; + const USER_ID: u32 = 0; + const TEST_KEY_ALIAS: &str = "TEST_KEY"; + const TEST_BOOT_KEY_ALIAS: &str = "TEST_BOOT_KEY"; + + pub fn generate_password_blob() -> Password<'static> { + let mut rng = rand::thread_rng(); + let mut password = vec![0u8; 64]; + rng.fill_bytes(&mut password); + + let mut zvec = ZVec::new(64).expect("Failed to create ZVec"); + zvec[..].copy_from_slice(&password[..]); + + Password::Owned(zvec) + } + + fn setup_test(pw: &Password) -> (Arc<RwLock<SuperKeyManager>>, KeystoreDB, LegacyImporter) { + let mut keystore_db = new_test_db().unwrap(); + let mut legacy_importer = LegacyImporter::new(Arc::new(Default::default())); + legacy_importer.set_empty(); + let skm: Arc<RwLock<SuperKeyManager>> = Default::default(); + assert!(skm + .write() + .unwrap() + .init_user(&mut keystore_db, &legacy_importer, USER_ID, pw) + .is_ok()); + (skm, keystore_db, legacy_importer) + } + + fn assert_unlocked( + skm: &Arc<RwLock<SuperKeyManager>>, + keystore_db: &mut KeystoreDB, + legacy_importer: &LegacyImporter, + user_id: u32, + err_msg: &str, + ) { + let user_state = + skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); + match user_state { + UserState::AfterFirstUnlock(_) => {} + _ => panic!("{}", err_msg), + } + } + + fn assert_locked( + skm: &Arc<RwLock<SuperKeyManager>>, + keystore_db: &mut KeystoreDB, + legacy_importer: &LegacyImporter, + user_id: u32, + err_msg: &str, + ) { + let user_state = + skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); + match user_state { + UserState::BeforeFirstUnlock => {} + _ => panic!("{}", err_msg), + } + } + + fn assert_uninitialized( + skm: &Arc<RwLock<SuperKeyManager>>, + keystore_db: &mut KeystoreDB, + legacy_importer: &LegacyImporter, + user_id: u32, + err_msg: &str, + ) { + let user_state = + skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); + match user_state { + UserState::Uninitialized => {} + _ => panic!("{}", err_msg), + } + } + + #[test] + fn test_init_user() { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + } + + #[test] + fn test_unlock_user() { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + + assert!(skm + .write() + .unwrap() + .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw) + .is_ok()); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user did not unlock!", + ); + } + + #[test] + fn test_unlock_wrong_password() { + let pw: Password = generate_password_blob(); + let wrong_pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + + assert!(skm + .write() + .unwrap() + .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &wrong_pw) + .is_err()); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was unlocked with an incorrect password!", + ); + } + + #[test] + fn test_unlock_user_idempotent() { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + + for _ in 0..5 { + assert!(skm + .write() + .unwrap() + .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw) + .is_ok()); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user did not unlock!", + ); + } + } + + fn test_user_removal(locked: bool) { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + assert!(make_test_key_entry( + &mut keystore_db, + Domain::APP, + USER_ID.into(), + TEST_KEY_ALIAS, + None + ) + .is_ok()); + assert!(make_bootlevel_key_entry( + &mut keystore_db, + Domain::APP, + USER_ID.into(), + TEST_BOOT_KEY_ALIAS, + false + ) + .is_ok()); + + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) + .unwrap()); + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) + .unwrap()); + + if locked { + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + } + + assert!(skm + .write() + .unwrap() + .remove_user(&mut keystore_db, &legacy_importer, USER_ID) + .is_ok()); + assert_uninitialized( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not removed!", + ); + + assert!(!skm + .write() + .unwrap() + .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID) + .unwrap()); + + assert!(!keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) + .unwrap()); + assert!(!keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) + .unwrap()); + } + + fn test_user_reset(locked: bool) { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + assert!(make_test_key_entry( + &mut keystore_db, + Domain::APP, + USER_ID.into(), + TEST_KEY_ALIAS, + None + ) + .is_ok()); + assert!(make_bootlevel_key_entry( + &mut keystore_db, + Domain::APP, + USER_ID.into(), + TEST_BOOT_KEY_ALIAS, + false + ) + .is_ok()); + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) + .unwrap()); + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) + .unwrap()); + + if locked { + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + assert!(skm + .write() + .unwrap() + .reset_user(&mut keystore_db, &legacy_importer, USER_ID) + .is_err()); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "User state should not have changed!", + ); + + // Keys should still exist. + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) + .unwrap()); + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) + .unwrap()); + } else { + assert!(skm + .write() + .unwrap() + .reset_user(&mut keystore_db, &legacy_importer, USER_ID) + .is_ok()); + assert_uninitialized( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not reset!", + ); + assert!(!skm + .write() + .unwrap() + .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID) + .unwrap()); + + // Auth bound key should no longer exist. + assert!(!keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) + .unwrap()); + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) + .unwrap()); + } + } + + #[test] + fn test_remove_unlocked_user() { + test_user_removal(false); + } + + #[test] + fn test_remove_locked_user() { + test_user_removal(true); + } + + #[test] + fn test_reset_unlocked_user() { + test_user_reset(false); + } + + #[test] + fn test_reset_locked_user() { + test_user_reset(true); + } +} diff --git a/keystore2/src/sw_keyblob.rs b/keystore2/src/sw_keyblob.rs new file mode 100644 index 00000000..47ab49fd --- /dev/null +++ b/keystore2/src/sw_keyblob.rs @@ -0,0 +1,1036 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Code for parsing software-backed keyblobs, as emitted by the C++ reference implementation of +//! KeyMint. + +use crate::error::Error; +use crate::ks_err; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType, + KeyFormat::KeyFormat, KeyOrigin::KeyOrigin, KeyParameter::KeyParameter, + KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, + Tag::Tag, TagType::TagType, +}; +use anyhow::Result; +use keystore2_crypto::hmac_sha256; +use std::mem::size_of; + +/// Root of trust value. +const SOFTWARE_ROOT_OF_TRUST: &[u8] = b"SW"; + +/// Error macro. +macro_rules! bloberr { + { $($arg:tt)+ } => { + anyhow::Error::new(Error::Km(ErrorCode::INVALID_KEY_BLOB)).context(ks_err!($($arg)+)) + }; +} + +/// Get the `KeyParameterValue` associated with a tag from a collection of `KeyParameter`s. +fn get_tag_value(params: &[KeyParameter], tag: Tag) -> Option<&KeyParameterValue> { + params.iter().find_map(|kp| if kp.tag == tag { Some(&kp.value) } else { None }) +} + +/// Get the [`TagType`] for a [`Tag`]. +fn tag_type(tag: &Tag) -> TagType { + TagType((tag.0 as u32 & 0xf0000000) as i32) +} + +/// Extract key material and combined key characteristics from a legacy authenticated keyblob. +pub fn export_key( + data: &[u8], + params: &[KeyParameter], +) -> Result<(KeyFormat, Vec<u8>, Vec<KeyParameter>)> { + let hidden = hidden_params(params, &[SOFTWARE_ROOT_OF_TRUST]); + let KeyBlob { key_material, hw_enforced, sw_enforced } = + KeyBlob::new_from_serialized(data, &hidden)?; + + let mut combined = hw_enforced; + combined.extend_from_slice(&sw_enforced); + + let algo_val = + get_tag_value(&combined, Tag::ALGORITHM).ok_or_else(|| bloberr!("No algorithm found!"))?; + + let format = match algo_val { + KeyParameterValue::Algorithm(Algorithm::AES) + | KeyParameterValue::Algorithm(Algorithm::TRIPLE_DES) + | KeyParameterValue::Algorithm(Algorithm::HMAC) => KeyFormat::RAW, + KeyParameterValue::Algorithm(Algorithm::RSA) + | KeyParameterValue::Algorithm(Algorithm::EC) => KeyFormat::PKCS8, + _ => return Err(bloberr!("Unexpected algorithm {:?}", algo_val)), + }; + + let key_material = match (format, algo_val) { + (KeyFormat::PKCS8, KeyParameterValue::Algorithm(Algorithm::EC)) => { + // Key material format depends on the curve. + let curve = get_tag_value(&combined, Tag::EC_CURVE) + .ok_or_else(|| bloberr!("Failed to determine curve for EC key!"))?; + match curve { + KeyParameterValue::EcCurve(EcCurve::CURVE_25519) => key_material, + KeyParameterValue::EcCurve(EcCurve::P_224) => { + pkcs8_wrap_nist_key(&key_material, EcCurve::P_224)? + } + KeyParameterValue::EcCurve(EcCurve::P_256) => { + pkcs8_wrap_nist_key(&key_material, EcCurve::P_256)? + } + KeyParameterValue::EcCurve(EcCurve::P_384) => { + pkcs8_wrap_nist_key(&key_material, EcCurve::P_384)? + } + KeyParameterValue::EcCurve(EcCurve::P_521) => { + pkcs8_wrap_nist_key(&key_material, EcCurve::P_521)? + } + _ => { + return Err(bloberr!("Unexpected EC curve {curve:?}")); + } + } + } + (KeyFormat::RAW, _) => key_material, + (format, algo) => { + return Err(bloberr!( + "Unsupported combination of {format:?} format for {algo:?} algorithm" + )); + } + }; + Ok((format, key_material, combined)) +} + +/// DER-encoded `AlgorithmIdentifier` for a P-224 key. +const DER_ALGORITHM_ID_P224: &[u8] = &[ + 0x30, 0x10, // SEQUENCE (AlgorithmIdentifier) { + 0x06, 0x07, // OBJECT IDENTIFIER (algorithm) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 1.2.840.10045.2.1 (ecPublicKey) + 0x06, 0x05, // OBJECT IDENTIFIER (param) + 0x2b, 0x81, 0x04, 0x00, 0x21, // 1.3.132.0.33 (secp224r1) } +]; + +/// DER-encoded `AlgorithmIdentifier` for a P-256 key. +const DER_ALGORITHM_ID_P256: &[u8] = &[ + 0x30, 0x13, // SEQUENCE (AlgorithmIdentifier) { + 0x06, 0x07, // OBJECT IDENTIFIER (algorithm) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 1.2.840.10045.2.1 (ecPublicKey) + 0x06, 0x08, // OBJECT IDENTIFIER (param) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, // 1.2.840.10045.3.1.7 (secp256r1) } +]; + +/// DER-encoded `AlgorithmIdentifier` for a P-384 key. +const DER_ALGORITHM_ID_P384: &[u8] = &[ + 0x30, 0x10, // SEQUENCE (AlgorithmIdentifier) { + 0x06, 0x07, // OBJECT IDENTIFIER (algorithm) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 1.2.840.10045.2.1 (ecPublicKey) + 0x06, 0x05, // OBJECT IDENTIFIER (param) + 0x2b, 0x81, 0x04, 0x00, 0x22, // 1.3.132.0.34 (secp384r1) } +]; + +/// DER-encoded `AlgorithmIdentifier` for a P-384 key. +const DER_ALGORITHM_ID_P521: &[u8] = &[ + 0x30, 0x10, // SEQUENCE (AlgorithmIdentifier) { + 0x06, 0x07, // OBJECT IDENTIFIER (algorithm) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 1.2.840.10045.2.1 (ecPublicKey) + 0x06, 0x05, // OBJECT IDENTIFIER (param) + 0x2b, 0x81, 0x04, 0x00, 0x23, // 1.3.132.0.35 (secp521r1) } +]; + +/// DER-encoded integer value zero. +const DER_VERSION_0: &[u8] = &[ + 0x02, // INTEGER + 0x01, // len + 0x00, // value 0 +]; + +/// Given a NIST curve EC key in the form of a DER-encoded `ECPrivateKey` +/// (RFC 5915 s3), wrap it in a DER-encoded PKCS#8 format (RFC 5208 s5). +fn pkcs8_wrap_nist_key(nist_key: &[u8], curve: EcCurve) -> Result<Vec<u8>> { + let der_alg_id = match curve { + EcCurve::P_224 => DER_ALGORITHM_ID_P224, + EcCurve::P_256 => DER_ALGORITHM_ID_P256, + EcCurve::P_384 => DER_ALGORITHM_ID_P384, + EcCurve::P_521 => DER_ALGORITHM_ID_P521, + _ => return Err(bloberr!("unknown curve {curve:?}")), + }; + + // Output format is: + // + // PrivateKeyInfo ::= SEQUENCE { + // version INTEGER, + // privateKeyAlgorithm AlgorithmIdentifier, + // privateKey OCTET STRING, + // } + // + // Start by building the OCTET STRING so we know its length. + let mut nist_key_octet_string = Vec::new(); + nist_key_octet_string.push(0x04); // OCTET STRING + add_der_len(&mut nist_key_octet_string, nist_key.len())?; + nist_key_octet_string.extend_from_slice(nist_key); + + let mut buf = Vec::new(); + buf.push(0x30); // SEQUENCE + add_der_len(&mut buf, DER_VERSION_0.len() + der_alg_id.len() + nist_key_octet_string.len())?; + buf.extend_from_slice(DER_VERSION_0); + buf.extend_from_slice(der_alg_id); + buf.extend_from_slice(&nist_key_octet_string); + Ok(buf) +} + +/// Append a DER-encoded length value to the given buffer. +fn add_der_len(buf: &mut Vec<u8>, len: usize) -> Result<()> { + if len <= 0x7f { + buf.push(len as u8) + } else if len <= 0xff { + buf.push(0x81); // One length octet to come + buf.push(len as u8); + } else if len <= 0xffff { + buf.push(0x82); // Two length octets to come + buf.push((len >> 8) as u8); + buf.push((len & 0xff) as u8); + } else { + return Err(bloberr!("Unsupported DER length {len}")); + } + Ok(()) +} + +/// Plaintext key blob, with key characteristics. +#[derive(PartialEq, Eq)] +struct KeyBlob { + /// Raw key material. + key_material: Vec<u8>, + /// Hardware-enforced key characteristics. + hw_enforced: Vec<KeyParameter>, + /// Software-enforced key characteristics. + sw_enforced: Vec<KeyParameter>, +} + +impl KeyBlob { + /// Key blob version. + const KEY_BLOB_VERSION: u8 = 0; + + /// Hard-coded HMAC key used for keyblob authentication. + const LEGACY_HMAC_KEY: &'static [u8] = b"IntegrityAssuredBlob0\0"; + + /// Size (in bytes) of appended MAC. + const MAC_LEN: usize = 8; + + /// Parse a serialized [`KeyBlob`]. + fn new_from_serialized(mut data: &[u8], hidden: &[KeyParameter]) -> Result<Self> { + // Keyblob needs to be at least long enough for: + // - version byte, + // - 4-byte len for key material + // - 4-byte len for hw_enforced params + // - 4-byte len for sw_enforced params + // - MAC tag. + if data.len() < (1 + 3 * size_of::<u32>() + Self::MAC_LEN) { + return Err(bloberr!("blob not long enough (len = {})", data.len())); + } + + // Check the HMAC in the last 8 bytes before doing anything else. + let mac = &data[data.len() - Self::MAC_LEN..]; + let computed_mac = Self::compute_hmac(&data[..data.len() - Self::MAC_LEN], hidden)?; + if mac != computed_mac { + return Err(bloberr!("invalid key blob")); + } + + let version = consume_u8(&mut data)?; + if version != Self::KEY_BLOB_VERSION { + return Err(bloberr!("unexpected blob version {}", version)); + } + let key_material = consume_vec(&mut data)?; + let hw_enforced = deserialize_params(&mut data)?; + let sw_enforced = deserialize_params(&mut data)?; + + // Should just be the (already-checked) MAC left. + let rest = &data[Self::MAC_LEN..]; + if !rest.is_empty() { + return Err(bloberr!("extra data (len {})", rest.len())); + } + Ok(KeyBlob { key_material, hw_enforced, sw_enforced }) + } + + /// Compute the authentication HMAC for a KeyBlob. This is built as: + /// HMAC-SHA256(HK, data || serialize(hidden)) + /// with HK = b"IntegrityAssuredBlob0\0". + fn compute_hmac(data: &[u8], hidden: &[KeyParameter]) -> Result<Vec<u8>> { + let hidden_data = serialize_params(hidden)?; + let mut combined = data.to_vec(); + combined.extend_from_slice(&hidden_data); + let mut tag = hmac_sha256(Self::LEGACY_HMAC_KEY, &combined)?; + tag.truncate(Self::MAC_LEN); + Ok(tag) + } +} + +/// Build the parameters that are used as the hidden input to HMAC calculations: +/// - `ApplicationId(data)` if present +/// - `ApplicationData(data)` if present +/// - (repeated) `RootOfTrust(rot)` where `rot` is a hardcoded piece of root of trust information. +fn hidden_params(params: &[KeyParameter], rots: &[&[u8]]) -> Vec<KeyParameter> { + let mut results = Vec::new(); + if let Some(app_id) = get_tag_value(params, Tag::APPLICATION_ID) { + results.push(KeyParameter { tag: Tag::APPLICATION_ID, value: app_id.clone() }); + } + if let Some(app_data) = get_tag_value(params, Tag::APPLICATION_DATA) { + results.push(KeyParameter { tag: Tag::APPLICATION_DATA, value: app_data.clone() }); + } + for rot in rots { + results.push(KeyParameter { + tag: Tag::ROOT_OF_TRUST, + value: KeyParameterValue::Blob(rot.to_vec()), + }); + } + results +} + +/// Retrieve a `u8` from the start of the given slice, if possible. +fn consume_u8(data: &mut &[u8]) -> Result<u8> { + match data.first() { + Some(b) => { + *data = &(*data)[1..]; + Ok(*b) + } + None => Err(bloberr!("failed to find 1 byte")), + } +} + +/// Move past a bool value from the start of the given slice, if possible. +/// Bool values should only be included if `true`, so fail if the value +/// is anything other than 1. +fn consume_bool(data: &mut &[u8]) -> Result<bool> { + let b = consume_u8(data)?; + if b == 0x01 { + Ok(true) + } else { + Err(bloberr!("bool value other than 1 encountered")) + } +} + +/// Retrieve a (host-ordered) `u32` from the start of the given slice, if possible. +fn consume_u32(data: &mut &[u8]) -> Result<u32> { + const LEN: usize = size_of::<u32>(); + if data.len() < LEN { + return Err(bloberr!("failed to find {LEN} bytes")); + } + let chunk: [u8; LEN] = data[..LEN].try_into().unwrap(); // safe: just checked + *data = &(*data)[LEN..]; + Ok(u32::from_ne_bytes(chunk)) +} + +/// Retrieve a (host-ordered) `i32` from the start of the given slice, if possible. +fn consume_i32(data: &mut &[u8]) -> Result<i32> { + const LEN: usize = size_of::<i32>(); + if data.len() < LEN { + return Err(bloberr!("failed to find {LEN} bytes")); + } + let chunk: [u8; LEN] = data[..LEN].try_into().unwrap(); // safe: just checked + *data = &(*data)[4..]; + Ok(i32::from_ne_bytes(chunk)) +} + +/// Retrieve a (host-ordered) `i64` from the start of the given slice, if possible. +fn consume_i64(data: &mut &[u8]) -> Result<i64> { + const LEN: usize = size_of::<i64>(); + if data.len() < LEN { + return Err(bloberr!("failed to find {LEN} bytes")); + } + let chunk: [u8; LEN] = data[..LEN].try_into().unwrap(); // safe: just checked + *data = &(*data)[LEN..]; + Ok(i64::from_ne_bytes(chunk)) +} + +/// Retrieve a vector of bytes from the start of the given slice, if possible, +/// with the length of the data expected to appear as a host-ordered `u32` prefix. +fn consume_vec(data: &mut &[u8]) -> Result<Vec<u8>> { + let len = consume_u32(data)? as usize; + if len > data.len() { + return Err(bloberr!("failed to find {} bytes", len)); + } + let result = data[..len].to_vec(); + *data = &(*data)[len..]; + Ok(result) +} + +/// Retrieve the contents of a tag of `TagType::Bytes`. The `data` parameter holds +/// the as-yet unparsed data, and a length and offset are read from this (and consumed). +/// This length and offset refer to a location in the combined `blob_data`; however, +/// the offset is expected to be the next unconsumed chunk of `blob_data`, as indicated +/// by `next_blob_offset` (which itself is updated as a result of consuming the data). +fn consume_blob( + data: &mut &[u8], + next_blob_offset: &mut usize, + blob_data: &[u8], +) -> Result<Vec<u8>> { + let data_len = consume_u32(data)? as usize; + let data_offset = consume_u32(data)? as usize; + // Expect the blob data to come from the next offset in the initial blob chunk. + if data_offset != *next_blob_offset { + return Err(bloberr!("got blob offset {} instead of {}", data_offset, next_blob_offset)); + } + if (data_offset + data_len) > blob_data.len() { + return Err(bloberr!( + "blob at offset [{}..{}+{}] goes beyond blob data size {}", + data_offset, + data_offset, + data_len, + blob_data.len(), + )); + } + + let slice = &blob_data[data_offset..data_offset + data_len]; + *next_blob_offset += data_len; + Ok(slice.to_vec()) +} + +/// Deserialize a collection of [`KeyParam`]s in legacy serialized format. The provided slice is +/// modified to contain the unconsumed part of the data. +fn deserialize_params(data: &mut &[u8]) -> Result<Vec<KeyParameter>> { + let blob_data_size = consume_u32(data)? as usize; + if blob_data_size > data.len() { + return Err(bloberr!( + "blob data size {} bigger than data (len={})", + blob_data_size, + data.len() + )); + } + + let blob_data = &data[..blob_data_size]; + let mut next_blob_offset = 0; + + // Move past the blob data. + *data = &data[blob_data_size..]; + + let param_count = consume_u32(data)? as usize; + let param_size = consume_u32(data)? as usize; + if param_size > data.len() { + return Err(bloberr!( + "size mismatch 4+{}+4+4+{} > {}", + blob_data_size, + param_size, + data.len() + )); + } + + let mut results = Vec::new(); + for _i in 0..param_count { + let tag_num = consume_u32(data)? as i32; + let tag = Tag(tag_num); + let value = match tag_type(&tag) { + TagType::INVALID => return Err(bloberr!("invalid tag {:?} encountered", tag)), + TagType::ENUM | TagType::ENUM_REP => { + let val = consume_i32(data)?; + match tag { + Tag::ALGORITHM => KeyParameterValue::Algorithm(Algorithm(val)), + Tag::BLOCK_MODE => KeyParameterValue::BlockMode(BlockMode(val)), + Tag::PADDING => KeyParameterValue::PaddingMode(PaddingMode(val)), + Tag::DIGEST | Tag::RSA_OAEP_MGF_DIGEST => { + KeyParameterValue::Digest(Digest(val)) + } + Tag::EC_CURVE => KeyParameterValue::EcCurve(EcCurve(val)), + Tag::ORIGIN => KeyParameterValue::Origin(KeyOrigin(val)), + Tag::PURPOSE => KeyParameterValue::KeyPurpose(KeyPurpose(val)), + Tag::USER_AUTH_TYPE => { + KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType(val)) + } + _ => KeyParameterValue::Integer(val), + } + } + TagType::UINT | TagType::UINT_REP => KeyParameterValue::Integer(consume_i32(data)?), + TagType::ULONG | TagType::ULONG_REP => { + KeyParameterValue::LongInteger(consume_i64(data)?) + } + TagType::DATE => KeyParameterValue::DateTime(consume_i64(data)?), + TagType::BOOL => KeyParameterValue::BoolValue(consume_bool(data)?), + TagType::BIGNUM | TagType::BYTES => { + KeyParameterValue::Blob(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + _ => return Err(bloberr!("unexpected tag type for {:?}", tag)), + }; + results.push(KeyParameter { tag, value }); + } + + Ok(results) +} + +/// Serialize a collection of [`KeyParameter`]s into a format that is compatible with previous +/// implementations: +/// +/// ```text +/// [0..4] Size B of `TagType::Bytes` data, in host order. +/// [4..4+B] (*) Concatenated contents of each `TagType::Bytes` tag. +/// [4+B..4+B+4] Count N of the number of parameters, in host order. +/// [8+B..8+B+4] Size Z of encoded parameters. +/// [12+B..12+B+Z] Serialized parameters one after another. +/// ``` +/// +/// Individual parameters are serialized in the last chunk as: +/// +/// ```text +/// [0..4] Tag number, in host order. +/// Followed by one of the following depending on the tag's `TagType`; all integers in host order: +/// [4..5] Bool value (`TagType::Bool`) +/// [4..8] i32 values (`TagType::Uint[Rep]`, `TagType::Enum[Rep]`) +/// [4..12] i64 values, in host order (`TagType::UlongRep`, `TagType::Date`) +/// [4..8] + [8..12] Size + offset of data in (*) above (`TagType::Bytes`, `TagType::Bignum`) +/// ``` +fn serialize_params(params: &[KeyParameter]) -> Result<Vec<u8>> { + // First 4 bytes are the length of the combined [`TagType::Bytes`] data; come back to set that + // in a moment. + let mut result = vec![0; 4]; + + // Next append the contents of all of the [`TagType::Bytes`] data. + let mut blob_size = 0u32; + for param in params { + let tag_type = tag_type(¶m.tag); + if let KeyParameterValue::Blob(v) = ¶m.value { + if tag_type != TagType::BIGNUM && tag_type != TagType::BYTES { + return Err(bloberr!("unexpected tag type for tag {:?} with blob", param.tag)); + } + result.extend_from_slice(v); + blob_size += v.len() as u32; + } + } + // Go back and fill in the combined blob length in native order at the start. + result[..4].clone_from_slice(&blob_size.to_ne_bytes()); + + result.extend_from_slice(&(params.len() as u32).to_ne_bytes()); + + let params_size_offset = result.len(); + result.extend_from_slice(&[0u8; 4]); // placeholder for size of elements + let first_param_offset = result.len(); + let mut blob_offset = 0u32; + for param in params { + result.extend_from_slice(&(param.tag.0 as u32).to_ne_bytes()); + match ¶m.value { + KeyParameterValue::Invalid(_v) => { + return Err(bloberr!("invalid tag found in {:?}", param)) + } + + // Enum-holding variants. + KeyParameterValue::Algorithm(v) => { + result.extend_from_slice(&(v.0 as u32).to_ne_bytes()) + } + KeyParameterValue::BlockMode(v) => { + result.extend_from_slice(&(v.0 as u32).to_ne_bytes()) + } + KeyParameterValue::PaddingMode(v) => { + result.extend_from_slice(&(v.0 as u32).to_ne_bytes()) + } + KeyParameterValue::Digest(v) => result.extend_from_slice(&(v.0 as u32).to_ne_bytes()), + KeyParameterValue::EcCurve(v) => result.extend_from_slice(&(v.0 as u32).to_ne_bytes()), + KeyParameterValue::Origin(v) => result.extend_from_slice(&(v.0 as u32).to_ne_bytes()), + KeyParameterValue::KeyPurpose(v) => { + result.extend_from_slice(&(v.0 as u32).to_ne_bytes()) + } + KeyParameterValue::HardwareAuthenticatorType(v) => { + result.extend_from_slice(&(v.0 as u32).to_ne_bytes()) + } + + // Value-holding variants. + KeyParameterValue::Integer(v) => result.extend_from_slice(&(*v as u32).to_ne_bytes()), + KeyParameterValue::BoolValue(_v) => result.push(0x01u8), + KeyParameterValue::LongInteger(v) | KeyParameterValue::DateTime(v) => { + result.extend_from_slice(&(*v as u64).to_ne_bytes()) + } + KeyParameterValue::Blob(v) => { + let blob_len = v.len() as u32; + result.extend_from_slice(&blob_len.to_ne_bytes()); + result.extend_from_slice(&blob_offset.to_ne_bytes()); + blob_offset += blob_len; + } + + _ => return Err(bloberr!("unknown value found in {:?}", param)), + } + } + let serialized_size = (result.len() - first_param_offset) as u32; + + // Go back and fill in the total serialized size. + result[params_size_offset..params_size_offset + 4] + .clone_from_slice(&serialized_size.to_ne_bytes()); + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + KeyOrigin::KeyOrigin, KeyParameter::KeyParameter, + KeyParameterValue::KeyParameterValue as KPV, KeyPurpose::KeyPurpose, + PaddingMode::PaddingMode, Tag::Tag, + }; + + macro_rules! expect_err { + ($result:expr, $err_msg:expr) => { + assert!( + $result.is_err(), + "Expected error containing '{}', got success {:?}", + $err_msg, + $result + ); + let err = $result.err(); + assert!( + format!("{:?}", err).contains($err_msg), + "Unexpected error {:?}, doesn't contain '{}'", + err, + $err_msg + ); + }; + } + + #[test] + fn test_consume_u8() { + let buffer = [1, 2]; + let mut data = &buffer[..]; + assert_eq!(1u8, consume_u8(&mut data).unwrap()); + assert_eq!(2u8, consume_u8(&mut data).unwrap()); + let result = consume_u8(&mut data); + expect_err!(result, "failed to find 1 byte"); + } + + #[test] + fn test_consume_u32() { + // All supported platforms are little-endian. + let buffer = [ + 0x01, 0x02, 0x03, 0x04, // little-endian u32 + 0x04, 0x03, 0x02, 0x01, // little-endian u32 + 0x11, 0x12, 0x13, + ]; + let mut data = &buffer[..]; + assert_eq!(0x04030201u32, consume_u32(&mut data).unwrap()); + assert_eq!(0x01020304u32, consume_u32(&mut data).unwrap()); + let result = consume_u32(&mut data); + expect_err!(result, "failed to find 4 bytes"); + } + + #[test] + fn test_consume_i64() { + // All supported platforms are little-endian. + let buffer = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // little-endian i64 + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // little-endian i64 + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + ]; + let mut data = &buffer[..]; + assert_eq!(0x0807060504030201i64, consume_i64(&mut data).unwrap()); + assert_eq!(0x0102030405060708i64, consume_i64(&mut data).unwrap()); + let result = consume_i64(&mut data); + expect_err!(result, "failed to find 8 bytes"); + } + + #[test] + fn test_consume_vec() { + let buffer = [ + 0x01, 0x00, 0x00, 0x00, 0xaa, // + 0x00, 0x00, 0x00, 0x00, // + 0x01, 0x00, 0x00, 0x00, 0xbb, // + 0x07, 0x00, 0x00, 0x00, 0xbb, // not enough data + ]; + let mut data = &buffer[..]; + assert_eq!(vec![0xaa], consume_vec(&mut data).unwrap()); + assert_eq!(Vec::<u8>::new(), consume_vec(&mut data).unwrap()); + assert_eq!(vec![0xbb], consume_vec(&mut data).unwrap()); + let result = consume_vec(&mut data); + expect_err!(result, "failed to find 7 bytes"); + + let buffer = [ + 0x01, 0x00, 0x00, // + ]; + let mut data = &buffer[..]; + let result = consume_vec(&mut data); + expect_err!(result, "failed to find 4 bytes"); + } + + #[test] + fn test_key_new_from_serialized() { + let hidden = hidden_params(&[], &[SOFTWARE_ROOT_OF_TRUST]); + // Test data originally generated by instrumenting Cuttlefish C++ KeyMint while running VTS + // tests. + let tests = [ + ( + concat!( + "0010000000d43c2f04f948521b81bdbf001310f5920000000000000000000000", + "00000000000c0000006400000002000010200000000300003080000000010000", + "2000000000010000200100000004000020020000000600002001000000be0200", + "1000000000c1020030b0ad0100c20200307b150300bd020060a8bb52407b0100", + "00ce02003011643401cf020030000000003b06b13ae6ae6671", + ), + KeyBlob { + key_material: hex::decode("d43c2f04f948521b81bdbf001310f592").unwrap(), + hw_enforced: vec![], + sw_enforced: vec![ + KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::AES) }, + KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(128) }, + KeyParameter { + tag: Tag::PURPOSE, + value: KPV::KeyPurpose(KeyPurpose::ENCRYPT), + }, + KeyParameter { + tag: Tag::PURPOSE, + value: KPV::KeyPurpose(KeyPurpose::DECRYPT), + }, + KeyParameter { + tag: Tag::BLOCK_MODE, + value: KPV::BlockMode(BlockMode::CBC), + }, + KeyParameter { + tag: Tag::PADDING, + value: KPV::PaddingMode(PaddingMode::NONE), + }, + KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, + KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, + KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, + KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KPV::DateTime(1628871769000), + }, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, + KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, + ], + }, + Some(KeyFormat::RAW), + ), + ( + concat!( + "00df0000003081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6b", + "a14e46ab7ca532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654", + "e9e79413cd503eae3d9cf68ed24f47a00706052b81040023a181890381860004", + "006b840f0db0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d14", + "0743bdd028db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b7", + "45e30142e90685646661550344113aaf28bdee6cb02d19df1faab4398556a909", + "7d6f64b95209601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3", + "b877984d000000000000000000000000000000000c0000006400000002000010", + "030000000a000010030000000100002002000000010000200300000005000020", + "000000000300003009020000be02001000000000c1020030b0ad0100c2020030", + "7b150300bd02006018d352407b010000ce02003011643401cf02003000000000", + "2f69002e55e9b0a3" + ), + KeyBlob { + key_material: hex::decode(concat!( + "3081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6ba14e46ab7c", + "a532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654e9e79413cd", + "503eae3d9cf68ed24f47a00706052b81040023a181890381860004006b840f0d", + "b0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d140743bdd028", + "db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b745e30142e9", + "0685646661550344113aaf28bdee6cb02d19df1faab4398556a9097d6f64b952", + "09601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3b877984d", + )) + .unwrap(), + hw_enforced: vec![], + sw_enforced: vec![ + KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) }, + KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::P_521) }, + KeyParameter { + tag: Tag::PURPOSE, + value: KPV::KeyPurpose(KeyPurpose::SIGN), + }, + KeyParameter { + tag: Tag::PURPOSE, + value: KPV::KeyPurpose(KeyPurpose::VERIFY), + }, + KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) }, + KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(521) }, + KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, + KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, + KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, + KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KPV::DateTime(1628871775000), + }, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, + KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, + ], + }, + Some(KeyFormat::PKCS8), + ), + ( + concat!( + "0037000000541d4c440223650d5f51753c1abd80c725034485551e874d62327c", + "65f6247a057f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc00000000", + "0000000000000000000000000c00000064000000020000108000000003000030", + "b801000001000020020000000100002003000000050000200400000008000030", + "00010000be02001000000000c1020030b0ad0100c20200307b150300bd020060", + "00d752407b010000ce02003011643401cf0200300000000036e6986ffc45fbb0", + ), + KeyBlob { + key_material: hex::decode(concat!( + "541d4c440223650d5f51753c1abd80c725034485551e874d62327c65f6247a05", + "7f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc" + )) + .unwrap(), + hw_enforced: vec![], + sw_enforced: vec![ + KeyParameter { + tag: Tag::ALGORITHM, + value: KPV::Algorithm(Algorithm::HMAC), + }, + KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(440) }, + KeyParameter { + tag: Tag::PURPOSE, + value: KPV::KeyPurpose(KeyPurpose::SIGN), + }, + KeyParameter { + tag: Tag::PURPOSE, + value: KPV::KeyPurpose(KeyPurpose::VERIFY), + }, + KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::SHA_2_256) }, + KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KPV::Integer(256) }, + KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, + KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, + KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, + KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KPV::DateTime(1628871776000), + }, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, + KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, + ], + }, + Some(KeyFormat::RAW), + ), + ( + concat!( + "00a8040000308204a40201000282010100bc47b5c71116766669b91fa747df87", + "a1963df83956569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082", + "825c7c6e482a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31", + "c1ced84878edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c3973", + "6838151642eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d", + "5337c8bf9245d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0de", + "b0957d61dbba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2", + "540c84e45c4a99fb338b76bba7722856b5113341c349708937228f167d238ed8", + "efb9cc19547dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af", + "59fe87421af9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d3703", + "8bf9f588ae20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178", + "a848e06d558c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88e", + "e610260e406c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30a", + "e7a31f8262da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73a", + "e6f8b29a9144eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b", + "4217c8db50db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817", + "d9380b190bd382aaffa37785759f285194c11a188bccde0e2e2902818100fb23", + "3335770c9f3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a63", + "3f7790d1011bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f2", + "7a74852d6c7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd78009490", + "4c2856d2b944fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f45990281", + "8100bfecf2bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a7158458", + "18e01181ff06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb", + "0078b77fb5b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc84", + "20fcbf48d1eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b57", + "298902818100ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd", + "3765559994a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55", + "dad696d3821def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2", + "d8480ed07a7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f6332", + "38f521ba764902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6", + "603c6265f70018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d46", + "93fa48e3a9abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b", + "2a57e18a2e131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc", + "2af4ed75760858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1", + "c0a258d882984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4", + "340a9fae64a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c5526", + "1941b3654533b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a2", + "47f56a337e3b9845b4f2b61356000000000000000000000000000000000d0000", + "007000000002000010010000000300003000080000c800005001000100000000", + "0001000020020000000100002003000000050000200000000006000020010000", + "00be02001000000000c1020030b0ad0100c20200307b150300bd020060a8bb52", + "407b010000ce02003011643401cf02003000000000544862e9c961e857", + ), + KeyBlob { + key_material: hex::decode(concat!( + "308204a40201000282010100bc47b5c71116766669b91fa747df87a1963df839", + "56569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082825c7c6e48", + "2a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31c1ced84878", + "edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c39736838151642", + "eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d5337c8bf92", + "45d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0deb0957d61db", + "ba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2540c84e45c", + "4a99fb338b76bba7722856b5113341c349708937228f167d238ed8efb9cc1954", + "7dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af59fe87421a", + "f9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d37038bf9f588ae", + "20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178a848e06d55", + "8c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88ee610260e40", + "6c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30ae7a31f8262", + "da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73ae6f8b29a91", + "44eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b4217c8db50", + "db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817d9380b190b", + "d382aaffa37785759f285194c11a188bccde0e2e2902818100fb233335770c9f", + "3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a633f7790d101", + "1bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f27a74852d6c", + "7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd780094904c2856d2b9", + "44fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f459902818100bfecf2", + "bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a715845818e01181ff", + "06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb0078b77fb5", + "b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc8420fcbf48d1", + "eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b572989028181", + "00ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd3765559994", + "a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55dad696d382", + "1def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2d8480ed07a", + "7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f633238f521ba76", + "4902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6603c6265f7", + "0018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d4693fa48e3a9", + "abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b2a57e18a2e", + "131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc2af4ed7576", + "0858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1c0a258d882", + "984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4340a9fae64", + "a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c55261941b36545", + "33b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a247f56a337e", + "3b9845b4f2b61356", + )) + .unwrap(), + hw_enforced: vec![], + sw_enforced: vec![ + KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::RSA) }, + KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(2048) }, + KeyParameter { + tag: Tag::RSA_PUBLIC_EXPONENT, + value: KPV::LongInteger(65537), + }, + KeyParameter { + tag: Tag::PURPOSE, + value: KPV::KeyPurpose(KeyPurpose::SIGN), + }, + KeyParameter { + tag: Tag::PURPOSE, + value: KPV::KeyPurpose(KeyPurpose::VERIFY), + }, + KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) }, + KeyParameter { + tag: Tag::PADDING, + value: KPV::PaddingMode(PaddingMode::NONE), + }, + KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, + KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, + KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, + KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KPV::DateTime(1628871769000), + }, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, + KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, + ], + }, + // No support for RSA keys in export_key(). + None, + ), + ]; + + for (input, want, want_format) in tests { + let input = hex::decode(input).unwrap(); + let got = KeyBlob::new_from_serialized(&input, &hidden).expect("invalid keyblob!"); + assert!(got == want); + + if let Some(want_format) = want_format { + let (got_format, _key_material, params) = + export_key(&input, &[]).expect("invalid keyblob!"); + assert_eq!(got_format, want_format); + // All the test cases are software-only keys. + assert_eq!(params, got.sw_enforced); + } + } + } + + #[test] + fn test_add_der_len() { + let tests = [ + (0, "00"), + (1, "01"), + (126, "7e"), + (127, "7f"), + (128, "8180"), + (129, "8181"), + (255, "81ff"), + (256, "820100"), + (257, "820101"), + (65535, "82ffff"), + ]; + for (input, want) in tests { + let mut got = Vec::new(); + add_der_len(&mut got, input).unwrap(); + assert_eq!(hex::encode(got), want, " for input length {input}"); + } + } + + #[test] + fn test_pkcs8_wrap_key_p256() { + // Key material taken from `ec_256_key` in + // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp + let input = hex::decode(concat!( + "3025", // SEQUENCE (ECPrivateKey) + "020101", // INTEGER length 1 value 1 (version) + "0420", // OCTET STRING (privateKey) + "737c2ecd7b8d1940bf2930aa9b4ed3ff", + "941eed09366bc03299986481f3a4d859", + )) + .unwrap(); + let want = hex::decode(concat!( + // RFC 5208 s5 + "3041", // SEQUENCE (PrivateKeyInfo) { + "020100", // INTEGER length 1 value 0 (version) + "3013", // SEQUENCE length 0x13 (AlgorithmIdentifier) { + "0607", // OBJECT IDENTIFIER length 7 (algorithm) + "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey) + "0608", // OBJECT IDENTIFIER length 8 (param) + "2a8648ce3d030107", // 1.2.840.10045.3.1.7 (secp256r1) + // } end SEQUENCE (AlgorithmIdentifier) + "0427", // OCTET STRING (privateKey) holding... + "3025", // SEQUENCE (ECPrivateKey) + "020101", // INTEGER length 1 value 1 (version) + "0420", // OCTET STRING length 0x20 (privateKey) + "737c2ecd7b8d1940bf2930aa9b4ed3ff", + "941eed09366bc03299986481f3a4d859", + // } end SEQUENCE (ECPrivateKey) + // } end SEQUENCE (PrivateKeyInfo) + )) + .unwrap(); + let got = pkcs8_wrap_nist_key(&input, EcCurve::P_256).unwrap(); + assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input)); + } + + #[test] + fn test_pkcs8_wrap_key_p521() { + // Key material taken from `ec_521_key` in + // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp + let input = hex::decode(concat!( + "3047", // SEQUENCE length 0xd3 (ECPrivateKey) + "020101", // INTEGER length 1 value 1 (version) + "0442", // OCTET STRING length 0x42 (privateKey) + "0011458c586db5daa92afab03f4fe46a", + "a9d9c3ce9a9b7a006a8384bec4c78e8e", + "9d18d7d08b5bcfa0e53c75b064ad51c4", + "49bae0258d54b94b1e885ded08ed4fb2", + "5ce9", + // } end SEQUENCE (ECPrivateKey) + )) + .unwrap(); + let want = hex::decode(concat!( + // RFC 5208 s5 + "3060", // SEQUENCE (PrivateKeyInfo) { + "020100", // INTEGER length 1 value 0 (version) + "3010", // SEQUENCE length 0x10 (AlgorithmIdentifier) { + "0607", // OBJECT IDENTIFIER length 7 (algorithm) + "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey) + "0605", // OBJECT IDENTIFIER length 5 (param) + "2b81040023", // 1.3.132.0.35 (secp521r1) + // } end SEQUENCE (AlgorithmIdentifier) + "0449", // OCTET STRING (privateKey) holding... + "3047", // SEQUENCE (ECPrivateKey) + "020101", // INTEGER length 1 value 1 (version) + "0442", // OCTET STRING length 0x42 (privateKey) + "0011458c586db5daa92afab03f4fe46a", + "a9d9c3ce9a9b7a006a8384bec4c78e8e", + "9d18d7d08b5bcfa0e53c75b064ad51c4", + "49bae0258d54b94b1e885ded08ed4fb2", + "5ce9", + // } end SEQUENCE (ECPrivateKey) + // } end SEQUENCE (PrivateKeyInfo) + )) + .unwrap(); + let got = pkcs8_wrap_nist_key(&input, EcCurve::P_521).unwrap(); + assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input)); + } +} diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs index 7bc548eb..a3fd8824 100644 --- a/keystore2/src/utils.rs +++ b/keystore2/src/utils.rs @@ -20,13 +20,16 @@ use crate::key_parameter::KeyParameter; use crate::ks_err; use crate::permission; use crate::permission::{KeyPerm, KeyPermSet, KeystorePerm}; +pub use crate::watchdog_helper::watchdog; use crate::{ database::{KeyType, KeystoreDB}, globals::LEGACY_IMPORTER, + km_compat, + raw_device::KeyMintDevice, }; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics, - KeyParameter::KeyParameter as KmKeyParameter, Tag::Tag, + Algorithm::Algorithm, IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics, + KeyParameter::KeyParameter as KmKeyParameter, KeyParameterValue::KeyParameterValue, Tag::Tag, }; use android_os_permissions_aidl::aidl::android::os::IPermissionController; use android_security_apc::aidl::android::security::apc::{ @@ -46,6 +49,10 @@ use keystore2_apc_compat::{ use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt, ZVec}; use std::iter::IntoIterator; +/// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to GeneralizedTime +/// 999912312359559, which is 253402300799000 ms from Jan 1, 1970. +pub const UNDEFINED_NOT_AFTER: i64 = 253402300799000i64; + /// This function uses its namesake in the permission module and in /// combination with with_calling_sid from the binder crate to check /// if the caller has the given keystore permission. @@ -122,6 +129,15 @@ pub fn check_unique_id_attestation_permissions() -> anyhow::Result<()> { check_android_permission("android.permission.REQUEST_UNIQUE_ID_ATTESTATION") } +/// This function checks whether the calling app has the Android permissions needed to manage +/// users. Only callers that can manage users are allowed to get a list of apps affected +/// by a user's SID changing. +/// It throws an error if the permissions cannot be verified or if the caller doesn't +/// have the right permissions. Otherwise it returns silently. +pub fn check_get_app_uids_affected_by_sid_permissions() -> anyhow::Result<()> { + check_android_permission("android.permission.MANAGE_USERS") +} + fn check_android_permission(permission: &str) -> anyhow::Result<()> { let permission_controller: Strong<dyn IPermissionController::IPermissionController> = binder::get_interface("permission")?; @@ -163,6 +179,148 @@ pub fn key_characteristics_to_internal( .collect() } +/// Import a keyblob that is of the format used by the software C++ KeyMint implementation. After +/// successful import, invoke both the `new_blob_handler` and `km_op` closures. On success a tuple +/// of the `km_op`s result and the optional upgraded blob is returned. +fn import_keyblob_and_perform_op<T, KmOp, NewBlobHandler>( + km_dev: &dyn IKeyMintDevice, + inner_keyblob: &[u8], + upgrade_params: &[KmKeyParameter], + km_op: KmOp, + new_blob_handler: NewBlobHandler, +) -> Result<(T, Option<Vec<u8>>)> +where + KmOp: Fn(&[u8]) -> Result<T, Error>, + NewBlobHandler: FnOnce(&[u8]) -> Result<()>, +{ + let (format, key_material, mut chars) = + crate::sw_keyblob::export_key(inner_keyblob, upgrade_params)?; + log::debug!( + "importing {:?} key material (len={}) with original chars={:?}", + format, + key_material.len(), + chars + ); + let asymmetric = chars.iter().any(|kp| { + kp.tag == Tag::ALGORITHM + && (kp.value == KeyParameterValue::Algorithm(Algorithm::RSA) + || (kp.value == KeyParameterValue::Algorithm(Algorithm::EC))) + }); + + // Combine the characteristics of the previous keyblob with the upgrade parameters (which might + // include special things like APPLICATION_ID / APPLICATION_DATA). + chars.extend_from_slice(upgrade_params); + + // Now filter out values from the existing keyblob that shouldn't be set on import, either + // because they are per-operation parameter or because they are auto-added by KeyMint itself. + let mut import_params: Vec<KmKeyParameter> = chars + .into_iter() + .filter(|kp| { + !matches!( + kp.tag, + Tag::ORIGIN + | Tag::ROOT_OF_TRUST + | Tag::OS_VERSION + | Tag::OS_PATCHLEVEL + | Tag::UNIQUE_ID + | Tag::ATTESTATION_CHALLENGE + | Tag::ATTESTATION_APPLICATION_ID + | Tag::ATTESTATION_ID_BRAND + | Tag::ATTESTATION_ID_DEVICE + | Tag::ATTESTATION_ID_PRODUCT + | Tag::ATTESTATION_ID_SERIAL + | Tag::ATTESTATION_ID_IMEI + | Tag::ATTESTATION_ID_MEID + | Tag::ATTESTATION_ID_MANUFACTURER + | Tag::ATTESTATION_ID_MODEL + | Tag::VENDOR_PATCHLEVEL + | Tag::BOOT_PATCHLEVEL + | Tag::DEVICE_UNIQUE_ATTESTATION + | Tag::ATTESTATION_ID_SECOND_IMEI + | Tag::NONCE + | Tag::MAC_LENGTH + | Tag::CERTIFICATE_SERIAL + | Tag::CERTIFICATE_SUBJECT + | Tag::CERTIFICATE_NOT_BEFORE + | Tag::CERTIFICATE_NOT_AFTER + ) + }) + .collect(); + + // Now that any previous values have been removed, add any additional parameters that needed for + // import. In particular, if we are generating/importing an asymmetric key, we need to make sure + // that NOT_BEFORE and NOT_AFTER are present. + if asymmetric { + import_params.push(KmKeyParameter { + tag: Tag::CERTIFICATE_NOT_BEFORE, + value: KeyParameterValue::DateTime(0), + }); + import_params.push(KmKeyParameter { + tag: Tag::CERTIFICATE_NOT_AFTER, + value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER), + }); + } + log::debug!("import parameters={import_params:?}"); + + let creation_result = { + let _wp = watchdog::watch_millis( + "In utils::import_keyblob_and_perform_op: calling importKey.", + 500, + ); + map_km_error(km_dev.importKey(&import_params, format, &key_material, None)) + } + .context(ks_err!("Upgrade failed."))?; + + // Note that the importKey operation will produce key characteristics that may be different + // than are already stored in Keystore's SQL database. In particular, the KeyMint + // implementation will now mark the key as `Origin::IMPORTED` not `Origin::GENERATED`, and + // the security level for characteristics will now be `TRUSTED_ENVIRONMENT` not `SOFTWARE`. + // + // However, the DB metadata still accurately reflects the original origin of the key, and + // so we leave the values as-is (and so any `KeyInfo` retrieved in the Java layer will get the + // same results before and after import). + // + // Note that this also applies to the `USAGE_COUNT_LIMIT` parameter -- if the key has already + // been used, then the DB version of the parameter will be (and will continue to be) lower + // than the original count bound to the keyblob. This means that Keystore's policing of + // usage counts will continue where it left off. + + new_blob_handler(&creation_result.keyBlob).context(ks_err!("calling new_blob_handler."))?; + + km_op(&creation_result.keyBlob) + .map(|v| (v, Some(creation_result.keyBlob))) + .context(ks_err!("Calling km_op after upgrade.")) +} + +/// Upgrade a keyblob then invoke both the `new_blob_handler` and the `km_op` closures. On success +/// a tuple of the `km_op`s result and the optional upgraded blob is returned. +fn upgrade_keyblob_and_perform_op<T, KmOp, NewBlobHandler>( + km_dev: &dyn IKeyMintDevice, + key_blob: &[u8], + upgrade_params: &[KmKeyParameter], + km_op: KmOp, + new_blob_handler: NewBlobHandler, +) -> Result<(T, Option<Vec<u8>>)> +where + KmOp: Fn(&[u8]) -> Result<T, Error>, + NewBlobHandler: FnOnce(&[u8]) -> Result<()>, +{ + let upgraded_blob = { + let _wp = watchdog::watch_millis( + "In utils::upgrade_keyblob_and_perform_op: calling upgradeKey.", + 500, + ); + map_km_error(km_dev.upgradeKey(key_blob, upgrade_params)) + } + .context(ks_err!("Upgrade failed."))?; + + new_blob_handler(&upgraded_blob).context(ks_err!("calling new_blob_handler."))?; + + km_op(&upgraded_blob) + .map(|v| (v, Some(upgraded_blob))) + .context(ks_err!("Calling km_op after upgrade.")) +} + /// This function can be used to upgrade key blobs on demand. The return value of /// `km_op` is inspected and if ErrorCode::KEY_REQUIRES_UPGRADE is encountered, /// an attempt is made to upgrade the key blob. On success `new_blob_handler` is called @@ -171,6 +329,7 @@ pub fn key_characteristics_to_internal( /// optional upgraded blob is returned. pub fn upgrade_keyblob_if_required_with<T, KmOp, NewBlobHandler>( km_dev: &dyn IKeyMintDevice, + km_dev_version: i32, key_blob: &[u8], upgrade_params: &[KmKeyParameter], km_op: KmOp, @@ -181,21 +340,91 @@ where NewBlobHandler: FnOnce(&[u8]) -> Result<()>, { match km_op(key_blob) { - Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => { - let upgraded_blob = { - let _wp = watchdog::watch_millis( - "In utils::upgrade_keyblob_if_required_with: calling upgradeKey.", - 500, + Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => upgrade_keyblob_and_perform_op( + km_dev, + key_blob, + upgrade_params, + km_op, + new_blob_handler, + ), + Err(Error::Km(ErrorCode::INVALID_KEY_BLOB)) + if km_dev_version >= KeyMintDevice::KEY_MINT_V1 => + { + // A KeyMint (not Keymaster via km_compat) device says that this is an invalid keyblob. + // + // This may be because the keyblob was created before an Android upgrade, and as part of + // the device upgrade the underlying Keymaster/KeyMint implementation has been upgraded. + // + // If that's the case, there are three possible scenarios: + if key_blob.starts_with(km_compat::KEYMASTER_BLOB_HW_PREFIX) { + // 1) The keyblob was created in hardware by the km_compat C++ code, using a prior + // Keymaster implementation, and wrapped. + // + // In this case, the keyblob will have the km_compat magic prefix, including the + // marker that indicates that this was a hardware-backed key. + // + // The inner keyblob should still be recognized by the hardware implementation, so + // strip the prefix and attempt a key upgrade. + log::info!( + "found apparent km_compat(Keymaster) HW blob, attempt strip-and-upgrade" + ); + let inner_keyblob = &key_blob[km_compat::KEYMASTER_BLOB_HW_PREFIX.len()..]; + upgrade_keyblob_and_perform_op( + km_dev, + inner_keyblob, + upgrade_params, + km_op, + new_blob_handler, + ) + } else if keystore2_flags::import_previously_emulated_keys() + && key_blob.starts_with(km_compat::KEYMASTER_BLOB_SW_PREFIX) + { + // 2) The keyblob was created in software by the km_compat C++ code because a prior + // Keymaster implementation did not support ECDH (which was only added in KeyMint). + // + // In this case, the keyblob with have the km_compat magic prefix, but with the + // marker that indicates that this was a software-emulated key. + // + // The inner keyblob should be in the format produced by the C++ reference + // implementation of KeyMint. Extract the key material and import it into the + // current KeyMint device. + log::info!("found apparent km_compat(Keymaster) SW blob, attempt strip-and-import"); + let inner_keyblob = &key_blob[km_compat::KEYMASTER_BLOB_SW_PREFIX.len()..]; + import_keyblob_and_perform_op( + km_dev, + inner_keyblob, + upgrade_params, + km_op, + new_blob_handler, + ) + } else if let (true, km_compat::KeyBlob::Wrapped(inner_keyblob)) = ( + keystore2_flags::import_previously_emulated_keys(), + km_compat::unwrap_keyblob(key_blob), + ) { + // 3) The keyblob was created in software by km_compat.rs because a prior KeyMint + // implementation did not support a feature present in the current KeyMint spec. + // (For example, a curve 25519 key created when the device only supported KeyMint + // v1). + // + // In this case, the keyblob with have the km_compat.rs wrapper around it to + // indicate that this was a software-emulated key. + // + // The inner keyblob should be in the format produced by the C++ reference + // implementation of KeyMint. Extract the key material and import it into the + // current KeyMint device. + log::info!( + "found apparent km_compat.rs(KeyMint) SW blob, attempt strip-and-import" ); - map_km_error(km_dev.upgradeKey(key_blob, upgrade_params)) + import_keyblob_and_perform_op( + km_dev, + inner_keyblob, + upgrade_params, + km_op, + new_blob_handler, + ) + } else { + Err(Error::Km(ErrorCode::INVALID_KEY_BLOB)).context(ks_err!("Calling km_op")) } - .context(ks_err!("Upgrade failed."))?; - - new_blob_handler(&upgraded_blob).context(ks_err!("calling new_blob_handler."))?; - - km_op(&upgraded_blob) - .map(|v| (v, Some(upgraded_blob))) - .context(ks_err!("Calling km_op after upgrade.")) } r => r.map(|v| (v, None)).context(ks_err!("Calling km_op.")), } @@ -215,9 +444,9 @@ pub fn key_parameters_to_authorizations( /// as an integer. pub fn get_current_time_in_milliseconds() -> i64 { let mut current_time = libc::timespec { tv_sec: 0, tv_nsec: 0 }; - // Following unsafe block includes one system call to get monotonic time. - // Therefore, it is not considered harmful. - unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_RAW, &mut current_time) }; + // SAFETY: The pointer is valid because it comes from a reference, and clock_gettime doesn't + // retain it beyond the call. + unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut current_time) }; current_time.tv_sec as i64 * 1000 + (current_time.tv_nsec as i64 / 1_000_000) } @@ -258,32 +487,49 @@ pub fn uid_to_android_user(uid: u32) -> u32 { rustutils::users::multiuser_get_user_id(uid) } -/// List all key aliases for a given domain + namespace. -pub fn list_key_entries( - db: &mut KeystoreDB, - domain: Domain, - namespace: i64, -) -> Result<Vec<KeyDescriptor>> { - let mut result = Vec::new(); - result.append( - &mut LEGACY_IMPORTER - .list_uid(domain, namespace) - .context(ks_err!("Trying to list legacy keys."))?, - ); - result.append( - &mut db - .list(domain, namespace, KeyType::Client) - .context(ks_err!("Trying to list keystore database."))?, - ); +/// Merges and filters two lists of key descriptors. The first input list, legacy_descriptors, +/// is assumed to not be sorted or filtered. As such, all key descriptors in that list whose +/// alias is less than, or equal to, start_past_alias (if provided) will be removed. +/// This list will then be merged with the second list, db_descriptors. The db_descriptors list +/// is assumed to be sorted and filtered so the output list will be sorted prior to returning. +/// The returned value is a list of KeyDescriptor objects whose alias is greater than +/// start_past_alias, sorted and de-duplicated. +fn merge_and_filter_key_entry_lists( + legacy_descriptors: &[KeyDescriptor], + db_descriptors: &[KeyDescriptor], + start_past_alias: Option<&str>, +) -> Vec<KeyDescriptor> { + let mut result: Vec<KeyDescriptor> = + match start_past_alias { + Some(past_alias) => legacy_descriptors + .iter() + .filter(|kd| { + if let Some(alias) = &kd.alias { + alias.as_str() > past_alias + } else { + false + } + }) + .cloned() + .collect(), + None => legacy_descriptors.to_vec(), + }; + + result.extend_from_slice(db_descriptors); result.sort_unstable(); result.dedup(); + result +} +fn estimate_safe_amount_to_return( + key_descriptors: &[KeyDescriptor], + response_size_limit: usize, +) -> usize { let mut items_to_return = 0; let mut returned_bytes: usize = 0; - const RESPONSE_SIZE_LIMIT: usize = 358400; // Estimate the transaction size to avoid returning more items than what // could fit in a binder transaction. - for kd in result.iter() { + for kd in key_descriptors.iter() { // 4 bytes for the Domain enum // 8 bytes for the Namespace long. returned_bytes += 4 + 8; @@ -298,11 +544,11 @@ pub fn list_key_entries( // The binder transaction size limit is 1M. Empirical measurements show // that the binder overhead is 60% (to be confirmed). So break after // 350KB and return a partial list. - if returned_bytes > RESPONSE_SIZE_LIMIT { + if returned_bytes > response_size_limit { log::warn!( "Key descriptors list ({} items) may exceed binder \ size, returning {} items est {} bytes.", - result.len(), + key_descriptors.len(), items_to_return, returned_bytes ); @@ -310,37 +556,47 @@ pub fn list_key_entries( } items_to_return += 1; } - Ok(result[..items_to_return].to_vec()) + items_to_return } -/// This module provides helpers for simplified use of the watchdog module. -#[cfg(feature = "watchdog")] -pub mod watchdog { - pub use crate::watchdog::WatchPoint; - use crate::watchdog::Watchdog; - use lazy_static::lazy_static; - use std::sync::Arc; - use std::time::Duration; - - lazy_static! { - /// A Watchdog thread, that can be used to create watch points. - static ref WD: Arc<Watchdog> = Watchdog::new(Duration::from_secs(10)); - } +/// List all key aliases for a given domain + namespace. whose alias is greater +/// than start_past_alias (if provided). +pub fn list_key_entries( + db: &mut KeystoreDB, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, +) -> Result<Vec<KeyDescriptor>> { + let legacy_key_descriptors: Vec<KeyDescriptor> = LEGACY_IMPORTER + .list_uid(domain, namespace) + .context(ks_err!("Trying to list legacy keys."))?; + + // The results from the database will be sorted and unique + let db_key_descriptors: Vec<KeyDescriptor> = db + .list_past_alias(domain, namespace, KeyType::Client, start_past_alias) + .context(ks_err!("Trying to list keystore database past alias."))?; + + let merged_key_entries = merge_and_filter_key_entry_lists( + &legacy_key_descriptors, + &db_key_descriptors, + start_past_alias, + ); - /// Sets a watch point with `id` and a timeout of `millis` milliseconds. - pub fn watch_millis(id: &'static str, millis: u64) -> Option<WatchPoint> { - Watchdog::watch(&WD, id, Duration::from_millis(millis)) - } + const RESPONSE_SIZE_LIMIT: usize = 358400; + let safe_amount_to_return = + estimate_safe_amount_to_return(&merged_key_entries, RESPONSE_SIZE_LIMIT); + Ok(merged_key_entries[..safe_amount_to_return].to_vec()) +} - /// Like `watch_millis` but with a callback that is called every time a report - /// is printed about this watch point. - pub fn watch_millis_with( - id: &'static str, - millis: u64, - callback: impl Fn() -> String + Send + 'static, - ) -> Option<WatchPoint> { - Watchdog::watch_with(&WD, id, Duration::from_millis(millis), callback) - } +/// Count all key aliases for a given domain + namespace. +pub fn count_key_entries(db: &mut KeystoreDB, domain: Domain, namespace: i64) -> Result<i32> { + let legacy_keys = LEGACY_IMPORTER + .list_uid(domain, namespace) + .context(ks_err!("Trying to list legacy keys."))?; + + let num_keys_in_db = db.count_keys(domain, namespace, KeyType::Client)?; + + Ok((legacy_keys.len() + num_keys_in_db) as i32) } /// Trait implemented by objects that can be used to decrypt cipher text using AES-GCM. @@ -372,25 +628,6 @@ impl<T: AesGcmKey> AesGcm for T { } } -/// This module provides empty/noop implementations of the watch dog utility functions. -#[cfg(not(feature = "watchdog"))] -pub mod watchdog { - /// Noop watch point. - pub struct WatchPoint(); - /// Sets a Noop watch point. - fn watch_millis(_: &'static str, _: u64) -> Option<WatchPoint> { - None - } - - pub fn watch_millis_with( - _: &'static str, - _: u64, - _: impl Fn() -> String + Send + 'static, - ) -> Option<WatchPoint> { - None - } -} - #[cfg(test)] mod tests { use super::*; @@ -407,4 +644,84 @@ mod tests { } }) } + + fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> { + key_aliases + .iter() + .map(|key_alias| KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(key_alias.to_string()), + blob: None, + }) + .collect::<Vec<KeyDescriptor>>() + } + + fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> { + key_descriptors + .iter() + .map( + |kd| { + if let Some(alias) = &kd.alias { + String::from(alias) + } else { + String::from("") + } + }, + ) + .collect::<Vec<String>>() + } + + #[test] + fn test_safe_amount_to_return() -> Result<()> { + let key_aliases = vec!["key1", "key2", "key3"]; + let key_descriptors = create_key_descriptors_from_aliases(&key_aliases); + + assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 20), 1); + assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 50), 2); + assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 100), 3); + Ok(()) + } + + #[test] + fn test_merge_and_sort_lists_without_filtering() -> Result<()> { + let legacy_key_aliases = vec!["key_c", "key_a", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_a", "key_d"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = + merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]); + Ok(()) + } + + #[test] + fn test_merge_and_sort_lists_with_filtering() -> Result<()> { + let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_c", "key_g"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = merge_and_filter_key_entry_lists( + &legacy_key_descriptors, + &db_key_descriptors, + Some("key_b"), + ); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]); + Ok(()) + } + + #[test] + fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> { + let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_d", "key_e", "key_g"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = merge_and_filter_key_entry_lists( + &legacy_key_descriptors, + &db_key_descriptors, + Some("key_c"), + ); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]); + Ok(()) + } } diff --git a/keystore2/src/vintf/vintf.cpp b/keystore2/src/vintf/vintf.cpp deleted file mode 100644 index a550b100..00000000 --- a/keystore2/src/vintf/vintf.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <algorithm> -#include <vintf/HalManifest.h> -#include <vintf/VintfObject.h> - -#include "rust/cxx.h" - -rust::Vec<rust::String> convert(const std::set<std::string>& names) { - rust::Vec<rust::String> result; - std::copy(names.begin(), names.end(), std::back_inserter(result)); - return result; -} - -rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_version, - size_t minor_version, rust::Str interfaceName) { - android::vintf::Version version(major_version, minor_version); - const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); - const auto names = manifest->getHidlInstances(static_cast<std::string>(package), version, - static_cast<std::string>(interfaceName)); - return convert(names); -} - -rust::Vec<rust::String> get_aidl_instances(rust::Str package, size_t version, - rust::Str interfaceName) { - const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); - const auto names = manifest->getAidlInstances(static_cast<std::string>(package), version, - static_cast<std::string>(interfaceName)); - return convert(names); -} diff --git a/keystore2/src/watchdog_helper.rs b/keystore2/src/watchdog_helper.rs new file mode 100644 index 00000000..92a0abc1 --- /dev/null +++ b/keystore2/src/watchdog_helper.rs @@ -0,0 +1,64 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Helpers for the watchdog module. + +/// This module provides helpers for simplified use of the watchdog module. +#[cfg(feature = "watchdog")] +pub mod watchdog { + use lazy_static::lazy_static; + use std::sync::Arc; + use std::time::Duration; + pub use watchdog_rs::WatchPoint; + use watchdog_rs::Watchdog; + + lazy_static! { + /// A Watchdog thread, that can be used to create watch points. + static ref WD: Arc<Watchdog> = Watchdog::new(Duration::from_secs(10)); + } + + /// Sets a watch point with `id` and a timeout of `millis` milliseconds. + pub fn watch_millis(id: &'static str, millis: u64) -> Option<WatchPoint> { + Watchdog::watch(&WD, id, Duration::from_millis(millis)) + } + + /// Like `watch_millis` but with a callback that is called every time a report + /// is printed about this watch point. + pub fn watch_millis_with( + id: &'static str, + millis: u64, + callback: impl Fn() -> String + Send + 'static, + ) -> Option<WatchPoint> { + Watchdog::watch_with(&WD, id, Duration::from_millis(millis), callback) + } +} + +/// This module provides empty/noop implementations of the watch dog utility functions. +#[cfg(not(feature = "watchdog"))] +pub mod watchdog { + /// Noop watch point. + pub struct WatchPoint(); + /// Sets a Noop watch point. + fn watch_millis(_: &'static str, _: u64) -> Option<WatchPoint> { + None + } + + pub fn watch_millis_with( + _: &'static str, + _: u64, + _: impl Fn() -> String + Send + 'static, + ) -> Option<WatchPoint> { + None + } +} diff --git a/keystore2/test_utils/Android.bp b/keystore2/test_utils/Android.bp new file mode 100644 index 00000000..4c7c18a4 --- /dev/null +++ b/keystore2/test_utils/Android.bp @@ -0,0 +1,109 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "system_security_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_security_license"], +} + +rust_defaults { + name: "libkeystore2_test_utils_defaults", + defaults: [ + "keymint_use_latest_hal_aidl_rust", + "keystore2_use_latest_aidl_rust", + ], + rustlibs: [ + "android.security.authorization-rust", + "libanyhow", + "libbinder_rs", + "libcxx", + "libkeystore2_selinux", + "liblog_rust", + "libnix", + "librand", + "librustutils", + "libserde", + "libserde_cbor", + "libthiserror", + ], + static_libs: [ + "libkeystore-engine", + "libkeystore2_ffi_test_utils", + ], + shared_libs: [ + "android.system.keystore2-V4-ndk", + "libbase", + "libcrypto", + "libkeymaster_portable", + "libkeymint_support", + ], +} + +rust_library { + name: "libkeystore2_test_utils", + crate_name: "keystore2_test_utils", + srcs: ["lib.rs"], + defaults: ["libkeystore2_test_utils_defaults"], +} + +rust_test { + name: "keystore2_test_utils_test", + srcs: ["lib.rs"], + defaults: ["libkeystore2_test_utils_defaults"], + test_suites: ["general-tests"], + require_root: true, + auto_gen_config: true, + compile_multilib: "first", +} + +cc_library_static { + name: "libkeystore2_ffi_test_utils", + srcs: ["ffi_test_utils.cpp"], + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + "keystore2_use_latest_aidl_ndk_shared", + ], + generated_headers: [ + "cxx-bridge-header", + "libkeystore2_ffi_test_utils_bridge_header", + ], + generated_sources: ["libkeystore2_ffi_test_utils_bridge_code"], + static_libs: ["libkeystore-engine"], + shared_libs: [ + "libbase", + "libcrypto", + "libkeymaster_portable", + "libkeymint_support", + ], +} + +genrule { + name: "libkeystore2_ffi_test_utils_bridge_code", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) >> $(out)", + srcs: ["ffi_test_utils.rs"], + out: ["libkeystore2_test_utils_cxx_generated.cc"], +} + +genrule { + name: "libkeystore2_ffi_test_utils_bridge_header", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) --header >> $(out)", + srcs: ["ffi_test_utils.rs"], + out: ["ffi_test_utils.rs.h"], +} diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs index 4608bc5f..2cb2aaf6 100644 --- a/keystore2/test_utils/authorizations.rs +++ b/keystore2/test_utils/authorizations.rs @@ -161,6 +161,205 @@ 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<u8>) -> 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<u8>) -> 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<u8>) -> 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<u8>) -> 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<u8>) -> 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<u8>) -> 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<u8>) -> 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<u8>) -> 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<u8>) -> Self { + self.0.push(KeyParameter { + tag: Tag::ATTESTATION_ID_MODEL, + value: KeyParameterValue::Blob(b), + }); + self + } + + /// Set active date-time. + pub fn active_date_time(mut self, date: i64) -> Self { + self.0.push(KeyParameter { + tag: Tag::ACTIVE_DATETIME, + value: KeyParameterValue::DateTime(date), + }); + self + } + + /// Set origination expire date-time. + pub fn origination_expire_date_time(mut self, date: i64) -> Self { + self.0.push(KeyParameter { + tag: Tag::ORIGINATION_EXPIRE_DATETIME, + value: KeyParameterValue::DateTime(date), + }); + self + } + + /// Set usage expire date-time. + pub fn usage_expire_date_time(mut self, date: i64) -> Self { + self.0.push(KeyParameter { + tag: Tag::USAGE_EXPIRE_DATETIME, + value: KeyParameterValue::DateTime(date), + }); + self + } + + /// Set boot loader only. + pub fn boot_loader_only(mut self) -> Self { + self.0.push(KeyParameter { + tag: Tag::BOOTLOADER_ONLY, + value: KeyParameterValue::BoolValue(true), + }); + self + } + + /// Set early boot only. + pub fn early_boot_only(mut self) -> Self { + self.0.push(KeyParameter { + tag: Tag::EARLY_BOOT_ONLY, + value: KeyParameterValue::BoolValue(true), + }); + self + } + + /// Set max uses per boot. + pub fn max_uses_per_boot(mut self, max_uses: i32) -> Self { + self.0.push(KeyParameter { + tag: Tag::MAX_USES_PER_BOOT, + value: KeyParameterValue::Integer(max_uses), + }); + self + } + + /// Set max usage count. + pub fn usage_count_limit(mut self, usage_count: i32) -> Self { + self.0.push(KeyParameter { + tag: Tag::USAGE_COUNT_LIMIT, + value: KeyParameterValue::Integer(usage_count), + }); + self + } + + /// Set creation date-time. + pub fn creation_date_time(mut self, date: i64) -> Self { + self.0.push(KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KeyParameterValue::DateTime(date), + }); + self + } + + /// Set include unique id. + pub fn include_unique_id(mut self) -> Self { + self.0.push(KeyParameter { + tag: Tag::INCLUDE_UNIQUE_ID, + value: KeyParameterValue::BoolValue(true), + }); + self + } + + /// Add app-data. + pub fn app_data(mut self, b: Vec<u8>) -> Self { + self.0.push(KeyParameter { tag: Tag::APPLICATION_DATA, value: KeyParameterValue::Blob(b) }); + self + } + + /// Add app-id. + pub fn app_id(mut self, b: Vec<u8>) -> Self { + self.0.push(KeyParameter { tag: Tag::APPLICATION_ID, value: KeyParameterValue::Blob(b) }); + self + } + + /// Set device-unique-attestation. + pub fn device_unique_attestation(mut self) -> Self { + self.0.push(KeyParameter { + tag: Tag::DEVICE_UNIQUE_ATTESTATION, + value: KeyParameterValue::BoolValue(true), + }); + self + } + + /// Add certificate serial number. + pub fn cert_serial(mut self, b: Vec<u8>) -> Self { + self.0 + .push(KeyParameter { tag: Tag::CERTIFICATE_SERIAL, value: KeyParameterValue::Blob(b) }); + self + } + + /// Add certificate subject name. + pub fn cert_subject_name(mut self, b: Vec<u8>) -> Self { + self.0.push(KeyParameter { + tag: Tag::CERTIFICATE_SUBJECT, + value: KeyParameterValue::Blob(b), + }); + self + } } impl Deref for AuthSetBuilder { diff --git a/keystore2/test_utils/ffi_test_utils.cpp b/keystore2/test_utils/ffi_test_utils.cpp new file mode 100644 index 00000000..ea030692 --- /dev/null +++ b/keystore2/test_utils/ffi_test_utils.cpp @@ -0,0 +1,752 @@ +#include "ffi_test_utils.hpp" + +#include <iostream> +#include <vector> + +#include <android-base/logging.h> +#include <keymaster/km_openssl/attestation_record.h> +#include <keymaster/km_openssl/openssl_err.h> +#include <keymaster/km_openssl/openssl_utils.h> +#include <keymint_support/attestation_record.h> +#include <keymint_support/keymint_utils.h> +#include <openssl/mem.h> + +using keymaster::ASN1_OBJECT_Ptr; +using keymaster::EVP_PKEY_Ptr; +using keymaster::X509_Ptr; +using std::endl; +using std::string; +using std::vector; + +#define TAG_SEQUENCE 0x30 +#define LENGTH_MASK 0x80 +#define LENGTH_VALUE_MASK 0x7F + +/* EVP_PKEY_from_keystore is from system/security/keystore-engine. */ +extern "C" EVP_PKEY* EVP_PKEY_from_keystore(const char* key_id); + +typedef std::vector<uint8_t> certificate_t; + +/** + * ASN.1 structure for `KeyDescription` Schema. + * See `IKeyMintDevice.aidl` for documentation of the `KeyDescription` schema. + * KeyDescription ::= SEQUENCE( + * keyFormat INTEGER, # Values from KeyFormat enum. + * keyParams AuthorizationList, + * ) + */ +typedef struct key_description { + ASN1_INTEGER* key_format; + keymaster::KM_AUTH_LIST* key_params; +} TEST_KEY_DESCRIPTION; + +ASN1_SEQUENCE(TEST_KEY_DESCRIPTION) = { + ASN1_SIMPLE(TEST_KEY_DESCRIPTION, key_format, ASN1_INTEGER), + ASN1_SIMPLE(TEST_KEY_DESCRIPTION, key_params, keymaster::KM_AUTH_LIST), +} ASN1_SEQUENCE_END(TEST_KEY_DESCRIPTION); +DECLARE_ASN1_FUNCTIONS(TEST_KEY_DESCRIPTION); + +/** + * ASN.1 structure for `SecureKeyWrapper` Schema. + * See `IKeyMintDevice.aidl` for documentation of the `SecureKeyWrapper` schema. + * SecureKeyWrapper ::= SEQUENCE( + * version INTEGER, # Contains value 0 + * encryptedTransportKey OCTET_STRING, + * initializationVector OCTET_STRING, + * keyDescription KeyDescription, + * encryptedKey OCTET_STRING, + * tag OCTET_STRING + * ) + */ +typedef struct secure_key_wrapper { + ASN1_INTEGER* version; + ASN1_OCTET_STRING* encrypted_transport_key; + ASN1_OCTET_STRING* initialization_vector; + TEST_KEY_DESCRIPTION* key_desc; + ASN1_OCTET_STRING* encrypted_key; + ASN1_OCTET_STRING* tag; +} TEST_SECURE_KEY_WRAPPER; + +ASN1_SEQUENCE(TEST_SECURE_KEY_WRAPPER) = { + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, version, ASN1_INTEGER), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, encrypted_transport_key, ASN1_OCTET_STRING), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, initialization_vector, ASN1_OCTET_STRING), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, key_desc, TEST_KEY_DESCRIPTION), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, encrypted_key, ASN1_OCTET_STRING), + ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, tag, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(TEST_SECURE_KEY_WRAPPER); +DECLARE_ASN1_FUNCTIONS(TEST_SECURE_KEY_WRAPPER); + +IMPLEMENT_ASN1_FUNCTIONS(TEST_SECURE_KEY_WRAPPER); +IMPLEMENT_ASN1_FUNCTIONS(TEST_KEY_DESCRIPTION); + +struct TEST_KEY_DESCRIPTION_Delete { + void operator()(TEST_KEY_DESCRIPTION* p) { TEST_KEY_DESCRIPTION_free(p); } +}; +struct TEST_SECURE_KEY_WRAPPER_Delete { + void operator()(TEST_SECURE_KEY_WRAPPER* p) { TEST_SECURE_KEY_WRAPPER_free(p); } +}; + +const std::string keystore2_grant_id_prefix("ks2_keystore-engine_grant_id:"); + +string bin2hex(const vector<uint8_t>& data) { + string retval; + char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + retval.reserve(data.size() * 2 + 1); + for (uint8_t byte : data) { + retval.push_back(nibble2hex[0x0F & (byte >> 4)]); + retval.push_back(nibble2hex[0x0F & byte]); + } + return retval; +} + +string x509NameToStr(X509_NAME* name) { + char* s = X509_NAME_oneline(name, nullptr, 0); + string retval(s); + OPENSSL_free(s); + return retval; +} + +X509_Ptr parseCertBlob(const vector<uint8_t>& blob) { + const uint8_t* p = blob.data(); + return X509_Ptr(d2i_X509(nullptr /* allocate new */, &p, blob.size())); +} + +// Extract attestation record from cert. Returned object is still part of cert; don't free it +// separately. +ASN1_OCTET_STRING* getAttestationRecord(X509* certificate) { + ASN1_OBJECT_Ptr oid(OBJ_txt2obj(aidl::android::hardware::security::keymint::kAttestionRecordOid, + 1 /* dotted string format */)); + if (!oid.get()) return nullptr; + + int location = X509_get_ext_by_OBJ(certificate, oid.get(), -1 /* search from beginning */); + if (location == -1) return nullptr; + + X509_EXTENSION* attest_rec_ext = X509_get_ext(certificate, location); + if (!attest_rec_ext) return nullptr; + + ASN1_OCTET_STRING* attest_rec = X509_EXTENSION_get_data(attest_rec_ext); + return attest_rec; +} + +bool ChainSignaturesAreValid(const vector<certificate_t>& chain, bool strict_issuer_check) { + std::stringstream cert_data; + + for (size_t i = 0; i < chain.size(); ++i) { + cert_data << bin2hex(chain[i]) << std::endl; + + X509_Ptr key_cert(parseCertBlob(chain[i])); + X509_Ptr signing_cert; + if (i < chain.size() - 1) { + signing_cert = parseCertBlob(chain[i + 1]); + } else { + signing_cert = parseCertBlob(chain[i]); + } + if (!key_cert.get() || !signing_cert.get()) { + LOG(ERROR) << cert_data.str(); + return false; + } + + EVP_PKEY_Ptr signing_pubkey(X509_get_pubkey(signing_cert.get())); + if (!signing_pubkey.get()) { + LOG(ERROR) << cert_data.str(); + return false; + } + + if (!X509_verify(key_cert.get(), signing_pubkey.get())) { + // Handles the case of device-unique attestation chain which is not expected to be + // self-signed - b/191361618 + // For device-unique attestation chain `strict_issuer_check` is not set, so ignore the + // root certificate signature verification result and in all other cases return the + // error. + bool is_root_cert = (i == chain.size() - 1); + if (strict_issuer_check || !is_root_cert) { + LOG(ERROR) << "Verification of certificate " << i << " failed " + << "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL) + << '\n' + << cert_data.str(); + return false; + } + } + + string cert_issuer = x509NameToStr(X509_get_issuer_name(key_cert.get())); + string signer_subj = x509NameToStr(X509_get_subject_name(signing_cert.get())); + if (cert_issuer != signer_subj && strict_issuer_check) { + LOG(ERROR) << "Cert " << i << " has wrong issuer.\n" + << " Signer subject is " << signer_subj << " Issuer subject is " + << cert_issuer << endl + << cert_data.str(); + } + } + + // Dump cert data. + LOG(ERROR) << cert_data.str(); + return true; +} + +/* This function extracts a certificate from the certs_chain_buffer at the given + * offset. Each DER encoded certificate starts with TAG_SEQUENCE followed by the + * total length of the certificate. The length of the certificate is determined + * as per ASN.1 encoding rules for the length octets. + * + * @param certs_chain_buffer: buffer containing DER encoded X.509 certificates + * arranged sequentially. + * @data_size: Length of the DER encoded X.509 certificates buffer. + * @index: DER encoded X.509 certificates buffer offset. + * @cert: Encoded certificate to be extracted from buffer as outcome. + * @return: true on success, otherwise false. + */ +bool extractCertFromCertChainBuffer(uint8_t* certs_chain_buffer, int certs_chain_buffer_size, + int& index, certificate_t& cert) { + if (index >= certs_chain_buffer_size) { + return false; + } + + uint32_t length = 0; + std::vector<uint8_t> cert_bytes; + if (certs_chain_buffer[index] == TAG_SEQUENCE) { + // Short form. One octet. Bit 8 has value "0" and bits 7-1 give the length. + if (0 == (certs_chain_buffer[index + 1] & LENGTH_MASK)) { + length = (uint32_t)certs_chain_buffer[index]; + // Add SEQ and Length fields + length += 2; + } else { + // Long form. Two to 127 octets. Bit 8 of first octet has value "1" and + // bits 7-1 give the number of additional length octets. Second and following + // octets give the actual length. + int additionalBytes = certs_chain_buffer[index + 1] & LENGTH_VALUE_MASK; + if (additionalBytes == 0x01) { + length = certs_chain_buffer[index + 2]; + // Add SEQ and Length fields + length += 3; + } else if (additionalBytes == 0x02) { + length = (certs_chain_buffer[index + 2] << 8 | certs_chain_buffer[index + 3]); + // Add SEQ and Length fields + length += 4; + } else if (additionalBytes == 0x04) { + length = certs_chain_buffer[index + 2] << 24; + length |= certs_chain_buffer[index + 3] << 16; + length |= certs_chain_buffer[index + 4] << 8; + length |= certs_chain_buffer[index + 5]; + // Add SEQ and Length fields + length += 6; + } else { + // Length is larger than uint32_t max limit. + return false; + } + } + cert_bytes.insert(cert_bytes.end(), (certs_chain_buffer + index), + (certs_chain_buffer + index + length)); + index += length; + + for (int i = 0; i < cert_bytes.size(); i++) { + cert = std::move(cert_bytes); + } + } else { + // SEQUENCE TAG MISSING. + return false; + } + + return true; +} + +bool getCertificateChain(rust::Vec<rust::u8>& chainBuffer, std::vector<certificate_t>& certChain) { + uint8_t* data = chainBuffer.data(); + int index = 0; + int data_size = chainBuffer.size(); + + while (index < data_size) { + certificate_t cert; + if (!extractCertFromCertChainBuffer(data, data_size, index, cert)) { + return false; + } + certChain.push_back(std::move(cert)); + } + return true; +} + +bool validateCertChain(rust::Vec<rust::u8> cert_buf, uint32_t cert_len, bool strict_issuer_check) { + std::vector<certificate_t> cert_chain = std::vector<certificate_t>(); + if (cert_len <= 0) { + return false; + } + if (!getCertificateChain(cert_buf, cert_chain)) { + return false; + } + + std::stringstream cert_data; + for (int i = 0; i < cert_chain.size(); i++) { + cert_data << bin2hex(cert_chain[i]) << std::endl; + } + LOG(INFO) << cert_data.str() << "\n"; + + return ChainSignaturesAreValid(cert_chain, strict_issuer_check); +} + +/** + * Below mentioned key parameters are used to create authorization list of + * secure key. + * Algorithm: AES-256 + * Padding: PKCS7 + * Blockmode: ECB + * Purpose: Encrypt, Decrypt + */ +keymaster::AuthorizationSet build_wrapped_key_auth_list() { + return keymaster::AuthorizationSet(keymaster::AuthorizationSetBuilder() + .AesEncryptionKey(256) + .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_ECB) + .Authorization(keymaster::TAG_PADDING, KM_PAD_PKCS7) + .Authorization(keymaster::TAG_NO_AUTH_REQUIRED)); +} + +/** + * Creates ASN.1 DER-encoded data corresponding to `KeyDescription` schema as + * AAD. See `IKeyMintDevice.aidl` for documentation of the `KeyDescription` schema. + */ +CxxResult buildAsn1DerEncodedWrappedKeyDescription() { + CxxResult cxx_result{}; + keymaster_error_t error; + cxx_result.error = KM_ERROR_OK; + + keymaster::UniquePtr<TEST_KEY_DESCRIPTION, TEST_KEY_DESCRIPTION_Delete> key_description( + TEST_KEY_DESCRIPTION_new()); + if (!key_description.get()) { + cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; + return cxx_result; + } + + // Fill secure key authorizations. + keymaster::AuthorizationSet auth_list = build_wrapped_key_auth_list(); + error = build_auth_list(auth_list, key_description->key_params); + if (error != KM_ERROR_OK) { + cxx_result.error = error; + return cxx_result; + } + + // Fill secure key format. + if (!ASN1_INTEGER_set(key_description->key_format, KM_KEY_FORMAT_RAW)) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + // Perform ASN.1 DER encoding of KeyDescription. + int asn1_data_len = i2d_TEST_KEY_DESCRIPTION(key_description.get(), nullptr); + if (asn1_data_len < 0) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + std::vector<uint8_t> asn1_data(asn1_data_len, 0); + + if (!asn1_data.data()) { + cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; + return cxx_result; + } + + uint8_t* p = asn1_data.data(); + asn1_data_len = i2d_TEST_KEY_DESCRIPTION(key_description.get(), &p); + if (asn1_data_len < 0) { + cxx_result.error = keymaster::TranslateLastOpenSslError(); + return cxx_result; + } + + std::move(asn1_data.begin(), asn1_data.end(), std::back_inserter(cxx_result.data)); + + return cxx_result; +} + +/** + * Creates wrapped key material to import in ASN.1 DER-encoded data corresponding to + * `SecureKeyWrapper` schema. See `IKeyMintDevice.aidl` for documentation of the `SecureKeyWrapper` + * schema. + */ +CxxResult createWrappedKey(rust::Vec<rust::u8> encrypted_secure_key, + rust::Vec<rust::u8> encrypted_transport_key, rust::Vec<rust::u8> iv, + rust::Vec<rust::u8> tag) { + CxxResult cxx_result{}; + keymaster_error_t error; + cxx_result.error = false; + + uint8_t* enc_secure_key_data = encrypted_secure_key.data(); + int enc_secure_key_size = encrypted_secure_key.size(); + + uint8_t* iv_data = iv.data(); + int iv_size = iv.size(); + + uint8_t* tag_data = tag.data(); + int tag_size = tag.size(); + + uint8_t* enc_transport_key_data = encrypted_transport_key.data(); + int enc_transport_key_size = encrypted_transport_key.size(); + + keymaster::UniquePtr<TEST_SECURE_KEY_WRAPPER, TEST_SECURE_KEY_WRAPPER_Delete> sec_key_wrapper( + TEST_SECURE_KEY_WRAPPER_new()); + if (!sec_key_wrapper.get()) { + LOG(ERROR) << "createWrappedKey - Failed to allocate a memory"; + cxx_result.error = true; + return cxx_result; + } + + // Fill version = 0 + if (!ASN1_INTEGER_set(sec_key_wrapper->version, 0)) { + LOG(ERROR) << "createWrappedKey - Error while filling version: " + << keymaster::TranslateLastOpenSslError(); + cxx_result.error = true; + return cxx_result; + } + + // Fill encrypted transport key. + if (enc_transport_key_size && + !ASN1_OCTET_STRING_set(sec_key_wrapper->encrypted_transport_key, enc_transport_key_data, + enc_transport_key_size)) { + LOG(ERROR) << "createWrappedKey - Error while filling encrypted transport key: " + << keymaster::TranslateLastOpenSslError(); + cxx_result.error = true; + return cxx_result; + } + + // Fill encrypted secure key. + if (enc_secure_key_size && !ASN1_OCTET_STRING_set(sec_key_wrapper->encrypted_key, + enc_secure_key_data, enc_secure_key_size)) { + LOG(ERROR) << "createWrappedKey - Error while filling encrypted secure key: " + << keymaster::TranslateLastOpenSslError(); + cxx_result.error = true; + return cxx_result; + } + + // Fill secure key authorization list. + keymaster::AuthorizationSet auth_list = build_wrapped_key_auth_list(); + error = build_auth_list(auth_list, sec_key_wrapper->key_desc->key_params); + if (error != KM_ERROR_OK) { + cxx_result.error = true; + return cxx_result; + } + + // Fill secure key format. + if (!ASN1_INTEGER_set(sec_key_wrapper->key_desc->key_format, KM_KEY_FORMAT_RAW)) { + LOG(ERROR) << "createWrappedKey - Error while filling secure key format: " + << keymaster::TranslateLastOpenSslError(); + cxx_result.error = true; + return cxx_result; + } + + // Fill initialization vector used for encrypting secure key. + if (iv_size && + !ASN1_OCTET_STRING_set(sec_key_wrapper->initialization_vector, iv_data, iv_size)) { + LOG(ERROR) << "createWrappedKey - Error while filling IV: " + << keymaster::TranslateLastOpenSslError(); + cxx_result.error = true; + return cxx_result; + } + + // Fill GCM-tag, extracted during secure key encryption. + if (tag_size && !ASN1_OCTET_STRING_set(sec_key_wrapper->tag, tag_data, tag_size)) { + LOG(ERROR) << "createWrappedKey - Error while filling GCM-tag: " + << keymaster::TranslateLastOpenSslError(); + cxx_result.error = true; + return cxx_result; + } + + // ASN.1 DER-encoding of secure key wrapper. + int asn1_data_len = i2d_TEST_SECURE_KEY_WRAPPER(sec_key_wrapper.get(), nullptr); + if (asn1_data_len < 0) { + LOG(ERROR) << "createWrappedKey - Error while performing DER encode: " + << keymaster::TranslateLastOpenSslError(); + cxx_result.error = true; + return cxx_result; + } + std::vector<uint8_t> asn1_data(asn1_data_len, 0); + + if (!asn1_data.data()) { + LOG(ERROR) << "createWrappedKey - Failed to allocate a memory for asn1_data"; + cxx_result.error = true; + return cxx_result; + } + + uint8_t* p = asn1_data.data(); + asn1_data_len = i2d_TEST_SECURE_KEY_WRAPPER(sec_key_wrapper.get(), &p); + if (asn1_data_len < 0) { + cxx_result.error = true; + return cxx_result; + } + + std::move(asn1_data.begin(), asn1_data.end(), std::back_inserter(cxx_result.data)); + + return cxx_result; +} + +/** + * Perform EC/RSA sign operation using `EVP_PKEY`. + */ +bool performSignData(const char* data, size_t data_len, EVP_PKEY* pkey, unsigned char** signature, + size_t* signature_len) { + // Create the signing context + EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); + if (md_ctx == NULL) { + LOG(ERROR) << "Failed to create signing context"; + return false; + } + + // Initialize the signing operation + if (EVP_DigestSignInit(md_ctx, NULL, EVP_sha256(), NULL, pkey) != 1) { + LOG(ERROR) << "Failed to initialize signing operation"; + EVP_MD_CTX_free(md_ctx); + return false; + } + + // Sign the data + if (EVP_DigestSignUpdate(md_ctx, data, data_len) != 1) { + LOG(ERROR) << "Failed to sign data"; + EVP_MD_CTX_free(md_ctx); + return false; + } + + // Determine the length of the signature + if (EVP_DigestSignFinal(md_ctx, NULL, signature_len) != 1) { + LOG(ERROR) << "Failed to determine signature length"; + EVP_MD_CTX_free(md_ctx); + return false; + } + + // Allocate memory for the signature + *signature = (unsigned char*)malloc(*signature_len); + if (*signature == NULL) { + LOG(ERROR) << "Failed to allocate memory for the signature"; + EVP_MD_CTX_free(md_ctx); + return false; + } + + // Perform the final signing operation + if (EVP_DigestSignFinal(md_ctx, *signature, signature_len) != 1) { + LOG(ERROR) << "Failed to perform signing operation"; + free(*signature); + EVP_MD_CTX_free(md_ctx); + return false; + } + + EVP_MD_CTX_free(md_ctx); + return true; +} + +/** + * Perform EC/RSA verify operation using `EVP_PKEY`. + */ +int performVerifySignature(const char* data, size_t data_len, EVP_PKEY* pkey, + const unsigned char* signature, size_t signature_len) { + // Create the verification context + EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); + if (md_ctx == NULL) { + LOG(ERROR) << "Failed to create verification context"; + return false; + } + + // Initialize the verification operation + if (EVP_DigestVerifyInit(md_ctx, NULL, EVP_sha256(), NULL, pkey) != 1) { + LOG(ERROR) << "Failed to initialize verification operation"; + EVP_MD_CTX_free(md_ctx); + return false; + } + + // Verify the data + if (EVP_DigestVerifyUpdate(md_ctx, data, data_len) != 1) { + LOG(ERROR) << "Failed to verify data"; + EVP_MD_CTX_free(md_ctx); + return false; + } + + // Perform the verification operation + int ret = EVP_DigestVerifyFinal(md_ctx, signature, signature_len); + EVP_MD_CTX_free(md_ctx); + + return ret == 1; +} + +/** + * Extract the `EVP_PKEY` for the given KeyMint Key and perform Sign/Verify operations + * using extracted `EVP_PKEY`. + */ +bool performCryptoOpUsingKeystoreEngine(int64_t grant_id) { + const int KEY_ID_LEN = 20; + char key_id[KEY_ID_LEN] = ""; + snprintf(key_id, KEY_ID_LEN, "%" PRIx64, grant_id); + std::string str_key = std::string(keystore2_grant_id_prefix) + key_id; + bool result = false; + +#if defined(OPENSSL_IS_BORINGSSL) + EVP_PKEY* evp = EVP_PKEY_from_keystore(str_key.c_str()); + if (!evp) { + LOG(ERROR) << "Error while loading a key from keystore-engine"; + return false; + } + + int algo_type = EVP_PKEY_id(evp); + if (algo_type != EVP_PKEY_RSA && algo_type != EVP_PKEY_EC) { + LOG(ERROR) << "Unsupported Algorithm. Only RSA and EC are allowed."; + EVP_PKEY_free(evp); + return false; + } + + unsigned char* signature = NULL; + size_t signature_len = 0; + const char* INPUT_DATA = "MY MESSAGE FOR SIGN"; + size_t data_len = strlen(INPUT_DATA); + if (!performSignData(INPUT_DATA, data_len, evp, &signature, &signature_len)) { + LOG(ERROR) << "Failed to sign data"; + EVP_PKEY_free(evp); + return false; + } + + result = performVerifySignature(INPUT_DATA, data_len, evp, signature, signature_len); + if (!result) { + LOG(ERROR) << "Signature verification failed"; + } else { + LOG(INFO) << "Signature verification success"; + } + + free(signature); + EVP_PKEY_free(evp); +#endif + return result; +} + +CxxResult getValueFromAttestRecord(rust::Vec<rust::u8> cert_buf, int32_t tag, + int32_t expected_sec_level) { + CxxResult cxx_result{}; + cxx_result.error = false; + + uint8_t* cert_data = cert_buf.data(); + int cert_data_size = cert_buf.size(); + + std::vector<uint8_t> cert_bytes; + cert_bytes.insert(cert_bytes.end(), cert_data, (cert_data + cert_data_size)); + + X509_Ptr cert(parseCertBlob(cert_bytes)); + if (!cert.get()) { + LOG(ERROR) << "getValueFromAttestRecord - Failed to allocate a memory for certificate"; + cxx_result.error = true; + return cxx_result; + } + + ASN1_OCTET_STRING* attest_rec = getAttestationRecord(cert.get()); + if (!attest_rec) { + LOG(ERROR) << "getValueFromAttestRecord - Error in getAttestationRecord: " + << keymaster::TranslateLastOpenSslError(); + cxx_result.error = true; + 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<uint8_t> att_challenge; + std::vector<uint8_t> att_unique_id; + std::vector<uint8_t> att_app_id; + + int32_t error = + static_cast<int32_t>(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)); + if (error) { + LOG(ERROR) << "getValueFromAttestRecord - Error in parse_attestation_record: " << error; + cxx_result.error = true; + return cxx_result; + } + + aidl::android::hardware::security::keymint::Tag auth_tag = + static_cast<aidl::android::hardware::security::keymint::Tag>(tag); + aidl::android::hardware::security::keymint::SecurityLevel tag_security_level = + static_cast<aidl::android::hardware::security::keymint::SecurityLevel>(expected_sec_level); + + 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) { + LOG(ERROR) << "getValueFromAttestRecord - Attestation-application-id missing."; + cxx_result.error = true; + return cxx_result; + } + aidl::android::hardware::security::keymint::KeyParameter param = att_sw_enforced[pos]; + std::vector<uint8_t> val = + param.value.get<aidl::android::hardware::security::keymint::KeyParameterValue::blob>(); + 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) { + LOG(ERROR) << "getValueFromAttestRecord - Attestation-challenge missing."; + cxx_result.error = true; + 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) { + LOG(ERROR) << "getValueFromAttestRecord - unsupported tag - UNIQUE_ID."; + cxx_result.error = true; + return cxx_result; + } + std::move(att_unique_id.begin(), att_unique_id.end(), std::back_inserter(cxx_result.data)); + return cxx_result; + } + + if (auth_tag == aidl::android::hardware::security::keymint::Tag::USAGE_COUNT_LIMIT) { + aidl::android::hardware::security::keymint::KeyParameter param; + int pos = att_hw_enforced.find(auth_tag); + if (tag_security_level == + aidl::android::hardware::security::keymint::SecurityLevel::SOFTWARE || + tag_security_level == + aidl::android::hardware::security::keymint::SecurityLevel::KEYSTORE) { + pos = att_sw_enforced.find(auth_tag); + if (pos == -1) { + LOG(ERROR) << "USAGE_COUNT_LIMIT not found in software enforced auth list"; + cxx_result.error = KM_ERROR_INVALID_TAG; + return cxx_result; + } + param = att_sw_enforced[pos]; + } else { + pos = att_hw_enforced.find(auth_tag); + if (pos == -1) { + LOG(ERROR) << "USAGE_COUNT_LIMIT not found in hardware enforced auth list"; + cxx_result.error = KM_ERROR_INVALID_TAG; + return cxx_result; + } + param = att_hw_enforced[pos]; + } + std::string val = std::to_string( + param.value + .get<aidl::android::hardware::security::keymint::KeyParameterValue::integer>()); + std::move(val.begin(), val.end(), std::back_inserter(cxx_result.data)); + return cxx_result; + } + + int pos = att_hw_enforced.find(auth_tag); + if (pos == -1) { + LOG(ERROR) << "getValueFromAttestRecord - unsupported tag."; + cxx_result.error = true; + return cxx_result; + } + aidl::android::hardware::security::keymint::KeyParameter param = att_hw_enforced[pos]; + std::vector<uint8_t> val = + param.value.get<aidl::android::hardware::security::keymint::KeyParameterValue::blob>(); + std::move(val.begin(), val.end(), std::back_inserter(cxx_result.data)); + return cxx_result; +} + +uint32_t getOsVersion() { + return aidl::android::hardware::security::keymint::getOsVersion(); +} + +uint32_t getOsPatchlevel() { + return aidl::android::hardware::security::keymint::getOsPatchlevel(); +} + +uint32_t getVendorPatchlevel() { + return aidl::android::hardware::security::keymint::getVendorPatchlevel(); +} diff --git a/keystore2/test_utils/ffi_test_utils.hpp b/keystore2/test_utils/ffi_test_utils.hpp new file mode 100644 index 00000000..c4db1ba4 --- /dev/null +++ b/keystore2/test_utils/ffi_test_utils.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "ffi_test_utils.rs.h" +#include "rust/cxx.h" + +bool validateCertChain(rust::Vec<rust::u8> cert_buf, uint32_t cert_len, bool strict_issuer_check); +CxxResult createWrappedKey(rust::Vec<rust::u8> encrypted_secure_key, + rust::Vec<rust::u8> encrypted_transport_key, rust::Vec<rust::u8> iv, + rust::Vec<rust::u8> tag); +CxxResult buildAsn1DerEncodedWrappedKeyDescription(); +bool performCryptoOpUsingKeystoreEngine(int64_t grant_id); +CxxResult getValueFromAttestRecord(rust::Vec<rust::u8> cert_buf, int32_t tag, + int32_t expected_sec_level); +uint32_t getOsVersion(); +uint32_t getOsPatchlevel(); +uint32_t getVendorPatchlevel(); diff --git a/keystore2/tests/ffi_test_utils.rs b/keystore2/test_utils/ffi_test_utils.rs index 066d4a1e..1ccdcc81 100644 --- a/keystore2/tests/ffi_test_utils.rs +++ b/keystore2/test_utils/ffi_test_utils.rs @@ -12,13 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use keystore2_test_utils::key_generations::Error; +//! This module implements helper methods to access the functionalities implemented in CPP. + +use crate::key_generations::Error; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + SecurityLevel::SecurityLevel, Tag::Tag, +}; #[cxx::bridge] mod ffi { struct CxxResult { data: Vec<u8>, - error: i32, + error: bool, } unsafe extern "C++" { @@ -31,20 +36,42 @@ mod ffi { tag: Vec<u8>, ) -> CxxResult; fn buildAsn1DerEncodedWrappedKeyDescription() -> CxxResult; + fn performCryptoOpUsingKeystoreEngine(grant_id: i64) -> bool; + fn getValueFromAttestRecord( + cert_buf: Vec<u8>, + tag: i32, + expected_sec_level: i32, + ) -> CxxResult; + fn getOsVersion() -> u32; + fn getOsPatchlevel() -> u32; + fn getVendorPatchlevel() -> u32; } } /// Validate given certificate chain. pub fn validate_certchain(cert_buf: &[u8]) -> Result<bool, Error> { - if ffi::validateCertChain(cert_buf.to_vec(), cert_buf.len().try_into().unwrap(), true) { + validate_certchain_with_strict_issuer_check(cert_buf, true) +} + +/// Validate given certificate chain with an option to validate the issuer. +pub fn validate_certchain_with_strict_issuer_check( + cert_buf: &[u8], + strict_issuer_check: bool, +) -> Result<bool, Error> { + if ffi::validateCertChain( + cert_buf.to_vec(), + cert_buf.len().try_into().unwrap(), + strict_issuer_check, + ) { return Ok(true); } Err(Error::ValidateCertChainFailed) } +/// Collect the result from CxxResult into a Rust supported structure. fn get_result(result: ffi::CxxResult) -> Result<Vec<u8>, Error> { - if result.error == 0 && !result.data.is_empty() { + if !result.error && !result.data.is_empty() { Ok(result.data) } else { Err(Error::DerEncodeFailed) @@ -78,3 +105,40 @@ pub fn create_wrapped_key( pub fn create_wrapped_key_additional_auth_data() -> Result<Vec<u8>, Error> { get_result(ffi::buildAsn1DerEncodedWrappedKeyDescription()) } + +/// Performs crypto operation using Keystore-Engine APIs. +pub fn perform_crypto_op_using_keystore_engine(grant_id: i64) -> Result<bool, Error> { + if ffi::performCryptoOpUsingKeystoreEngine(grant_id) { + return Ok(true); + } + + Err(Error::Keystore2EngineOpFailed) +} + +/// Get the value of the given `Tag` from attestation record. +pub fn get_value_from_attest_record( + cert_buf: &[u8], + tag: Tag, + expected_sec_level: SecurityLevel, +) -> Result<Vec<u8>, Error> { + let result = ffi::getValueFromAttestRecord(cert_buf.to_vec(), tag.0, expected_sec_level.0); + if !result.error && !result.data.is_empty() { + return Ok(result.data); + } + Err(Error::AttestRecordGetValueFailed) +} + +/// Get OS Version +pub fn get_os_version() -> u32 { + ffi::getOsVersion() +} + +/// Get OS Patch Level +pub fn get_os_patchlevel() -> u32 { + ffi::getOsPatchlevel() +} + +/// Get vendor Patch Level +pub fn get_vendor_patchlevel() -> u32 { + ffi::getVendorPatchlevel() +} diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs index e4c4968f..a733be39 100644 --- a/keystore2/test_utils/key_generations.rs +++ b/keystore2/test_utils/key_generations.rs @@ -15,15 +15,22 @@ //! This module implements test utils to generate various types of keys. use anyhow::Result; +use core::ops::Range; +use nix::unistd::getuid; +use std::collections::HashSet; +use std::fmt::Write; + +use binder::ThreadState; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, - KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, Tag::Tag, + KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, Tag::Tag, }; use android_system_keystore2::aidl::android::system::keystore2::{ - AuthenticatorSpec::AuthenticatorSpec, Authorization::Authorization, Domain::Domain, + AuthenticatorSpec::AuthenticatorSpec, Authorization::Authorization, + CreateOperationResponse::CreateOperationResponse, Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, ResponseCode::ResponseCode, }; @@ -31,6 +38,11 @@ use android_system_keystore2::aidl::android::system::keystore2::{ use crate::authorizations::AuthSetBuilder; use android_system_keystore2::binder::{ExceptionCode, Result as BinderResult}; +use crate::ffi_test_utils::{ + get_os_patchlevel, get_os_version, get_value_from_attest_record, get_vendor_patchlevel, + validate_certchain_with_strict_issuer_check, +}; + /// Shell namespace. pub const SELINUX_SHELL_NAMESPACE: i64 = 1; /// Vold namespace. @@ -42,6 +54,52 @@ pub const TARGET_SU_CTX: &str = "u:r:su:s0"; /// Vold context pub const TARGET_VOLD_CTX: &str = "u:r:vold:s0"; +/// Allowed tags in generated/imported key authorizations. +/// See hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl for the +/// list feature tags. +/// Note: This list need to be updated whenever a new Tag is introduced and is expected to be added +/// in key authorizations. +pub const ALLOWED_TAGS_IN_KEY_AUTHS: &[Tag] = &[ + Tag::ACTIVE_DATETIME, + Tag::ALGORITHM, + Tag::ALLOW_WHILE_ON_BODY, + Tag::AUTH_TIMEOUT, + Tag::BLOCK_MODE, + Tag::BOOTLOADER_ONLY, + Tag::BOOT_PATCHLEVEL, + Tag::CALLER_NONCE, + Tag::CREATION_DATETIME, + Tag::DIGEST, + Tag::EARLY_BOOT_ONLY, + Tag::EC_CURVE, + Tag::IDENTITY_CREDENTIAL_KEY, + Tag::INCLUDE_UNIQUE_ID, + Tag::KEY_SIZE, + Tag::MAX_BOOT_LEVEL, + Tag::MAX_USES_PER_BOOT, + Tag::MIN_MAC_LENGTH, + Tag::NO_AUTH_REQUIRED, + Tag::ORIGIN, + Tag::ORIGINATION_EXPIRE_DATETIME, + Tag::OS_PATCHLEVEL, + Tag::OS_VERSION, + Tag::PADDING, + Tag::PURPOSE, + Tag::ROLLBACK_RESISTANCE, + Tag::RSA_OAEP_MGF_DIGEST, + Tag::RSA_PUBLIC_EXPONENT, + Tag::STORAGE_KEY, + Tag::TRUSTED_CONFIRMATION_REQUIRED, + Tag::TRUSTED_USER_PRESENCE_REQUIRED, + Tag::UNLOCKED_DEVICE_REQUIRED, + Tag::USAGE_COUNT_LIMIT, + Tag::USAGE_EXPIRE_DATETIME, + Tag::USER_AUTH_TYPE, + Tag::USER_ID, + Tag::USER_SECURE_ID, + Tag::VENDOR_PATCHLEVEL, +]; + /// Key parameters to generate a key. pub struct KeyParams { /// Key Size. @@ -299,6 +357,15 @@ pub enum Error { /// Error code to indicate error in ASN.1 DER-encoded data creation. #[error("Failed to create and encode ASN.1 data.")] DerEncodeFailed, + /// 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. @@ -324,6 +391,140 @@ pub fn map_ks_error<T>(r: BinderResult<T>) -> Result<T, Error> { }) } +/// 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") + .expect("Could not check for declared keymint interface") +} + +/// Verify that given key param is listed in given authorizations list. +pub fn check_key_param(authorizations: &[Authorization], key_param: &KeyParameter) -> bool { + authorizations.iter().any(|auth| &auth.keyParameter == key_param) +} + +/// Verify the given key authorizations with the expected authorizations. +pub fn check_key_authorizations( + authorizations: &[Authorization], + expected_params: &[KeyParameter], + expected_key_origin: KeyOrigin, +) { + // Make sure key authorizations contains only `ALLOWED_TAGS_IN_KEY_AUTHS` + authorizations.iter().all(|auth| { + // Ignore `INVALID` tag if the backend is Keymaster and not KeyMint. + // Keymaster allows INVALID tag for unsupported key parameters. + if !has_default_keymint() && auth.keyParameter.tag == Tag::INVALID { + return true; + } + assert!( + ALLOWED_TAGS_IN_KEY_AUTHS.contains(&auth.keyParameter.tag), + "key authorization is not allowed: {:#?}", + auth.keyParameter + ); + true + }); + + //Check allowed-expected-key-parameters are present in given key authorizations list. + expected_params.iter().all(|key_param| { + // `INCLUDE_UNIQUE_ID` is not strictly expected to be in key authorizations but has been + // put there by some implementations so cope with that. + if key_param.tag == Tag::INCLUDE_UNIQUE_ID + && !authorizations.iter().any(|auth| auth.keyParameter.tag == key_param.tag) + { + return true; + } + + // Ignore below parameters if the backend is Keymaster and not KeyMint. + // Keymaster does not support these parameters. These key parameters are introduced in + // KeyMint1.0. + if !has_default_keymint() { + if matches!(key_param.tag, Tag::RSA_OAEP_MGF_DIGEST | Tag::USAGE_COUNT_LIMIT) { + return true; + } + if key_param.tag == Tag::PURPOSE + && key_param.value == KeyParameterValue::KeyPurpose(KeyPurpose::ATTEST_KEY) + { + return true; + } + } + + if ALLOWED_TAGS_IN_KEY_AUTHS.contains(&key_param.tag) { + assert!( + check_key_param(authorizations, key_param), + "Key parameter not found: {:#?}", + key_param + ); + } + true + }); + + check_common_auths(authorizations, expected_key_origin); +} + +/// Verify common key authorizations. +fn check_common_auths(authorizations: &[Authorization], expected_key_origin: KeyOrigin) { + assert!(check_key_param( + authorizations, + &KeyParameter { + tag: Tag::OS_VERSION, + value: KeyParameterValue::Integer(get_os_version().try_into().unwrap()) + } + )); + assert!(check_key_param( + authorizations, + &KeyParameter { + tag: Tag::OS_PATCHLEVEL, + value: KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap()) + } + )); + + // Access denied for finding vendor-patch-level ("ro.vendor.build.security_patch") property + // in a test running with `untrusted_app` context. Keeping this check to verify + // vendor-patch-level in tests running with `su` context. + if getuid().is_root() { + assert!(check_key_param( + authorizations, + &KeyParameter { + tag: Tag::VENDOR_PATCHLEVEL, + value: KeyParameterValue::Integer(get_vendor_patchlevel().try_into().unwrap()) + } + )); + } + assert!(check_key_param( + authorizations, + &KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(expected_key_origin) } + )); + assert!(check_key_param( + authorizations, + &KeyParameter { + tag: Tag::USER_ID, + value: KeyParameterValue::Integer( + rustutils::users::multiuser_get_user_id(ThreadState::get_calling_uid()) + .try_into() + .unwrap() + ) + } + )); + + if has_default_keymint() { + assert!(authorizations + .iter() + .map(|auth| &auth.keyParameter) + .any(|key_param| key_param.tag == Tag::CREATION_DATETIME)); + } +} + +/// Get the key `Authorization` for the given auth `Tag`. +pub fn get_key_auth(authorizations: &[Authorization], tag: Tag) -> Option<&Authorization> { + let auths: Vec<&Authorization> = + authorizations.iter().filter(|auth| auth.keyParameter.tag == tag).collect(); + + if !auths.is_empty() { + Some(auths[0]) + } else { + None + } +} + /// Generate EC Key using given security level and domain with below key parameters and /// optionally allow the generated key to be attested with factory provisioned attest key using /// given challenge and application id - @@ -367,6 +568,11 @@ pub fn generate_ec_p256_signing_key( assert!(key_metadata.key.blob.is_some()); } + check_key_authorizations( + &key_metadata.authorizations, + &gen_params, + KeyOrigin::GENERATED, + ); Ok(key_metadata) } Err(e) => Err(e), @@ -409,6 +615,7 @@ pub fn generate_ec_key( } else { assert!(key_metadata.key.blob.is_none()); } + check_key_authorizations(&key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); Ok(key_metadata) } @@ -470,6 +677,19 @@ pub fn generate_rsa_key( || key_metadata.key.blob.is_none() ); + check_key_authorizations(&key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); + // If `RSA_OAEP_MGF_DIGEST` tag is not mentioned explicitly while generating/importing a key, + // then make sure `RSA_OAEP_MGF_DIGEST` tag with default value (SHA1) must not be included in + // key authorization list. + if key_params.mgf_digest.is_none() { + assert!(!check_key_param( + &key_metadata.authorizations, + &KeyParameter { + tag: Tag::RSA_OAEP_MGF_DIGEST, + value: KeyParameterValue::Digest(Digest::SHA1) + } + )); + } Ok(key_metadata) } @@ -514,6 +734,7 @@ pub fn generate_sym_key( // Should not have an attestation record. assert!(key_metadata.certificateChain.is_none()); + check_key_authorizations(&key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); Ok(key_metadata) } @@ -553,6 +774,7 @@ pub fn generate_hmac_key( // Should not have an attestation record. assert!(key_metadata.certificateChain.is_none()); + check_key_authorizations(&key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); Ok(key_metadata) } @@ -637,6 +859,11 @@ pub fn generate_ec_attestation_key( // Should have an attestation record. assert!(attestation_key_metadata.certificateChain.is_some()); + check_key_authorizations( + &attestation_key_metadata.authorizations, + &gen_params, + KeyOrigin::GENERATED, + ); Ok(attestation_key_metadata) } @@ -671,20 +898,10 @@ pub fn generate_ec_256_attested_key( // Shouldn't have an attestation record. assert!(ec_key_metadata.certificateChain.is_none()); + check_key_authorizations(&ec_key_metadata.authorizations, &ec_gen_params, KeyOrigin::GENERATED); Ok(ec_key_metadata) } -/// Verify that given key param is listed in given authorizations list. -pub fn check_key_param(authorizations: &[Authorization], key_param: KeyParameter) -> bool { - for authrization in authorizations { - if authrization.keyParameter == key_param { - return true; - } - } - - false -} - /// Imports above defined RSA key - `RSA_2048_KEY` and validates imported key parameters. pub fn import_rsa_2048_key( sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, @@ -706,24 +923,27 @@ pub fn import_rsa_2048_key( assert!(key_metadata.certificate.is_some()); assert!(key_metadata.certificateChain.is_none()); + check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + + // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::RSA) } + &KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::RSA) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(2048) } + &KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(2048) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } + &KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { + &KeyParameter { tag: Tag::RSA_PUBLIC_EXPONENT, value: KeyParameterValue::LongInteger(65537) } @@ -731,7 +951,7 @@ pub fn import_rsa_2048_key( assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { + &KeyParameter { tag: Tag::PADDING, value: KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS) } @@ -739,7 +959,7 @@ pub fn import_rsa_2048_key( assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + &KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } )); Ok(key_metadata) @@ -766,23 +986,26 @@ pub fn import_ec_p_256_key( assert!(key_metadata.certificate.is_some()); assert!(key_metadata.certificateChain.is_none()); + check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + + // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::EC) } + &KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::EC) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::EC_CURVE, value: KeyParameterValue::EcCurve(EcCurve::P_256) } + &KeyParameter { tag: Tag::EC_CURVE, value: KeyParameterValue::EcCurve(EcCurve::P_256) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } + &KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + &KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } )); Ok(key_metadata) @@ -815,28 +1038,31 @@ pub fn import_aes_key( AES_KEY, )?; + check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + + // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::AES) } + &KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::AES) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) } + &KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { + &KeyParameter { tag: Tag::PADDING, value: KeyParameterValue::PaddingMode(PaddingMode::PKCS7) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(BlockMode::ECB) } + &KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(BlockMode::ECB) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + &KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } )); Ok(key_metadata) @@ -871,31 +1097,34 @@ pub fn import_3des_key( TRIPLE_DES_KEY, )?; + check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + + // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { + &KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::TRIPLE_DES) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(168) } + &KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(168) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { + &KeyParameter { tag: Tag::PADDING, value: KeyParameterValue::PaddingMode(PaddingMode::PKCS7) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(BlockMode::ECB) } + &KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(BlockMode::ECB) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + &KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } )); Ok(key_metadata) @@ -928,21 +1157,24 @@ pub fn import_hmac_key( HMAC_KEY, )?; + check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + + // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::HMAC) } + &KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::HMAC) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) } + &KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } + &KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) } )); assert!(check_key_param( &key_metadata.authorizations, - KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } + &KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) } )); Ok(key_metadata) @@ -1077,8 +1309,184 @@ pub fn generate_ec_agree_key( assert!(key_metadata.key.blob.is_some()); } + check_key_authorizations( + &key_metadata.authorizations, + &gen_params, + KeyOrigin::GENERATED, + ); Ok(key_metadata) } Err(e) => Err(e), } } + +/// Helper method to import AES keys `total_count` of times. +pub fn import_aes_keys( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias_prefix: String, + total_count: Range<i32>, +) -> binder::Result<HashSet<String>> { + let mut imported_key_aliases = HashSet::new(); + + // Import Total number of keys with given alias prefix. + for count in total_count { + let mut alias = String::new(); + write!(alias, "{}_{}", alias_prefix, count).unwrap(); + imported_key_aliases.insert(alias.clone()); + + import_aes_key(sec_level, Domain::APP, -1, Some(alias))?; + } + + 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<dyn IKeystoreSecurityLevel>, + algorithm: Algorithm, + alias: Option<String>, + att_challenge: &[u8], + attest_key: &KeyDescriptor, + attest_id: Tag, + value: Vec<u8>, +) -> binder::Result<KeyMetadata> { + 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", + ) +} + +/// Generate Key and validate key characteristics. +pub fn generate_key( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + gen_params: &AuthSetBuilder, + alias: &str, +) -> binder::Result<KeyMetadata> { + let key_metadata = sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + gen_params, + 0, + b"entropy", + )?; + + if gen_params.iter().any(|kp| { + matches!( + kp.value, + KeyParameterValue::Algorithm(Algorithm::RSA) + | KeyParameterValue::Algorithm(Algorithm::EC) + ) + }) { + assert!(key_metadata.certificate.is_some()); + if gen_params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) { + assert!(key_metadata.certificateChain.is_some()); + let mut cert_chain: Vec<u8> = Vec::new(); + cert_chain.extend(key_metadata.certificate.as_ref().unwrap()); + cert_chain.extend(key_metadata.certificateChain.as_ref().unwrap()); + let strict_issuer_check = + !(gen_params.iter().any(|kp| kp.tag == Tag::DEVICE_UNIQUE_ATTESTATION)); + validate_certchain_with_strict_issuer_check(&cert_chain, strict_issuer_check) + .expect("Error while validating cert chain"); + } + + if let Some(challenge_param) = + gen_params.iter().find(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) + { + if let KeyParameterValue::Blob(val) = &challenge_param.value { + let att_challenge = get_value_from_attest_record( + key_metadata.certificate.as_ref().unwrap(), + challenge_param.tag, + key_metadata.keySecurityLevel, + ) + .expect("Attestation challenge verification failed."); + assert_eq!(&att_challenge, val); + } + + let att_app_id = get_value_from_attest_record( + key_metadata.certificate.as_ref().unwrap(), + Tag::ATTESTATION_APPLICATION_ID, + SecurityLevel::KEYSTORE, + ) + .expect("Attestation application id verification failed."); + assert!(!att_app_id.is_empty()); + } + } + check_key_authorizations(&key_metadata.authorizations, gen_params, KeyOrigin::GENERATED); + + Ok(key_metadata) +} + +/// Generate a key using given authorizations and create an operation using the generated key. +pub fn create_key_and_operation( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + gen_params: &AuthSetBuilder, + op_params: &AuthSetBuilder, + alias: &str, +) -> binder::Result<CreateOperationResponse> { + let key_metadata = generate_key(sec_level, gen_params, alias)?; + + sec_level.createOperation(&key_metadata.key, op_params, false) +} diff --git a/keystore2/test_utils/lib.rs b/keystore2/test_utils/lib.rs index c63bfacc..8394ca1c 100644 --- a/keystore2/test_utils/lib.rs +++ b/keystore2/test_utils/lib.rs @@ -20,12 +20,15 @@ use std::path::{Path, PathBuf}; use std::{env::temp_dir, ops::Deref}; use android_system_keystore2::aidl::android::system::keystore2::IKeystoreService::IKeystoreService; +use android_security_authorization::aidl::android::security::authorization::IKeystoreAuthorization::IKeystoreAuthorization; pub mod authorizations; +pub mod ffi_test_utils; pub mod key_generations; pub mod run_as; static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default"; +static AUTH_SERVICE_NAME: &str = "android.security.authorization"; /// Represents the lifecycle of a temporary directory for testing. #[derive(Debug)] @@ -115,3 +118,8 @@ impl Deref for PathBuilder { pub fn get_keystore_service() -> binder::Strong<dyn IKeystoreService> { binder::get_interface(KS2_SERVICE_NAME).unwrap() } + +/// Get Keystore auth service. +pub fn get_keystore_auth_service() -> binder::Strong<dyn IKeystoreAuthorization> { + binder::get_interface(AUTH_SERVICE_NAME).unwrap() +} diff --git a/keystore2/test_utils/run_as.rs b/keystore2/test_utils/run_as.rs index 2485ab57..d39d0697 100644 --- a/keystore2/test_utils/run_as.rs +++ b/keystore2/test_utils/run_as.rs @@ -29,13 +29,14 @@ use keystore2_selinux as selinux; use nix::sys::wait::{waitpid, WaitStatus}; use nix::unistd::{ - close, fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write, - ForkResult, Gid, Pid, Uid, + fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write, ForkResult, Gid, + Pid, Uid, }; use serde::{de::DeserializeOwned, Serialize}; use std::io::{Read, Write}; use std::marker::PhantomData; -use std::os::unix::io::RawFd; +use std::os::fd::AsRawFd; +use std::os::fd::OwnedFd; fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) { setgid(gid).expect("Failed to set GID. This test might need more privileges."); @@ -48,35 +49,23 @@ fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) { /// PipeReader is a simple wrapper around raw pipe file descriptors. /// It takes ownership of the file descriptor and closes it on drop. It provides `read_all`, which /// reads from the pipe into an expending vector, until no more data can be read. -struct PipeReader(RawFd); +struct PipeReader(OwnedFd); impl Read for PipeReader { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - let bytes = nix_read(self.0, buf)?; + let bytes = nix_read(self.0.as_raw_fd(), buf)?; Ok(bytes) } } -impl Drop for PipeReader { - fn drop(&mut self) { - close(self.0).expect("Failed to close reader pipe fd."); - } -} - /// PipeWriter is a simple wrapper around raw pipe file descriptors. /// It takes ownership of the file descriptor and closes it on drop. It provides `write`, which /// writes the given buffer into the pipe, returning the number of bytes written. -struct PipeWriter(RawFd); - -impl Drop for PipeWriter { - fn drop(&mut self) { - close(self.0).expect("Failed to close writer pipe fd."); - } -} +struct PipeWriter(OwnedFd); impl Write for PipeWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { - let written = nix_write(self.0, buf)?; + let written = nix_write(&self.0, buf)?; Ok(written) } @@ -255,7 +244,9 @@ where let (response_reader, mut response_writer) = pipe_channel().expect("Failed to create cmd pipe."); - match fork() { + // SAFETY: Our caller guarantees that the process only has a single thread, so calling + // non-async-signal-safe functions in the child is in fact safe. + match unsafe { fork() } { Ok(ForkResult::Parent { child, .. }) => { drop(response_writer); drop(cmd_reader); @@ -314,7 +305,9 @@ where selinux::Context::new(se_context).expect("Unable to construct selinux::Context."); let (mut reader, mut writer) = pipe_channel::<R>().expect("Failed to create pipe."); - match fork() { + // SAFETY: Our caller guarantees that the process only has a single thread, so calling + // non-async-signal-safe functions in the child is in fact safe. + match unsafe { fork() } { Ok(ForkResult::Parent { child, .. }) => { drop(writer); let status = waitpid(child, None).expect("Failed while waiting for child."); diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp index 78dd2d72..01ea7465 100644 --- a/keystore2/tests/Android.bp +++ b/keystore2/tests/Android.bp @@ -23,6 +23,7 @@ package { rust_test { name: "keystore2_client_tests", + compile_multilib: "first", defaults: [ "keymint_use_latest_hal_aidl_rust", "keystore2_use_latest_aidl_rust", @@ -30,71 +31,21 @@ rust_test { srcs: ["keystore2_client_tests.rs"], test_suites: [ "general-tests", + "vts", ], test_config: "AndroidTest.xml", rustlibs: [ - "librustutils", + "android.hardware.security.secureclock-V1-rust", + "android.security.authorization-rust", + "libaconfig_android_hardware_biometrics_rust", + "libbinder_rs", "libkeystore2_test_utils", - "packagemanager_aidl-rust", "libnix", - "libanyhow", - "libbinder_rs", - "liblazy_static", - "liblibc", - "libserde", - "libthiserror", - "libcxx", "libopenssl", - ], - static_libs: [ - "libkeystore2_ffi_test_utils", - "libgtest", - "libkeymint_vts_test_utils", - ], - shared_libs: [ - "libcrypto", - "libkeymaster_portable", - "libkeymaster_messages", - "libcppbor_external", + "librustutils", + "libserde", + "packagemanager_aidl-rust", ], require_root: true, } - -cc_library_static { - name: "libkeystore2_ffi_test_utils", - srcs: ["ffi_test_utils.cpp"], - defaults: [ - "keymint_vts_defaults", - "hidl_defaults", - ], - generated_headers: [ - "cxx-bridge-header", - "libkeystore2_ffi_test_utils_bridge_header", - ], - generated_sources: ["libkeystore2_ffi_test_utils_bridge_code"], - static_libs: [ - "libkeymint_vts_test_utils", - ], - shared_libs: [ - "libkeymaster_portable", - "libkeymaster_messages", - "libcppbor_external", - ], -} - -genrule { - name: "libkeystore2_ffi_test_utils_bridge_code", - tools: ["cxxbridge"], - cmd: "$(location cxxbridge) $(in) >> $(out)", - srcs: ["ffi_test_utils.rs"], - out: ["libkeystore2_test_utils_cxx_generated.cc"], -} - -genrule { - name: "libkeystore2_ffi_test_utils_bridge_header", - tools: ["cxxbridge"], - cmd: "$(location cxxbridge) $(in) --header >> $(out)", - srcs: ["ffi_test_utils.rs"], - out: ["ffi_test_utils.rs.h"], -} diff --git a/keystore2/tests/AndroidTest.xml b/keystore2/tests/AndroidTest.xml index 7db36f7e..dde18a9b 100644 --- a/keystore2/tests/AndroidTest.xml +++ b/keystore2/tests/AndroidTest.xml @@ -14,6 +14,7 @@ limitations under the License. --> <configuration description="Config to run keystore2_client_tests device tests."> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> </target_preparer> diff --git a/keystore2/tests/ffi_test_utils.cpp b/keystore2/tests/ffi_test_utils.cpp deleted file mode 100644 index de20d838..00000000 --- a/keystore2/tests/ffi_test_utils.cpp +++ /dev/null @@ -1,366 +0,0 @@ -#include "ffi_test_utils.hpp" - -#include <iostream> - -#include <KeyMintAidlTestBase.h> -#include <aidl/android/hardware/security/keymint/ErrorCode.h> -#include <keymaster/UniquePtr.h> - -#include <memory> -#include <vector> - -#include <hardware/keymaster_defs.h> -#include <keymaster/android_keymaster_utils.h> -#include <keymaster/keymaster_tags.h> - -#include <keymaster/km_openssl/attestation_record.h> -#include <keymaster/km_openssl/openssl_err.h> -#include <keymaster/km_openssl/openssl_utils.h> -#include <openssl/asn1t.h> - -using aidl::android::hardware::security::keymint::ErrorCode; - -#define TAG_SEQUENCE 0x30 -#define LENGTH_MASK 0x80 -#define LENGTH_VALUE_MASK 0x7F - -/** - * ASN.1 structure for `KeyDescription` Schema. - * See `IKeyMintDevice.aidl` for documentation of the `KeyDescription` schema. - * KeyDescription ::= SEQUENCE( - * keyFormat INTEGER, # Values from KeyFormat enum. - * keyParams AuthorizationList, - * ) - */ -typedef struct key_description { - ASN1_INTEGER* key_format; - keymaster::KM_AUTH_LIST* key_params; -} TEST_KEY_DESCRIPTION; - -ASN1_SEQUENCE(TEST_KEY_DESCRIPTION) = { - ASN1_SIMPLE(TEST_KEY_DESCRIPTION, key_format, ASN1_INTEGER), - ASN1_SIMPLE(TEST_KEY_DESCRIPTION, key_params, keymaster::KM_AUTH_LIST), -} ASN1_SEQUENCE_END(TEST_KEY_DESCRIPTION); -DECLARE_ASN1_FUNCTIONS(TEST_KEY_DESCRIPTION); - -/** - * ASN.1 structure for `SecureKeyWrapper` Schema. - * See `IKeyMintDevice.aidl` for documentation of the `SecureKeyWrapper` schema. - * SecureKeyWrapper ::= SEQUENCE( - * version INTEGER, # Contains value 0 - * encryptedTransportKey OCTET_STRING, - * initializationVector OCTET_STRING, - * keyDescription KeyDescription, - * encryptedKey OCTET_STRING, - * tag OCTET_STRING - * ) - */ -typedef struct secure_key_wrapper { - ASN1_INTEGER* version; - ASN1_OCTET_STRING* encrypted_transport_key; - ASN1_OCTET_STRING* initialization_vector; - TEST_KEY_DESCRIPTION* key_desc; - ASN1_OCTET_STRING* encrypted_key; - ASN1_OCTET_STRING* tag; -} TEST_SECURE_KEY_WRAPPER; - -ASN1_SEQUENCE(TEST_SECURE_KEY_WRAPPER) = { - ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, version, ASN1_INTEGER), - ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, encrypted_transport_key, ASN1_OCTET_STRING), - ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, initialization_vector, ASN1_OCTET_STRING), - ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, key_desc, TEST_KEY_DESCRIPTION), - ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, encrypted_key, ASN1_OCTET_STRING), - ASN1_SIMPLE(TEST_SECURE_KEY_WRAPPER, tag, ASN1_OCTET_STRING), -} ASN1_SEQUENCE_END(TEST_SECURE_KEY_WRAPPER); -DECLARE_ASN1_FUNCTIONS(TEST_SECURE_KEY_WRAPPER); - -IMPLEMENT_ASN1_FUNCTIONS(TEST_SECURE_KEY_WRAPPER); -IMPLEMENT_ASN1_FUNCTIONS(TEST_KEY_DESCRIPTION); - -struct TEST_KEY_DESCRIPTION_Delete { - void operator()(TEST_KEY_DESCRIPTION* p) { TEST_KEY_DESCRIPTION_free(p); } -}; -struct TEST_SECURE_KEY_WRAPPER_Delete { - void operator()(TEST_SECURE_KEY_WRAPPER* p) { TEST_SECURE_KEY_WRAPPER_free(p); } -}; - -/* This function extracts a certificate from the certs_chain_buffer at the given - * offset. Each DER encoded certificate starts with TAG_SEQUENCE followed by the - * total length of the certificate. The length of the certificate is determined - * as per ASN.1 encoding rules for the length octets. - * - * @param certs_chain_buffer: buffer containing DER encoded X.509 certificates - * arranged sequentially. - * @data_size: Length of the DER encoded X.509 certificates buffer. - * @index: DER encoded X.509 certificates buffer offset. - * @cert: Encoded certificate to be extracted from buffer as outcome. - * @return: ErrorCode::OK on success, otherwise ErrorCode::UNKNOWN_ERROR. - */ -ErrorCode -extractCertFromCertChainBuffer(uint8_t* certs_chain_buffer, int certs_chain_buffer_size, int& index, - aidl::android::hardware::security::keymint::Certificate& cert) { - if (index >= certs_chain_buffer_size) { - return ErrorCode::UNKNOWN_ERROR; - } - - uint32_t length = 0; - std::vector<uint8_t> cert_bytes; - if (certs_chain_buffer[index] == TAG_SEQUENCE) { - // Short form. One octet. Bit 8 has value "0" and bits 7-1 give the length. - if (0 == (certs_chain_buffer[index + 1] & LENGTH_MASK)) { - length = (uint32_t)certs_chain_buffer[index]; - // Add SEQ and Length fields - length += 2; - } else { - // Long form. Two to 127 octets. Bit 8 of first octet has value "1" and - // bits 7-1 give the number of additional length octets. Second and following - // octets give the actual length. - int additionalBytes = certs_chain_buffer[index + 1] & LENGTH_VALUE_MASK; - if (additionalBytes == 0x01) { - length = certs_chain_buffer[index + 2]; - // Add SEQ and Length fields - length += 3; - } else if (additionalBytes == 0x02) { - length = (certs_chain_buffer[index + 2] << 8 | certs_chain_buffer[index + 3]); - // Add SEQ and Length fields - length += 4; - } else if (additionalBytes == 0x04) { - length = certs_chain_buffer[index + 2] << 24; - length |= certs_chain_buffer[index + 3] << 16; - length |= certs_chain_buffer[index + 4] << 8; - length |= certs_chain_buffer[index + 5]; - // Add SEQ and Length fields - length += 6; - } else { - // Length is larger than uint32_t max limit. - return ErrorCode::UNKNOWN_ERROR; - } - } - cert_bytes.insert(cert_bytes.end(), (certs_chain_buffer + index), - (certs_chain_buffer + index + length)); - index += length; - - for (int i = 0; i < cert_bytes.size(); i++) { - cert.encodedCertificate = std::move(cert_bytes); - } - } else { - // SEQUENCE TAG MISSING. - return ErrorCode::UNKNOWN_ERROR; - } - - return ErrorCode::OK; -} - -ErrorCode getCertificateChain( - rust::Vec<rust::u8>& chainBuffer, - std::vector<aidl::android::hardware::security::keymint::Certificate>& certChain) { - uint8_t* data = chainBuffer.data(); - int index = 0; - int data_size = chainBuffer.size(); - - while (index < data_size) { - aidl::android::hardware::security::keymint::Certificate cert = - aidl::android::hardware::security::keymint::Certificate(); - if (extractCertFromCertChainBuffer(data, data_size, index, cert) != ErrorCode::OK) { - return ErrorCode::UNKNOWN_ERROR; - } - certChain.push_back(std::move(cert)); - } - return ErrorCode::OK; -} - -bool validateCertChain(rust::Vec<rust::u8> cert_buf, uint32_t cert_len, bool strict_issuer_check) { - std::vector<aidl::android::hardware::security::keymint::Certificate> cert_chain = - std::vector<aidl::android::hardware::security::keymint::Certificate>(); - if (cert_len <= 0) { - return false; - } - if (getCertificateChain(cert_buf, cert_chain) != ErrorCode::OK) { - return false; - } - - for (int i = 0; i < cert_chain.size(); i++) { - std::cout << cert_chain[i].toString() << "\n"; - } - auto result = aidl::android::hardware::security::keymint::test::ChainSignaturesAreValid( - cert_chain, strict_issuer_check); - - if (result == testing::AssertionSuccess()) return true; - - return false; -} - -/** - * Below mentioned key parameters are used to create authorization list of - * secure key. - * Algorithm: AES-256 - * Padding: PKCS7 - * Blockmode: ECB - * Purpose: Encrypt, Decrypt - */ -keymaster::AuthorizationSet build_wrapped_key_auth_list() { - return keymaster::AuthorizationSet(keymaster::AuthorizationSetBuilder() - .AesEncryptionKey(256) - .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_ECB) - .Authorization(keymaster::TAG_PADDING, KM_PAD_PKCS7) - .Authorization(keymaster::TAG_NO_AUTH_REQUIRED)); -} - -/** - * Creates ASN.1 DER-encoded data corresponding to `KeyDescription` schema as - * AAD. See `IKeyMintDevice.aidl` for documentation of the `KeyDescription` schema. - */ -CxxResult buildAsn1DerEncodedWrappedKeyDescription() { - CxxResult cxx_result{}; - keymaster_error_t error; - cxx_result.error = KM_ERROR_OK; - - keymaster::UniquePtr<TEST_KEY_DESCRIPTION, TEST_KEY_DESCRIPTION_Delete> key_description( - TEST_KEY_DESCRIPTION_new()); - if (!key_description.get()) { - cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; - return cxx_result; - } - - // Fill secure key authorizations. - keymaster::AuthorizationSet auth_list = build_wrapped_key_auth_list(); - error = build_auth_list(auth_list, key_description->key_params); - if (error != KM_ERROR_OK) { - cxx_result.error = error; - return cxx_result; - } - - // Fill secure key format. - if (!ASN1_INTEGER_set(key_description->key_format, KM_KEY_FORMAT_RAW)) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - // Perform ASN.1 DER encoding of KeyDescription. - int asn1_data_len = i2d_TEST_KEY_DESCRIPTION(key_description.get(), nullptr); - if (asn1_data_len < 0) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - std::vector<uint8_t> asn1_data(asn1_data_len, 0); - - if (!asn1_data.data()) { - cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; - return cxx_result; - } - - uint8_t* p = asn1_data.data(); - asn1_data_len = i2d_TEST_KEY_DESCRIPTION(key_description.get(), &p); - if (asn1_data_len < 0) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - std::move(asn1_data.begin(), asn1_data.end(), std::back_inserter(cxx_result.data)); - - return cxx_result; -} - -/** - * Creates wrapped key material to import in ASN.1 DER-encoded data corresponding to - * `SecureKeyWrapper` schema. See `IKeyMintDevice.aidl` for documentation of the `SecureKeyWrapper` - * schema. - */ -CxxResult createWrappedKey(rust::Vec<rust::u8> encrypted_secure_key, - rust::Vec<rust::u8> encrypted_transport_key, rust::Vec<rust::u8> iv, - rust::Vec<rust::u8> tag) { - CxxResult cxx_result{}; - keymaster_error_t error; - cxx_result.error = KM_ERROR_OK; - - uint8_t* enc_secure_key_data = encrypted_secure_key.data(); - int enc_secure_key_size = encrypted_secure_key.size(); - - uint8_t* iv_data = iv.data(); - int iv_size = iv.size(); - - uint8_t* tag_data = tag.data(); - int tag_size = tag.size(); - - uint8_t* enc_transport_key_data = encrypted_transport_key.data(); - int enc_transport_key_size = encrypted_transport_key.size(); - - keymaster::UniquePtr<TEST_SECURE_KEY_WRAPPER, TEST_SECURE_KEY_WRAPPER_Delete> sec_key_wrapper( - TEST_SECURE_KEY_WRAPPER_new()); - if (!sec_key_wrapper.get()) { - cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; - return cxx_result; - } - - // Fill version = 0 - if (!ASN1_INTEGER_set(sec_key_wrapper->version, 0)) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - // Fill encrypted transport key. - if (enc_transport_key_size && - !ASN1_OCTET_STRING_set(sec_key_wrapper->encrypted_transport_key, enc_transport_key_data, - enc_transport_key_size)) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - // Fill encrypted secure key. - if (enc_secure_key_size && !ASN1_OCTET_STRING_set(sec_key_wrapper->encrypted_key, - enc_secure_key_data, enc_secure_key_size)) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - // Fill secure key authorization list. - keymaster::AuthorizationSet auth_list = build_wrapped_key_auth_list(); - error = build_auth_list(auth_list, sec_key_wrapper->key_desc->key_params); - if (error != KM_ERROR_OK) { - cxx_result.error = error; - return cxx_result; - } - - // Fill secure key format. - if (!ASN1_INTEGER_set(sec_key_wrapper->key_desc->key_format, KM_KEY_FORMAT_RAW)) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - // Fill initialization vector used for encrypting secure key. - if (iv_size && - !ASN1_OCTET_STRING_set(sec_key_wrapper->initialization_vector, iv_data, iv_size)) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - // Fill GCM-tag, extracted during secure key encryption. - if (tag_size && !ASN1_OCTET_STRING_set(sec_key_wrapper->tag, tag_data, tag_size)) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - // ASN.1 DER-encoding of secure key wrapper. - int asn1_data_len = i2d_TEST_SECURE_KEY_WRAPPER(sec_key_wrapper.get(), nullptr); - if (asn1_data_len < 0) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - std::vector<uint8_t> asn1_data(asn1_data_len, 0); - - if (!asn1_data.data()) { - cxx_result.error = KM_ERROR_MEMORY_ALLOCATION_FAILED; - return cxx_result; - } - - uint8_t* p = asn1_data.data(); - asn1_data_len = i2d_TEST_SECURE_KEY_WRAPPER(sec_key_wrapper.get(), &p); - if (asn1_data_len < 0) { - cxx_result.error = keymaster::TranslateLastOpenSslError(); - return cxx_result; - } - - std::move(asn1_data.begin(), asn1_data.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 deleted file mode 100644 index b8c7c483..00000000 --- a/keystore2/tests/ffi_test_utils.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "rust/cxx.h" -#include "ffi_test_utils.rs.h" - -bool validateCertChain(rust::Vec<rust::u8> cert_buf, uint32_t cert_len, bool strict_issuer_check); -CxxResult createWrappedKey(rust::Vec<rust::u8> encrypted_secure_key, - rust::Vec<rust::u8> encrypted_transport_key, - rust::Vec<rust::u8> iv, - rust::Vec<rust::u8> tag); -CxxResult buildAsn1DerEncodedWrappedKeyDescription(); diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs index 4febd9b5..454248a3 100644 --- a/keystore2/tests/keystore2_client_attest_key_tests.rs +++ b/keystore2/tests/keystore2_client_attest_key_tests.rs @@ -17,21 +17,27 @@ 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 keystore2_test_utils::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_device_id_attestation_tests, 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, skip_device_id_attest_tests, }; /// Generate RSA and EC attestation keys and use them for signing RSA-signing keys. @@ -480,3 +486,183 @@ 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<dyn IKeystoreService>) -> Vec<(Tag, Vec<u8>)> { + let attest_ids = vec![ + (Tag::ATTESTATION_ID_BRAND, "brand"), + (Tag::ATTESTATION_ID_DEVICE, "device"), + (Tag::ATTESTATION_ID_PRODUCT, "name"), + (Tag::ATTESTATION_ID_SERIAL, "serialno"), + (Tag::ATTESTATION_ID_MANUFACTURER, "manufacturer"), + (Tag::ATTESTATION_ID_MODEL, "model"), + (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<u8>)> = 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!(); + skip_device_id_attestation_tests!(); + skip_test_if_no_app_attest_key_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<u8> = 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, + SecurityLevel::TRUSTED_ENVIRONMENT, + ) + .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_authorizations_tests.rs b/keystore2/tests/keystore2_client_authorizations_tests.rs new file mode 100644 index 00000000..32be99e0 --- /dev/null +++ b/keystore2/tests/keystore2_client_authorizations_tests.rs @@ -0,0 +1,985 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::time::SystemTime; + +use openssl::bn::{BigNum, MsbOption}; +use openssl::x509::X509NameBuilder; + +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, Tag::Tag, +}; + +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, + KeyMetadata::KeyMetadata, ResponseCode::ResponseCode, +}; + +use aconfig_android_hardware_biometrics_rust; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + HardwareAuthToken::HardwareAuthToken, + HardwareAuthenticatorType::HardwareAuthenticatorType +}; +use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::Timestamp::Timestamp; + +use keystore2_test_utils::{ + authorizations, get_keystore_auth_service, get_keystore_service, key_generations, + key_generations::Error, +}; + +use crate::keystore2_client_test_utils::{ + app_attest_key_feature_exists, delete_app_key, perform_sample_asym_sign_verify_op, + perform_sample_hmac_sign_verify_op, perform_sample_sym_key_decrypt_op, + perform_sample_sym_key_encrypt_op, verify_certificate_serial_num, + verify_certificate_subject_name, SAMPLE_PLAIN_TEXT, +}; + +use crate::{skip_test_if_no_app_attest_key_feature, skip_tests_if_keymaster_impl_present}; + +use keystore2_test_utils::ffi_test_utils::get_value_from_attest_record; + +fn gen_key_including_unique_id( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: &str, +) -> Vec<u8> { + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .include_unique_id(); + + let key_metadata = key_generations::generate_key(sec_level, &gen_params, alias).unwrap(); + + let unique_id = get_value_from_attest_record( + key_metadata.certificate.as_ref().unwrap(), + Tag::UNIQUE_ID, + key_metadata.keySecurityLevel, + ) + .expect("Unique id not found."); + assert!(!unique_id.is_empty()); + unique_id +} + +fn generate_key_and_perform_sign_verify_op_max_times( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + gen_params: &authorizations::AuthSetBuilder, + alias: &str, + max_usage_count: i32, +) -> binder::Result<KeyMetadata> { + let key_metadata = key_generations::generate_key(sec_level, gen_params, alias)?; + + // Use above generated key `max_usage_count` times. + for _ in 0..max_usage_count { + perform_sample_asym_sign_verify_op(sec_level, &key_metadata, None, Some(Digest::SHA_2_256)); + } + + Ok(key_metadata) +} + +/// Generate a key with `USAGE_COUNT_LIMIT` and verify the key characteristics. Test should be able +/// to use the key successfully `max_usage_count` times. After exceeding key usage `max_usage_count` +/// times subsequent attempts to use the key in test should fail with response code `KEY_NOT_FOUND`. +/// Test should also verify that the attest record includes `USAGE_COUNT_LIMIT` for attested keys. +fn generate_key_and_perform_op_with_max_usage_limit( + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + gen_params: &authorizations::AuthSetBuilder, + alias: &str, + max_usage_count: i32, + check_attestation: bool, +) { + // Generate a key and use the key for `max_usage_count` times. + let key_metadata = generate_key_and_perform_sign_verify_op_max_times( + sec_level, + gen_params, + alias, + max_usage_count, + ) + .unwrap(); + + let auth = key_generations::get_key_auth(&key_metadata.authorizations, Tag::USAGE_COUNT_LIMIT) + .unwrap(); + if check_attestation && key_generations::has_default_keymint() { + // Check usage-count-limit is included in attest-record. + // `USAGE_COUNT_LIMIT` is supported from KeyMint1.0 + assert_ne!( + gen_params.iter().filter(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE).count(), + 0, + "Attestation challenge is missing in generated key parameters." + ); + let result = get_value_from_attest_record( + key_metadata.certificate.as_ref().unwrap(), + Tag::USAGE_COUNT_LIMIT, + auth.securityLevel, + ) + .expect("Attest id verification failed."); + let usage_count: i32 = std::str::from_utf8(&result).unwrap().parse().unwrap(); + assert_eq!(usage_count, max_usage_count); + } + if max_usage_count == 1 { + assert!(matches!( + auth.securityLevel, + SecurityLevel::KEYSTORE | SecurityLevel::TRUSTED_ENVIRONMENT + )); + } else { + assert_eq!(auth.securityLevel, SecurityLevel::KEYSTORE); + } + + // Try to use the key one more time. + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); +} + +/// Generate a key with `ACTIVE_DATETIME` set to current time. Test should successfully generate +/// a key and verify the key characteristics. Test should be able to create a sign operation using +/// the generated key successfully. +#[test] +fn keystore2_gen_key_auth_active_datetime_test_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let active_datetime = duration_since_epoch.as_millis(); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .active_date_time(active_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + alias, + ); + assert!(result.is_ok()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `ACTIVE_DATETIME` set to future date and time. Test should successfully +/// generate a key and verify the key characteristics. Try to create a sign operation +/// using the generated key, test should fail to create an operation with error code +/// `KEY_NOT_YET_VALID`. +#[test] +fn keystore2_gen_key_auth_future_active_datetime_test_op_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let future_active_datetime = duration_since_epoch.as_millis() + (24 * 60 * 60 * 1000); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .active_date_time(future_active_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::map_ks_error(key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + alias, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::KEY_NOT_YET_VALID), result.unwrap_err()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `ORIGINATION_EXPIRE_DATETIME` set to future date and time. Test should +/// successfully generate a key and verify the key characteristics. Test should be able to create +/// sign operation using the generated key successfully. +#[test] +fn keystore2_gen_key_auth_future_origination_expire_datetime_test_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let origination_expire_datetime = duration_since_epoch.as_millis() + (24 * 60 * 60 * 1000); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .origination_expire_date_time(origination_expire_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + alias, + ); + assert!(result.is_ok()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `ORIGINATION_EXPIRE_DATETIME` set to current date and time. Test should +/// successfully generate a key and verify the key characteristics. Try to create a sign operation +/// using the generated key, test should fail to create an operation with error code +/// `KEY_EXPIRED`. +#[test] +fn keystore2_gen_key_auth_origination_expire_datetime_test_op_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let origination_expire_datetime = duration_since_epoch.as_millis(); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .origination_expire_date_time(origination_expire_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::map_ks_error(key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + alias, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::KEY_EXPIRED), result.unwrap_err()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a HMAC key with `USAGE_EXPIRE_DATETIME` set to future date and time. Test should +/// successfully generate a key and verify the key characteristics. Test should be able to create +/// sign and verify operations using the generated key successfully. +#[test] +fn keystore2_gen_key_auth_future_usage_expire_datetime_hmac_verify_op_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let usage_expire_datetime = duration_since_epoch.as_millis() + (24 * 60 * 60 * 1000); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::HMAC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .key_size(128) + .min_mac_length(256) + .digest(Digest::SHA_2_256) + .usage_expire_date_time(usage_expire_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_hmac_verify_success"; + let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + + perform_sample_hmac_sign_verify_op(&sec_level, &key_metadata.key); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `USAGE_EXPIRE_DATETIME` set to current date and time. Test should +/// successfully generate a key and verify the key characteristics. Test should be able to create +/// sign operation successfully and fail while performing verify operation with error code +/// `KEY_EXPIRED`. +#[test] +fn keystore2_gen_key_auth_usage_expire_datetime_hmac_verify_op_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let usage_expire_datetime = duration_since_epoch.as_millis(); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::HMAC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .key_size(128) + .min_mac_length(256) + .digest(Digest::SHA_2_256) + .usage_expire_date_time(usage_expire_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_hamc_verify_fail"; + let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + + let result = key_generations::map_ks_error( + sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256), + false, + ), + ); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::KEY_EXPIRED), result.unwrap_err()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate AES key with `USAGE_EXPIRE_DATETIME` set to future date and time. Test should +/// successfully generate a key and verify the key characteristics. Test should be able to create +/// Encrypt and Decrypt operations successfully. +#[test] +fn keystore2_gen_key_auth_usage_future_expire_datetime_decrypt_op_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let usage_expire_datetime = duration_since_epoch.as_millis() + (24 * 60 * 60 * 1000); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::AES) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .key_size(128) + .padding_mode(PaddingMode::PKCS7) + .block_mode(BlockMode::ECB) + .usage_expire_date_time(usage_expire_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_test"; + let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + let cipher_text = perform_sample_sym_key_encrypt_op( + &sec_level, + PaddingMode::PKCS7, + BlockMode::ECB, + &mut None, + None, + &key_metadata.key, + ) + .unwrap(); + + assert!(cipher_text.is_some()); + + let plain_text = perform_sample_sym_key_decrypt_op( + &sec_level, + &cipher_text.unwrap(), + PaddingMode::PKCS7, + BlockMode::ECB, + &mut None, + None, + &key_metadata.key, + ) + .unwrap(); + assert!(plain_text.is_some()); + assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate AES key with `USAGE_EXPIRE_DATETIME` set to current date and time. Test should +/// successfully generate a key and verify the key characteristics. Test should be able to create +/// Encrypt operation successfully and fail while performing decrypt operation with error code +/// `KEY_EXPIRED`. +#[test] +fn keystore2_gen_key_auth_usage_expire_datetime_decrypt_op_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let usage_expire_datetime = duration_since_epoch.as_millis(); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::AES) + .purpose(KeyPurpose::ENCRYPT) + .purpose(KeyPurpose::DECRYPT) + .key_size(128) + .padding_mode(PaddingMode::PKCS7) + .block_mode(BlockMode::ECB) + .usage_expire_date_time(usage_expire_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_test"; + let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + let cipher_text = perform_sample_sym_key_encrypt_op( + &sec_level, + PaddingMode::PKCS7, + BlockMode::ECB, + &mut None, + None, + &key_metadata.key, + ) + .unwrap(); + + assert!(cipher_text.is_some()); + + let result = key_generations::map_ks_error(perform_sample_sym_key_decrypt_op( + &sec_level, + &cipher_text.unwrap(), + PaddingMode::PKCS7, + BlockMode::ECB, + &mut None, + None, + &key_metadata.key, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::KEY_EXPIRED), result.unwrap_err()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `EARLY_BOOT_ONLY`. Test should successfully generate +/// a key and verify the key characteristics. Test should fail with error code `EARLY_BOOT_ENDED` +/// during creation of an operation using this key. +#[test] +fn keystore2_gen_key_auth_early_boot_only_op_fail() { + skip_tests_if_keymaster_impl_present!(); + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .early_boot_only(); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::map_ks_error(key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + alias, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::EARLY_BOOT_ENDED), result.unwrap_err()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `MAX_USES_PER_BOOT`. Test should successfully generate +/// a key and verify the key characteristics. Test should be able to use the key successfully +/// `MAX_USES_COUNT` times. After exceeding key usage `MAX_USES_COUNT` times +/// subsequent attempts to use the key in test should fail with error code MAX_OPS_EXCEEDED. +#[test] +fn keystore2_gen_key_auth_max_uses_per_boot() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + const MAX_USES_COUNT: i32 = 3; + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .max_uses_per_boot(MAX_USES_COUNT); + + let alias = "ks_test_auth_tags_test"; + // Generate a key and use the key for `MAX_USES_COUNT` times. + let key_metadata = generate_key_and_perform_sign_verify_op_max_times( + &sec_level, + &gen_params, + alias, + MAX_USES_COUNT, + ) + .unwrap(); + + // Try to use the key one more time. + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), + false, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::KEY_MAX_OPS_EXCEEDED), result.unwrap_err()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `USAGE_COUNT_LIMIT`. Test should successfully generate +/// a key and verify the key characteristics. Test should be able to use the key successfully +/// `MAX_USES_COUNT` times. After exceeding key usage `MAX_USES_COUNT` times +/// subsequent attempts to use the key in test should fail with response code `KEY_NOT_FOUND`. +/// Test should also verify that the attest record includes `USAGE_COUNT_LIMIT`. +#[test] +fn keystore2_gen_key_auth_usage_count_limit() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + const MAX_USES_COUNT: i32 = 3; + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .usage_count_limit(MAX_USES_COUNT); + + let alias = "ks_test_auth_tags_test"; + generate_key_and_perform_op_with_max_usage_limit( + &sec_level, + &gen_params, + alias, + MAX_USES_COUNT, + true, + ); +} + +/// Generate a key with `USAGE_COUNT_LIMIT`. Test should successfully generate +/// a key and verify the key characteristics. Test should be able to use the key successfully +/// `MAX_USES_COUNT` times. After exceeding key usage `MAX_USES_COUNT` times +/// subsequent attempts to use the key in test should fail with response code `KEY_NOT_FOUND`. +/// Test should also verify that the attest record includes `USAGE_COUNT_LIMIT`. +#[test] +fn keystore2_gen_key_auth_usage_count_limit_one() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + const MAX_USES_COUNT: i32 = 1; + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .usage_count_limit(MAX_USES_COUNT); + + let alias = "ks_test_auth_tags_test"; + generate_key_and_perform_op_with_max_usage_limit( + &sec_level, + &gen_params, + alias, + MAX_USES_COUNT, + true, + ); +} + +/// Generate a non-attested key with `USAGE_COUNT_LIMIT`. Test should successfully generate +/// a key and verify the key characteristics. Test should be able to use the key successfully +/// `MAX_USES_COUNT` times. After exceeding key usage `MAX_USES_COUNT` times +/// subsequent attempts to use the key in test should fail with response code `KEY_NOT_FOUND`. +#[test] +fn keystore2_gen_non_attested_key_auth_usage_count_limit() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + const MAX_USES_COUNT: i32 = 2; + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .usage_count_limit(MAX_USES_COUNT); + + let alias = "ks_test_auth_tags_test"; + generate_key_and_perform_op_with_max_usage_limit( + &sec_level, + &gen_params, + alias, + MAX_USES_COUNT, + false, + ); +} + +/// Try to generate a key with `Tag::CREATION_DATETIME` set to valid value. Test should fail +/// to generate a key with `INVALID_ARGUMENT` error as Keystore2 backend doesn't allow user to +/// specify `CREATION_DATETIME`. +#[test] +fn keystore2_gen_key_auth_creation_date_time_test_fail_with_invalid_arg_error() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let creation_datetime = duration_since_epoch.as_millis(); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .creation_date_time(creation_datetime.try_into().unwrap()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::map_ks_error(sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + )); + + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); +} + +/// Generate a key with `Tag::INCLUDE_UNIQUE_ID` set. Test should verify that `Tag::UNIQUE_ID` is +/// included in attest record and it remains the same for new keys generated. +#[test] +fn keystore2_gen_key_auth_include_unique_id_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias_first = "ks_test_auth_tags_test_1"; + let unique_id_first = gen_key_including_unique_id(&sec_level, alias_first); + + let alias_second = "ks_test_auth_tags_test_2"; + let unique_id_second = gen_key_including_unique_id(&sec_level, alias_second); + + assert_eq!(unique_id_first, unique_id_second); + + delete_app_key(&keystore2, alias_first).unwrap(); + delete_app_key(&keystore2, alias_second).unwrap(); +} + +/// Generate a key with `APPLICATION_DATA`. Test should create an operation using the +/// same `APPLICATION_DATA` successfully. +#[test] +fn keystore2_gen_key_auth_app_data_test_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .app_data(b"app-data".to_vec()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256) + .app_data(b"app-data".to_vec()), + alias, + ); + assert!(result.is_ok()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `APPLICATION_DATA`. Try to create an operation using the +/// different `APPLICATION_DATA`, test should fail to create an operation with error code +/// `INVALID_KEY_BLOB`. +#[test] +fn keystore2_gen_key_auth_app_data_test_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .app_data(b"app-data".to_vec()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::map_ks_error(key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256) + .app_data(b"invalid-app-data".to_vec()), + alias, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `APPLICATION_ID`. Test should create an operation using the +/// same `APPLICATION_ID` successfully. +#[test] +fn keystore2_gen_key_auth_app_id_test_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .app_id(b"app-id".to_vec()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256) + .app_id(b"app-id".to_vec()), + alias, + ); + assert!(result.is_ok()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate a key with `APPLICATION_ID`. Try to create an operation using the +/// different `APPLICATION_ID`, test should fail to create an operation with error code +/// `INVALID_KEY_BLOB`. +#[test] +fn keystore2_gen_key_auth_app_id_test_fail() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .app_id(b"app-id".to_vec()); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::map_ks_error(key_generations::create_key_and_operation( + &sec_level, + &gen_params, + &authorizations::AuthSetBuilder::new() + .purpose(KeyPurpose::SIGN) + .digest(Digest::SHA_2_256) + .app_id(b"invalid-app-id".to_vec()), + alias, + )); + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err()); + delete_app_key(&keystore2, alias).unwrap(); +} + +/// Generate an attestation-key without specifying `APPLICATION_ID` and `APPLICATION_DATA`. +/// Test should be able to generate a new key with specifying app-id and app-data using previously +/// generated attestation-key. +#[test] +fn keystore2_gen_attested_key_auth_app_id_app_data_test_success() { + skip_test_if_no_app_attest_key_feature!(); + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Generate attestation key. + let attest_gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::ATTEST_KEY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()); + let attest_alias = "ks_test_auth_tags_attest_key"; + let attest_key_metadata = + key_generations::generate_key(&sec_level, &attest_gen_params, attest_alias).unwrap(); + + // Generate attested key. + let alias = "ks_test_auth_tags_attested_key"; + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"bar".to_vec()) + .app_id(b"app-id".to_vec()) + .app_data(b"app-data".to_vec()); + + let result = sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + Some(&attest_key_metadata.key), + &gen_params, + 0, + b"entropy", + ); + + assert!(result.is_ok()); + delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&keystore2, attest_alias).unwrap(); +} + +/// Generate an attestation-key with specifying `APPLICATION_ID` and `APPLICATION_DATA`. +/// Test should try to generate an attested key using previously generated attestation-key without +/// specifying app-id and app-data. Test should fail to generate a new key with error code +/// `INVALID_KEY_BLOB`. +/// It is an oversight of the Keystore API that `APPLICATION_ID` and `APPLICATION_DATA` tags cannot +/// be provided to generateKey for an attestation key that was generated with them. +#[test] +fn keystore2_gen_attestation_key_with_auth_app_id_app_data_test_fail() { + skip_test_if_no_app_attest_key_feature!(); + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Generate attestation key. + let attest_gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::ATTEST_KEY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .app_id(b"app-id".to_vec()) + .app_data(b"app-data".to_vec()); + let attest_alias = "ks_test_auth_tags_attest_key"; + let attest_key_metadata = + key_generations::generate_key(&sec_level, &attest_gen_params, attest_alias).unwrap(); + + // Generate new key using above generated attestation key without providing app-id and app-data. + let alias = "ks_test_auth_tags_attested_key"; + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()); + + let result = key_generations::map_ks_error(sec_level.generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + Some(&attest_key_metadata.key), + &gen_params, + 0, + b"entropy", + )); + + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err()); + delete_app_key(&keystore2, attest_alias).unwrap(); +} + +fn add_hardware_token(auth_type: HardwareAuthenticatorType) { + let keystore_auth = get_keystore_auth_service(); + + let token = HardwareAuthToken { + challenge: 0, + userId: 0, + authenticatorId: 0, + authenticatorType: auth_type, + timestamp: Timestamp { milliSeconds: 500 }, + mac: vec![], + }; + keystore_auth.addAuthToken(&token).unwrap(); +} + +#[test] +fn keystore2_flagged_off_get_last_auth_password_permission_denied() { + if aconfig_android_hardware_biometrics_rust::last_authentication_time() { + return; + } + + let keystore_auth = get_keystore_auth_service(); + + let result = keystore_auth.getLastAuthTime(0, &[HardwareAuthenticatorType::PASSWORD]); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err().service_specific_error(), ResponseCode::PERMISSION_DENIED.0); +} + +#[test] +fn keystore2_flagged_on_get_last_auth_password_success() { + if !aconfig_android_hardware_biometrics_rust::last_authentication_time() { + return; + } + + let keystore_auth = get_keystore_auth_service(); + + add_hardware_token(HardwareAuthenticatorType::PASSWORD); + assert!(keystore_auth.getLastAuthTime(0, &[HardwareAuthenticatorType::PASSWORD]).unwrap() > 0); +} + +#[test] +fn keystore2_flagged_on_get_last_auth_fingerprint_success() { + if !aconfig_android_hardware_biometrics_rust::last_authentication_time() { + return; + } + + let keystore_auth = get_keystore_auth_service(); + + add_hardware_token(HardwareAuthenticatorType::FINGERPRINT); + assert!( + keystore_auth.getLastAuthTime(0, &[HardwareAuthenticatorType::FINGERPRINT]).unwrap() > 0 + ); +} + +/// Generate a key with specifying `CERTIFICATE_SUBJECT and CERTIFICATE_SERIAL`. Test should +/// generate a key successfully and verify the specified key parameters. +#[test] +fn keystore2_gen_key_auth_serial_number_subject_test_success() { + skip_tests_if_keymaster_impl_present!(); + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let cert_subject = "test cert subject"; + let mut x509_name = X509NameBuilder::new().unwrap(); + x509_name.append_entry_by_text("CN", cert_subject).unwrap(); + let x509_name = x509_name.build().to_der().unwrap(); + + let mut serial = BigNum::new().unwrap(); + serial.rand(159, MsbOption::MAYBE_ZERO, false).unwrap(); + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .cert_subject_name(x509_name) + .cert_serial(serial.to_vec()); + + let alias = "ks_test_auth_tags_test"; + let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + verify_certificate_subject_name( + key_metadata.certificate.as_ref().unwrap(), + cert_subject.as_bytes(), + ); + verify_certificate_serial_num(key_metadata.certificate.as_ref().unwrap(), &serial); + delete_app_key(&keystore2, alias).unwrap(); +} diff --git a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs new file mode 100644 index 00000000..b784adf4 --- /dev/null +++ b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs @@ -0,0 +1,412 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, + KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, Tag::Tag, +}; + +use keystore2_test_utils::{ + authorizations, get_keystore_service, key_generations, key_generations::Error, +}; + +use keystore2_test_utils::ffi_test_utils::get_value_from_attest_record; + +use crate::keystore2_client_test_utils::{ + delete_app_key, get_attest_id_value, is_second_imei_id_attestation_required, + perform_sample_asym_sign_verify_op, +}; + +use crate::skip_tests_if_keymaster_impl_present; + +/// This macro is used for generating device unique attested EC key with device id attestation. +macro_rules! test_ec_key_device_unique_attestation_id { + ( $test_name:ident, $tag:expr, $prop_name:expr ) => { + #[test] + fn $test_name() { + generate_ec_key_device_unique_attested_with_id_attest($tag, $prop_name); + } + }; +} + +/// This macro is used for generating device unique attested RSA key with device id attestation. +macro_rules! test_rsa_key_device_unique_attestation_id { + ( $test_name:ident, $tag:expr, $prop_name:expr ) => { + #[test] + fn $test_name() { + generate_rsa_key_device_unique_attested_with_id_attest($tag, $prop_name); + } + }; +} + +fn generate_ec_key_device_unique_attested_with_id_attest(attest_id_tag: Tag, prop_name: &str) { + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .device_unique_attestation(); + generate_device_unique_attested_key_with_device_attest_ids( + gen_params, + attest_id_tag, + prop_name, + ); +} + +fn generate_rsa_key_device_unique_attested_with_id_attest(attest_id_tag: Tag, prop_name: &str) { + let gen_params = authorizations::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(b"foo".to_vec()) + .device_unique_attestation(); + generate_device_unique_attested_key_with_device_attest_ids( + gen_params, + attest_id_tag, + prop_name, + ); +} + +fn add_attest_id_auth( + gen_params: authorizations::AuthSetBuilder, + attest_id_tag: Tag, + value: Vec<u8>, +) -> authorizations::AuthSetBuilder { + match attest_id_tag { + Tag::ATTESTATION_ID_BRAND => gen_params.attestation_device_brand(value), + Tag::ATTESTATION_ID_DEVICE => gen_params.attestation_device_name(value), + Tag::ATTESTATION_ID_PRODUCT => gen_params.attestation_device_product_name(value), + Tag::ATTESTATION_ID_SERIAL => gen_params.attestation_device_serial(value), + Tag::ATTESTATION_ID_MANUFACTURER => gen_params.attestation_device_manufacturer(value), + Tag::ATTESTATION_ID_MODEL => gen_params.attestation_device_model(value), + Tag::ATTESTATION_ID_IMEI => gen_params.attestation_device_imei(value), + Tag::ATTESTATION_ID_SECOND_IMEI => gen_params.attestation_device_second_imei(value), + _ => { + panic!("Unknown attestation id"); + } + } +} + +/// Generate a device unique 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`. +fn generate_device_unique_attested_key_with_device_attest_ids( + gen_params: authorizations::AuthSetBuilder, + attest_id: Tag, + prop_name: &str, +) { + let keystore2 = get_keystore_service(); + let result = + key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::STRONGBOX)); + if result.is_err() { + assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); + return; + } + let sec_level = result.unwrap(); + + if attest_id == Tag::ATTESTATION_ID_SECOND_IMEI + && !is_second_imei_id_attestation_required(&keystore2) + { + return; + } + + if let Some(value) = get_attest_id_value(attest_id, prop_name) { + if value.is_empty() { + return; + } + let gen_params = add_attest_id_auth(gen_params, attest_id, value.clone()); + let alias = "ks_test_device_unique_attest_id_test"; + match key_generations::map_ks_error(key_generations::generate_key( + &sec_level, + &gen_params, + alias, + )) { + Ok(key_metadata) => { + let attest_id_value = get_value_from_attest_record( + key_metadata.certificate.as_ref().unwrap(), + attest_id, + key_metadata.keySecurityLevel, + ) + .expect("Attest id verification failed."); + assert_eq!(attest_id_value, value); + delete_app_key(&keystore2, alias).unwrap(); + } + Err(e) => { + assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); + } + } + } +} + +/// Try generate a key with `DEVICE_UNIQUE_ATTESTATION` using `TRUSTED_ENVIRONMENT` security level. +/// Test should fail to generate a key with error code `INVALID_ARGUMENT` +#[test] +fn keystore2_gen_key_device_unique_attest_with_default_sec_level_unimplemented() { + skip_tests_if_keymaster_impl_present!(); + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .device_unique_attestation(); + + let alias = "ks_test_auth_tags_test"; + let result = key_generations::map_ks_error(key_generations::generate_key( + &sec_level, + &gen_params, + alias, + )); + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + Error::Km(ErrorCode::INVALID_ARGUMENT) | Error::Km(ErrorCode::UNSUPPORTED_TAG) + )); +} + +/// Generate a EC key with `DEVICE_UNIQUE_ATTESTATION` using `STRONGBOX` security level. +/// Test should create a key successfully, verify key characteristics, cert-chain signatures and +/// use it for performing an operation. +#[test] +fn keystore2_gen_ec_key_device_unique_attest_with_strongbox_sec_level_test_success() { + let keystore2 = get_keystore_service(); + let result = + key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::STRONGBOX)); + if result.is_err() { + assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); + return; + } + + let sec_level = result.unwrap(); + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .device_unique_attestation(); + + let alias = "ks_device_unique_ec_key_attest_test"; + match key_generations::map_ks_error(key_generations::generate_key( + &sec_level, + &gen_params, + alias, + )) { + Ok(key_metadata) => { + perform_sample_asym_sign_verify_op( + &sec_level, + &key_metadata, + None, + Some(Digest::SHA_2_256), + ); + delete_app_key(&keystore2, alias).unwrap(); + } + Err(e) => { + assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); + } + } +} + +/// Generate a RSA key with `DEVICE_UNIQUE_ATTESTATION` using `STRONGBOX` security level. +/// Test should create a key successfully, verify key characteristics, cert-chain signatures and +/// use it for performing an operation. +#[test] +fn keystore2_gen_rsa_key_device_unique_attest_with_strongbox_sec_level_test_success() { + let keystore2 = get_keystore_service(); + let result = + key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::STRONGBOX)); + if result.is_err() { + assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); + return; + } + + let sec_level = result.unwrap(); + let gen_params = authorizations::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(b"foo".to_vec()) + .device_unique_attestation(); + + let alias = "ks_device_unique_rsa_key_attest_test"; + match key_generations::map_ks_error(key_generations::generate_key( + &sec_level, + &gen_params, + alias, + )) { + Ok(key_metadata) => { + perform_sample_asym_sign_verify_op( + &sec_level, + &key_metadata, + Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + Some(Digest::SHA_2_256), + ); + delete_app_key(&keystore2, alias).unwrap(); + } + Err(e) => { + assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); + } + } +} + +/// Try to generate a device unique attested key with attestation of invalid device's identifiers. +/// Test should fail with error response code `CANNOT_ATTEST_IDS`. +#[test] +fn keystore2_device_unique_attest_key_fails_with_invalid_attestation_id() { + let keystore2 = get_keystore_service(); + let result = + key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::STRONGBOX)); + if result.is_err() { + assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); + return; + } + + let sec_level = result.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 { + let gen_params = authorizations::AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::SHA_2_256) + .ec_curve(EcCurve::P_256) + .attestation_challenge(b"foo".to_vec()) + .device_unique_attestation(); + let alias = "ks_ec_device_unique_attested_test_key_fail"; + let gen_params = add_attest_id_auth(gen_params, attest_id, value.clone()); + + let result = key_generations::map_ks_error(key_generations::generate_key( + &sec_level, + &gen_params, + alias, + )); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS))); + } +} + +// Below macros generate tests for generating device unique attested EC keys with attestation +// of the device's identifiers. +test_ec_key_device_unique_attestation_id!( + keystore2_device_unique_attest_ecdsa_attest_id_brand, + Tag::ATTESTATION_ID_BRAND, + "brand" +); +test_ec_key_device_unique_attestation_id!( + keystore2_device_unique_attest_ecdsa_attest_id_device, + Tag::ATTESTATION_ID_DEVICE, + "device" +); +test_ec_key_device_unique_attestation_id!( + keystore2_device_unique_attest_ecdsa_attest_id_product, + Tag::ATTESTATION_ID_PRODUCT, + "name" +); +test_ec_key_device_unique_attestation_id!( + keystore2_device_unique_attest_ecdsa_attest_id_serial, + Tag::ATTESTATION_ID_SERIAL, + "serialno" +); +test_ec_key_device_unique_attestation_id!( + keystore2_device_unique_attest_ecdsa_attest_id_manufacturer, + Tag::ATTESTATION_ID_MANUFACTURER, + "manufacturer" +); +test_ec_key_device_unique_attestation_id!( + keystore2_device_unique_attest_ecdsa_attest_id_model, + Tag::ATTESTATION_ID_MODEL, + "model" +); +test_ec_key_device_unique_attestation_id!( + keystore2_device_unique_attest_ecdsa_attest_id_imei, + Tag::ATTESTATION_ID_IMEI, + "" +); +test_ec_key_device_unique_attestation_id!( + keystore2_device_unique_attest_ecdsa_attest_id_second_imei, + Tag::ATTESTATION_ID_SECOND_IMEI, + "" +); + +// Below macros generate tests for generating device unique attested RSA keys with attestation +// of the device's identifiers. +test_rsa_key_device_unique_attestation_id!( + keystore2_device_unique_attest_rsa_attest_id_brand, + Tag::ATTESTATION_ID_BRAND, + "brand" +); +test_rsa_key_device_unique_attestation_id!( + keystore2_device_unique_attest_rsa_attest_id_device, + Tag::ATTESTATION_ID_DEVICE, + "device" +); +test_rsa_key_device_unique_attestation_id!( + keystore2_device_unique_attest_rsa_attest_id_product, + Tag::ATTESTATION_ID_PRODUCT, + "name" +); +test_rsa_key_device_unique_attestation_id!( + keystore2_device_unique_attest_rsa_attest_id_serial, + Tag::ATTESTATION_ID_SERIAL, + "serialno" +); +test_rsa_key_device_unique_attestation_id!( + keystore2_device_unique_attest_rsa_attest_id_manufacturer, + Tag::ATTESTATION_ID_MANUFACTURER, + "manufacturer" +); +test_rsa_key_device_unique_attestation_id!( + keystore2_device_unique_attest_rsa_attest_id_model, + Tag::ATTESTATION_ID_MODEL, + "model" +); +test_rsa_key_device_unique_attestation_id!( + keystore2_device_unique_attest_rsa_attest_id_imei, + Tag::ATTESTATION_ID_IMEI, + "" +); +test_rsa_key_device_unique_attestation_id!( + keystore2_device_unique_attest_rsa_attest_id_second_imei, + Tag::ATTESTATION_ID_SECOND_IMEI, + "" +); diff --git a/keystore2/tests/keystore2_client_ec_key_tests.rs b/keystore2/tests/keystore2_client_ec_key_tests.rs index c2034ded..f2c6d0f9 100644 --- a/keystore2/tests/keystore2_client_ec_key_tests.rs +++ b/keystore2/tests/keystore2_client_ec_key_tests.rs @@ -30,8 +30,8 @@ use keystore2_test_utils::{ }; use crate::keystore2_client_test_utils::{ - delete_app_key, execute_op_run_as_child, perform_sample_sign_operation, BarrierReached, - ForcedOp, TestOutcome, + delete_app_key, execute_op_run_as_child, get_vsr_api_level, perform_sample_sign_operation, + BarrierReached, ForcedOp, TestOutcome, }; macro_rules! test_ec_sign_key_op_success { @@ -374,13 +374,18 @@ fn keystore2_ec_25519_generate_key_fail() { ) .unwrap(); - let result = key_generations::map_ks_error(sec_level.createOperation( - &key_metadata.key, - &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), - false, - )); - assert!(result.is_err()); - assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); + // The KeyMint v2 API added `CURVE_25519` and specified that "Ed25519 keys only support + // Digest::NONE". However, this was not checked at the time so we can only be strict about + // checking this for more recent implementations. + if get_vsr_api_level() >= 35 { + let result = key_generations::map_ks_error(sec_level.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), + false, + )); + assert!(result.is_err(), "unexpected success for digest {digest:?}"); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); + } } } @@ -432,15 +437,18 @@ fn keystore2_key_owner_validation() { // Client#1: Generate a key and create an operation using generated key. // Wait until the parent notifies to continue. Once the parent notifies, this operation // is expected to be completed successfully. - let mut child_handle = execute_op_run_as_child( - TARGET_CTX, - Domain::APP, - -1, - Some(alias.to_string()), - Uid::from_raw(uid1), - Gid::from_raw(gid1), - ForcedOp(false), - ); + // SAFETY: The test is run in a separate process with no other threads. + let mut child_handle = unsafe { + execute_op_run_as_child( + TARGET_CTX, + Domain::APP, + -1, + Some(alias.to_string()), + Uid::from_raw(uid1), + Gid::from_raw(gid1), + ForcedOp(false), + ) + }; // Wait until (client#1) child process notifies us to continue, so that there will be a key // generated by client#1. @@ -450,6 +458,7 @@ fn keystore2_key_owner_validation() { const APPLICATION_ID_2: u32 = 10602; let uid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2; let gid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2; + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as(TARGET_CTX, Uid::from_raw(uid2), Gid::from_raw(gid2), move || { let keystore2_inst = get_keystore_service(); diff --git a/keystore2/tests/keystore2_client_grant_key_tests.rs b/keystore2/tests/keystore2_client_grant_key_tests.rs index bde872d0..516869a1 100644 --- a/keystore2/tests/keystore2_client_grant_key_tests.rs +++ b/keystore2/tests/keystore2_client_grant_key_tests.rs @@ -114,6 +114,7 @@ fn keystore2_grant_key_with_perm_none() { static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; static GRANTEE_GID: u32 = GRANTEE_UID; + // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let empty_access_vector = KeyPermission::NONE.0; @@ -132,6 +133,7 @@ fn keystore2_grant_key_with_perm_none() { // In grantee context try to load the key, it should fail to load the granted key as it is // granted with empty access vector. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -169,6 +171,7 @@ fn keystore2_grant_get_info_use_key_perm() { static GRANTEE_GID: u32 = GRANTEE_UID; // Generate a key and grant it to a user with GET_INFO|USE key permissions. + // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0; @@ -185,6 +188,7 @@ fn keystore2_grant_get_info_use_key_perm() { }; // In grantee context load the key and try to perform crypto operation. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -251,6 +255,7 @@ fn keystore2_grant_delete_key_success() { static ALIAS: &str = "ks_grant_key_delete_success"; // Generate a key and grant it to a user with DELETE permission. + // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let keystore2 = get_keystore_service(); @@ -270,6 +275,7 @@ fn keystore2_grant_delete_key_success() { }; // Grantee context, delete the key. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -290,6 +296,7 @@ fn keystore2_grant_delete_key_success() { }; // Verify whether key got deleted in grantor's context. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), move || { let keystore2_inst = get_keystore_service(); @@ -325,6 +332,7 @@ fn keystore2_grant_key_fails_with_permission_denied() { static SEC_GRANTEE_GID: u32 = SEC_GRANTEE_UID; // Generate a key and grant it to a user with GET_INFO permission. + // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let keystore2 = get_keystore_service(); @@ -345,6 +353,7 @@ fn keystore2_grant_key_fails_with_permission_denied() { }; // Grantee context, load the granted key and try to grant it to `SEC_GRANTEE_UID` grantee. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -375,6 +384,7 @@ fn keystore2_grant_key_fails_with_permission_denied() { }; // Make sure second grantee shouldn't have access to the above granted key. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -457,6 +467,7 @@ fn keystore2_ungrant_key_success() { static GRANTEE_GID: u32 = GRANTEE_UID; // Generate a key and grant it to a user with GET_INFO permission. + // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let keystore2 = get_keystore_service(); @@ -492,6 +503,7 @@ fn keystore2_ungrant_key_success() { }; // Grantee context, try to load the ungranted key. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -527,6 +539,7 @@ fn keystore2_ungrant_fails_with_non_existing_key_expect_key_not_found_error() { static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; static GRANTEE_GID: u32 = GRANTEE_UID; + // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let keystore2 = get_keystore_service(); @@ -576,6 +589,7 @@ fn keystore2_ungrant_fails_with_non_existing_key_expect_key_not_found_error() { // Make sure grant did not persist, try to access the earlier granted key in grantee context. // Grantee context should fail to load the granted key as its associated key is deleted in // grantor context. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -614,6 +628,7 @@ fn keystore2_grant_key_to_multi_users_success() { static GRANTEE_2_GID: u32 = GRANTEE_2_UID; // Generate a key and grant it to multiple users with GET_INFO|USE permissions. + // SAFETY: The test is run in a separate process with no other threads. let mut grant_keys = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let keystore2 = get_keystore_service(); @@ -636,6 +651,7 @@ fn keystore2_grant_key_to_multi_users_success() { &[(GRANTEE_1_UID, GRANTEE_1_GID), (GRANTEE_2_UID, GRANTEE_2_GID)] { let grant_key_nspace = grant_keys.remove(0); + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -678,6 +694,7 @@ fn keystore2_grant_key_to_multi_users_delete_fails_with_key_not_found_error() { static GRANTEE_2_GID: u32 = GRANTEE_2_UID; // Generate a key and grant it to multiple users with GET_INFO permission. + // SAFETY: The test is run in a separate process with no other threads. let mut grant_keys = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let keystore2 = get_keystore_service(); @@ -699,6 +716,7 @@ fn keystore2_grant_key_to_multi_users_delete_fails_with_key_not_found_error() { // Grantee #1 context let grant_key1_nspace = grant_keys.remove(0); + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -733,6 +751,7 @@ fn keystore2_grant_key_to_multi_users_delete_fails_with_key_not_found_error() { // Grantee #2 context let grant_key2_nspace = grant_keys.remove(0); + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, diff --git a/keystore2/tests/keystore2_client_import_keys_tests.rs b/keystore2/tests/keystore2_client_import_keys_tests.rs index ecba402a..bf787d29 100644 --- a/keystore2/tests/keystore2_client_import_keys_tests.rs +++ b/keystore2/tests/keystore2_client_import_keys_tests.rs @@ -32,10 +32,12 @@ use keystore2_test_utils::{ authorizations, get_keystore_service, key_generations, key_generations::Error, }; -use crate::ffi_test_utils::{create_wrapped_key, create_wrapped_key_additional_auth_data}; +use keystore2_test_utils::ffi_test_utils::{ + create_wrapped_key, create_wrapped_key_additional_auth_data, +}; use crate::keystore2_client_test_utils::{ - encrypt_secure_key, encrypt_transport_key, has_default_keymint, + encrypt_secure_key, encrypt_transport_key, get_vsr_api_level, perform_sample_asym_sign_verify_op, perform_sample_hmac_sign_verify_op, perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, }; @@ -286,7 +288,7 @@ fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() { key_generations::RSA_2048_KEY, )); - if has_default_keymint() { + if key_generations::has_default_keymint() { assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); } else { @@ -304,6 +306,13 @@ fn keystore2_import_ec_key_success() { let alias = format!("ks_ec_key_test_import_1_{}{}", getuid(), 256); + if get_vsr_api_level() < 35 { + // The KeyMint spec was previously not clear as to whether EC_CURVE was optional on import + // of EC keys. However, this was not checked at the time so we can only be strict about + // checking this for implementations at VSR-V or later. + println!("Skipping EC_CURVE on import only strict >= VSR-V"); + return; + } // Don't specify ec-curve. let import_params = authorizations::AuthSetBuilder::new() .no_auth_required() diff --git a/keystore2/tests/keystore2_client_keystore_engine_tests.rs b/keystore2/tests/keystore2_client_keystore_engine_tests.rs new file mode 100644 index 00000000..4651931b --- /dev/null +++ b/keystore2/tests/keystore2_client_keystore_engine_tests.rs @@ -0,0 +1,305 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use nix::unistd::{Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; + +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose, + PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, +}; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, + IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, +}; + +use keystore2_test_utils::{authorizations::AuthSetBuilder, get_keystore_service, run_as}; + +use keystore2_test_utils::ffi_test_utils::perform_crypto_op_using_keystore_engine; + +use openssl::x509::X509; + +fn generate_rsa_key_and_grant_to_user( + keystore2: &binder::Strong<dyn IKeystoreService>, + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: &str, + grantee_uid: i32, + access_vector: i32, +) -> binder::Result<KeyDescriptor> { + let gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::RSA) + .rsa_public_exponent(65537) + .key_size(2048) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .padding_mode(PaddingMode::NONE) + .digest(Digest::NONE); + + let key_metadata = sec_level + .generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + ) + .expect("Failed to generate RSA Key."); + + assert!(key_metadata.certificate.is_some()); + + keystore2.grant(&key_metadata.key, grantee_uid, access_vector) +} + +fn generate_ec_key_and_grant_to_user( + keystore2: &binder::Strong<dyn IKeystoreService>, + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: &str, + grantee_uid: i32, + access_vector: i32, +) -> binder::Result<KeyDescriptor> { + let gen_params = AuthSetBuilder::new() + .no_auth_required() + .algorithm(Algorithm::EC) + .purpose(KeyPurpose::SIGN) + .purpose(KeyPurpose::VERIFY) + .digest(Digest::NONE) + .ec_curve(EcCurve::P_256); + + let key_metadata = sec_level + .generateKey( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }, + None, + &gen_params, + 0, + b"entropy", + ) + .expect("Failed to generate EC Key."); + + assert!(key_metadata.certificate.is_some()); + + keystore2.grant(&key_metadata.key, grantee_uid, access_vector) +} + +fn generate_key_and_grant_to_user( + keystore2: &binder::Strong<dyn IKeystoreService>, + sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + alias: &str, + grantee_uid: u32, + algo: Algorithm, +) -> Result<i64, Box<dyn std::error::Error>> { + let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0 | KeyPermission::DELETE.0; + + assert!(matches!(algo, Algorithm::RSA | Algorithm::EC)); + + let grant_key = match algo { + Algorithm::RSA => generate_rsa_key_and_grant_to_user( + keystore2, + sec_level, + alias, + grantee_uid.try_into().unwrap(), + access_vector, + ) + .unwrap(), + Algorithm::EC => generate_ec_key_and_grant_to_user( + keystore2, + sec_level, + alias, + grantee_uid.try_into().unwrap(), + access_vector, + ) + .unwrap(), + _ => panic!("Unsupported algorithms"), + }; + + assert_eq!(grant_key.domain, Domain::GRANT); + + Ok(grant_key.nspace) +} + +fn perform_crypto_op_using_granted_key( + keystore2: &binder::Strong<dyn IKeystoreService>, + grant_key_nspace: i64, +) { + // Load the granted key from Keystore2-Engine API and perform crypto operations. + assert!(perform_crypto_op_using_keystore_engine(grant_key_nspace).unwrap()); + + // Delete the granted key. + keystore2 + .deleteKey(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + }) + .unwrap(); +} + +#[test] +fn keystore2_perofrm_crypto_op_using_keystore2_engine_rsa_key_success() { + static TARGET_SU_CTX: &str = "u:r:su:s0"; + + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + // Generate a key and grant it to a user with GET_INFO|USE|DELETE key permissions. + // SAFETY: The test is run in a separate process with no other threads. + let grant_key_nspace = unsafe { + run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "keystore2_engine_rsa_key"; + generate_key_and_grant_to_user( + &keystore2, + &sec_level, + alias, + GRANTEE_UID, + Algorithm::RSA, + ) + .unwrap() + }) + }; + + // In grantee context load the key and try to perform crypto operation. + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace); + }, + ) + }; +} + +#[test] +fn keystore2_perofrm_crypto_op_using_keystore2_engine_ec_key_success() { + static TARGET_SU_CTX: &str = "u:r:su:s0"; + + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + // Generate a key and grant it to a user with GET_INFO|USE|DELETE key permissions. + // SAFETY: The test is run in a separate process with no other threads. + let grant_key_nspace = unsafe { + run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "keystore2_engine_ec_test_key"; + generate_key_and_grant_to_user( + &keystore2, + &sec_level, + alias, + GRANTEE_UID, + Algorithm::EC, + ) + .unwrap() + }) + }; + + // In grantee context load the key and try to perform crypto operation. + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace); + }, + ) + }; +} + +#[test] +fn keystore2_perofrm_crypto_op_using_keystore2_engine_pem_pub_key_success() { + static TARGET_SU_CTX: &str = "u:r:su:s0"; + + static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static GRANTEE_GID: u32 = GRANTEE_UID; + + // Generate a key and re-encode it's certificate as PEM and update it and + // grant it to a user with GET_INFO|USE|DELETE key permissions. + // SAFETY: The test is run in a separate process with no other threads. + let grant_key_nspace = unsafe { + run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let alias = "keystore2_engine_rsa_pem_pub_key"; + let grant_key_nspace = generate_key_and_grant_to_user( + &keystore2, + &sec_level, + alias, + GRANTEE_UID, + Algorithm::RSA, + ) + .unwrap(); + + // Update certificate with encodeed PEM data. + let key_entry_response = keystore2 + .getKeyEntry(&KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(alias.to_string()), + blob: None, + }) + .unwrap(); + let cert_bytes = key_entry_response.metadata.certificate.as_ref().unwrap(); + let cert = X509::from_der(cert_bytes.as_ref()).unwrap(); + let cert_pem = cert.to_pem().unwrap(); + keystore2 + .updateSubcomponent(&key_entry_response.metadata.key, Some(&cert_pem), None) + .expect("updateSubcomponent failed."); + + grant_key_nspace + }) + }; + + // In grantee context load the key and try to perform crypto operation. + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as( + GRANTEE_CTX, + Uid::from_raw(GRANTEE_UID), + Gid::from_raw(GRANTEE_GID), + move || { + let keystore2 = get_keystore_service(); + perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace); + }, + ) + }; +} diff --git a/keystore2/tests/keystore2_client_list_entries_tests.rs b/keystore2/tests/keystore2_client_list_entries_tests.rs index 3b656c3d..8b3f7001 100644 --- a/keystore2/tests/keystore2_client_list_entries_tests.rs +++ b/keystore2/tests/keystore2_client_list_entries_tests.rs @@ -23,7 +23,7 @@ use android_system_keystore2::aidl::android::system::keystore2::{ KeyPermission::KeyPermission, ResponseCode::ResponseCode, }; -use crate::keystore2_client_test_utils::delete_app_key; +use crate::keystore2_client_test_utils::{delete_all_entries, delete_app_key, verify_aliases}; use keystore2_test_utils::{get_keystore_service, key_generations, key_generations::Error, run_as}; /// Try to find a key with given key parameters using `listEntries` API. @@ -60,6 +60,7 @@ fn keystore2_list_entries_success() { static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; static GRANTEE_GID: u32 = GRANTEE_UID; + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let keystore2 = get_keystore_service(); @@ -113,6 +114,7 @@ fn keystore2_list_entries_success() { }; // In user context validate list of key entries associated with it. + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -138,7 +140,7 @@ fn keystore2_list_entries_success() { let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); assert_eq!(1, key_descriptors.len()); - let key = key_descriptors.get(0).unwrap(); + let key = key_descriptors.first().unwrap(); assert_eq!(key.alias, Some(alias)); assert_eq!(key.nspace, GRANTEE_UID.try_into().unwrap()); assert_eq!(key.domain, Domain::APP); @@ -161,6 +163,7 @@ fn keystore2_list_entries_fails_perm_denied() { let agid = 91 * AID_USER_OFFSET + 10001; static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || { let keystore2 = get_keystore_service(); @@ -198,6 +201,7 @@ fn keystore2_list_entries_with_long_aliases_success() { static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; static CLIENT_GID: u32 = CLIENT_UID; + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || { let keystore2 = get_keystore_service(); @@ -251,3 +255,471 @@ fn keystore2_list_entries_with_long_aliases_success() { }) }; } + +/// Import large number of Keystore entries with long aliases such that the +/// aliases list would exceed the binder transaction size limit. +/// Try to list aliases of all the entries in the keystore using `listEntriesBatched` API. +#[test] +fn keystore2_list_entries_batched_with_long_aliases_success() { + static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const USER_ID: u32 = 92; + const APPLICATION_ID: u32 = 10002; + static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static CLIENT_GID: u32 = CLIENT_UID; + + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Make sure there are no keystore entries exist before adding new entries. + delete_all_entries(&keystore2); + + // Import 100 keys with aliases of length 6000. + let mut imported_key_aliases = + key_generations::import_aes_keys(&sec_level, "X".repeat(6000), 1..101).unwrap(); + assert_eq!( + keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + 100, + "Error while importing keys" + ); + + let mut start_past_alias = None; + let mut alias; + while !imported_key_aliases.is_empty() { + let key_descriptors = + keystore2.listEntriesBatched(Domain::APP, -1, start_past_alias).unwrap(); + + // Check retrieved key entries list is a subset of imported keys list. + assert!(key_descriptors + .iter() + .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap()))); + + alias = key_descriptors.last().unwrap().alias.clone().unwrap(); + start_past_alias = Some(alias.as_ref()); + // Delete the listed key entries from imported keys list. + key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| { + assert!(imported_key_aliases.remove(&alias)); + }); + } + + assert!(imported_key_aliases.is_empty()); + delete_all_entries(&keystore2); + assert_eq!( + keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + 0, + "Error while doing cleanup" + ); + }) + }; +} + +/// Import keys from multiple processes with same user context and try to list the keystore entries +/// using `listEntriesBatched` API. +/// - Create two processes sharing user-id. +/// - From process-1, import 3 keys and try to list the keys using `listEntriesBatched` +/// without `startingPastAlias`, it should list all the 3 entries. +/// - From process-2, import another 5 keys and try to list the keys using `listEntriesBatched` +/// with the alias of the last key listed in process-1 as `startingPastAlias`. It should list +/// all the entries whose alias is greater than the provided `startingPastAlias`. +/// - From process-2 try to list all entries accessible to it by using `listEntriesBatched` with +/// `startingPastAlias` as None. It should list all the keys imported in process-1 and process-2. +#[test] +fn keystore2_list_entries_batched_with_multi_procs_success() { + static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const USER_ID: u32 = 92; + const APPLICATION_ID: u32 = 10002; + static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static CLIENT_GID: u32 = CLIENT_UID; + static ALIAS_PREFIX: &str = "key_test_batch_list"; + + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Make sure there are no keystore entries exist before adding new entries. + delete_all_entries(&keystore2); + + // Import 3 keys with below aliases - + // [key_test_batch_list_1, key_test_batch_list_2, key_test_batch_list_3] + let imported_key_aliases = + key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 1..4) + .unwrap(); + assert_eq!( + keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + 3, + "Error while importing keys" + ); + + // List all entries in keystore for this user-id. + let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap(); + assert_eq!(key_descriptors.len(), 3); + + // Makes sure all listed aliases are matching with imported keys aliases. + assert!(key_descriptors + .iter() + .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap()))); + }) + }; + + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Import another 5 keys with below aliases - + // [ key_test_batch_list_4, key_test_batch_list_5, key_test_batch_list_6, + // key_test_batch_list_7, key_test_batch_list_8 ] + let mut imported_key_aliases = + key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 4..9) + .unwrap(); + + // Above context already 3 keys are imported, in this context 5 keys are imported, + // total 8 keystore entries are expected to be present in Keystore for this user-id. + assert_eq!( + keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + 8, + "Error while importing keys" + ); + + // List keystore entries with `start_past_alias` as "key_test_batch_list_3". + // `listEntriesBatched` should list all the keystore entries with + // alias > "key_test_batch_list_3". + let key_descriptors = keystore2 + .listEntriesBatched(Domain::APP, -1, Some("key_test_batch_list_3")) + .unwrap(); + assert_eq!(key_descriptors.len(), 5); + + // Make sure above listed aliases are matching with imported keys aliases. + assert!(key_descriptors + .iter() + .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap()))); + + // List all keystore entries with `start_past_alias` as `None`. + // `listEntriesBatched` should list all the keystore entries. + let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap(); + assert_eq!(key_descriptors.len(), 8); + + // Include previously imported keys aliases as well + imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_1"); + imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_2"); + imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_3"); + + // Make sure all the above listed aliases are matching with imported keys aliases. + assert!(key_descriptors + .iter() + .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap()))); + + delete_all_entries(&keystore2); + assert_eq!( + keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + 0, + "Error while doing cleanup" + ); + }) + }; +} + +#[test] +fn keystore2_list_entries_batched_with_empty_keystore_success() { + static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const USER_ID: u32 = 92; + const APPLICATION_ID: u32 = 10002; + static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static CLIENT_GID: u32 = CLIENT_UID; + + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || { + let keystore2 = get_keystore_service(); + + // Make sure there are no keystore entries exist before adding new entries. + delete_all_entries(&keystore2); + + // List all entries in keystore for this user-id, pass startingPastAlias = None + let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap(); + assert_eq!(key_descriptors.len(), 0); + + // List all entries in keystore for this user-id, pass startingPastAlias = <random value> + let key_descriptors = + keystore2.listEntriesBatched(Domain::APP, -1, Some("startingPastAlias")).unwrap(); + assert_eq!(key_descriptors.len(), 0); + }) + }; +} + +/// Import a key with SELINUX as domain, list aliases using `listEntriesBatched`. +/// Test should successfully list the imported key. +#[test] +fn keystore2_list_entries_batched_with_selinux_domain_success() { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + let alias = "test_selinux_key_list_alias_batched"; + let _result = keystore2.deleteKey(&KeyDescriptor { + domain: Domain::SELINUX, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: Some(alias.to_string()), + blob: None, + }); + + let initial_count = keystore2 + .getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE) + .unwrap(); + + key_generations::import_aes_key( + &sec_level, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias.to_string()), + ) + .unwrap(); + + assert_eq!( + keystore2 + .getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE) + .unwrap(), + initial_count + 1, + "Error while getting number of keystore entries accessible." + ); + + let key_descriptors = keystore2 + .listEntriesBatched(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, None) + .unwrap(); + assert_eq!(key_descriptors.len(), (initial_count + 1) as usize); + + let count = + key_descriptors.into_iter().map(|key| key.alias.unwrap()).filter(|a| a == alias).count(); + assert_eq!(count, 1); + + keystore2 + .deleteKey(&KeyDescriptor { + domain: Domain::SELINUX, + nspace: key_generations::SELINUX_SHELL_NAMESPACE, + alias: Some(alias.to_string()), + blob: None, + }) + .unwrap(); +} + +#[test] +fn keystore2_list_entries_batched_validate_count_and_order_success() { + static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + const USER_ID: u32 = 92; + const APPLICATION_ID: u32 = 10002; + static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static CLIENT_GID: u32 = CLIENT_UID; + static ALIAS_PREFIX: &str = "key_test_batch_list"; + + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || { + let keystore2 = get_keystore_service(); + let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + + // Make sure there are no keystore entries exist before adding new entries. + delete_all_entries(&keystore2); + + // Import keys with below mentioned aliases - + // [ + // key_test_batch_list_1, + // key_test_batch_list_2, + // key_test_batch_list_3, + // key_test_batch_list_4, + // key_test_batch_list_5, + // key_test_batch_list_10, + // key_test_batch_list_11, + // key_test_batch_list_12, + // key_test_batch_list_21, + // key_test_batch_list_22, + // ] + let _imported_key_aliases = + key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 1..6) + .unwrap(); + assert_eq!( + keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + 5, + "Error while importing keys" + ); + let _imported_key_aliases = + key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 10..13) + .unwrap(); + assert_eq!( + keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + 8, + "Error while importing keys" + ); + let _imported_key_aliases = + key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 21..23) + .unwrap(); + assert_eq!( + keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + 10, + "Error while importing keys" + ); + + // List the aliases using given `startingPastAlias` and verify the listed + // aliases with the expected list of aliases. + verify_aliases(&keystore2, Some(format!("{}{}", ALIAS_PREFIX, "_5").as_str()), vec![]); + + verify_aliases( + &keystore2, + Some(format!("{}{}", ALIAS_PREFIX, "_4").as_str()), + vec![ALIAS_PREFIX.to_owned() + "_5"], + ); + + verify_aliases( + &keystore2, + Some(format!("{}{}", ALIAS_PREFIX, "_3").as_str()), + vec![ALIAS_PREFIX.to_owned() + "_4", ALIAS_PREFIX.to_owned() + "_5"], + ); + + verify_aliases( + &keystore2, + Some(format!("{}{}", ALIAS_PREFIX, "_2").as_str()), + vec![ + ALIAS_PREFIX.to_owned() + "_21", + ALIAS_PREFIX.to_owned() + "_22", + ALIAS_PREFIX.to_owned() + "_3", + ALIAS_PREFIX.to_owned() + "_4", + ALIAS_PREFIX.to_owned() + "_5", + ], + ); + + verify_aliases( + &keystore2, + Some(format!("{}{}", ALIAS_PREFIX, "_1").as_str()), + vec![ + ALIAS_PREFIX.to_owned() + "_10", + ALIAS_PREFIX.to_owned() + "_11", + ALIAS_PREFIX.to_owned() + "_12", + ALIAS_PREFIX.to_owned() + "_2", + ALIAS_PREFIX.to_owned() + "_21", + ALIAS_PREFIX.to_owned() + "_22", + ALIAS_PREFIX.to_owned() + "_3", + ALIAS_PREFIX.to_owned() + "_4", + ALIAS_PREFIX.to_owned() + "_5", + ], + ); + + verify_aliases( + &keystore2, + Some(ALIAS_PREFIX), + vec![ + ALIAS_PREFIX.to_owned() + "_1", + ALIAS_PREFIX.to_owned() + "_10", + ALIAS_PREFIX.to_owned() + "_11", + ALIAS_PREFIX.to_owned() + "_12", + ALIAS_PREFIX.to_owned() + "_2", + ALIAS_PREFIX.to_owned() + "_21", + ALIAS_PREFIX.to_owned() + "_22", + ALIAS_PREFIX.to_owned() + "_3", + ALIAS_PREFIX.to_owned() + "_4", + ALIAS_PREFIX.to_owned() + "_5", + ], + ); + + verify_aliases( + &keystore2, + None, + vec![ + ALIAS_PREFIX.to_owned() + "_1", + ALIAS_PREFIX.to_owned() + "_10", + ALIAS_PREFIX.to_owned() + "_11", + ALIAS_PREFIX.to_owned() + "_12", + ALIAS_PREFIX.to_owned() + "_2", + ALIAS_PREFIX.to_owned() + "_21", + ALIAS_PREFIX.to_owned() + "_22", + ALIAS_PREFIX.to_owned() + "_3", + ALIAS_PREFIX.to_owned() + "_4", + ALIAS_PREFIX.to_owned() + "_5", + ], + ); + }) + }; +} + +/// Try to list the key entries with domain SELINUX from user context where user doesn't possesses +/// `GET_INFO` permission for specified namespace. Test should fail to list key entries with error +/// response code `PERMISSION_DENIED`. +#[test] +fn keystore2_list_entries_batched_fails_perm_denied() { + let auid = 91 * AID_USER_OFFSET + 10001; + let agid = 91 * AID_USER_OFFSET + 10001; + static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error(keystore2.listEntriesBatched( + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + None, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); + }) + }; +} + +/// Try to list key entries with domain BLOB. Test should fail with error response code +/// `INVALID_ARGUMENT`. +#[test] +fn keystore2_list_entries_batched_fails_invalid_arg() { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error(keystore2.listEntriesBatched( + Domain::BLOB, + key_generations::SELINUX_SHELL_NAMESPACE, + None, + )); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); +} + +/// Try to get the number of key entries with domain SELINUX from user context where user doesn't +/// possesses `GET_INFO` permission for specified namespace. Test should fail to list key entries +/// with error response code `PERMISSION_DENIED`. +#[test] +fn keystore2_get_number_of_entries_fails_perm_denied() { + let auid = 91 * AID_USER_OFFSET + 10001; + let agid = 91 * AID_USER_OFFSET + 10001; + static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error( + keystore2 + .getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE), + ); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); + }) + }; +} + +/// Try to get number of key entries with domain BLOB. Test should fail with error response code +/// `INVALID_ARGUMENT`. +#[test] +fn keystore2_get_number_of_entries_fails_invalid_arg() { + let keystore2 = get_keystore_service(); + + let result = key_generations::map_ks_error( + keystore2.getNumberOfEntries(Domain::BLOB, key_generations::SELINUX_SHELL_NAMESPACE), + ); + assert!(result.is_err()); + assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); +} diff --git a/keystore2/tests/keystore2_client_operation_tests.rs b/keystore2/tests/keystore2_client_operation_tests.rs index 97149004..89b5a319 100644 --- a/keystore2/tests/keystore2_client_operation_tests.rs +++ b/keystore2/tests/keystore2_client_operation_tests.rs @@ -36,7 +36,11 @@ use crate::keystore2_client_test_utils::{ /// Create `max_ops` number child processes with the given context and perform an operation under each /// child process. -pub fn create_operations( +/// +/// # Safety +/// +/// Must be called from a process with no other threads. +pub unsafe fn create_operations( target_ctx: &'static str, forced_op: ForcedOp, max_ops: i32, @@ -45,8 +49,8 @@ pub fn create_operations( let base_gid = 99 * AID_USER_OFFSET + 10001; let base_uid = 99 * AID_USER_OFFSET + 10001; (0..max_ops) - .into_iter() - .map(|i| { + // SAFETY: The caller guarantees that there are no other threads. + .map(|i| unsafe { execute_op_run_as_child( target_ctx, Domain::APP, @@ -88,7 +92,8 @@ fn keystore2_backend_busy_test() { const MAX_OPS: i32 = 100; static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; - let mut child_handles = create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS); + // SAFETY: The test is run in a separate process with no other threads. + let mut child_handles = unsafe { create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS) }; // Wait until all child procs notifies us to continue, // so that there are definitely enough operations outstanding to trigger a BACKEND_BUSY. @@ -121,7 +126,8 @@ fn keystore2_forced_op_after_backendbusy_test() { static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; // Create regular operations. - let mut child_handles = create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS); + // SAFETY: The test is run in a separate process with no other threads. + let mut child_handles = unsafe { create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS) }; // Wait until all child procs notifies us to continue, so that there are enough // operations outstanding to trigger a BACKEND_BUSY. @@ -132,6 +138,7 @@ fn keystore2_forced_op_after_backendbusy_test() { // Create a forced operation. let auid = 99 * AID_USER_OFFSET + 10604; let agid = 99 * AID_USER_OFFSET + 10604; + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( key_generations::TARGET_VOLD_CTX, @@ -204,15 +211,18 @@ fn keystore2_max_forced_ops_test() { // Create initial forced operation in a child process // and wait for the parent to notify to perform operation. let alias = format!("ks_forced_op_key_{}", getuid()); - let mut first_op_handle = execute_op_run_as_child( - key_generations::TARGET_SU_CTX, - Domain::SELINUX, - key_generations::SELINUX_SHELL_NAMESPACE, - Some(alias), - Uid::from_raw(auid), - Gid::from_raw(agid), - ForcedOp(true), - ); + // SAFETY: The test is run in a separate process with no other threads. + let mut first_op_handle = unsafe { + execute_op_run_as_child( + key_generations::TARGET_SU_CTX, + Domain::SELINUX, + key_generations::SELINUX_SHELL_NAMESPACE, + Some(alias), + Uid::from_raw(auid), + Gid::from_raw(agid), + ForcedOp(true), + ) + }; // Wait until above child proc notifies us to continue, so that there is definitely a forced // operation outstanding to perform a operation. @@ -220,7 +230,8 @@ fn keystore2_max_forced_ops_test() { // Create MAX_OPS number of forced operations. let mut child_handles = - create_operations(key_generations::TARGET_SU_CTX, ForcedOp(true), MAX_OPS); + // SAFETY: The test is run in a separate process with no other threads. + unsafe { create_operations(key_generations::TARGET_SU_CTX, ForcedOp(true), MAX_OPS) }; // Wait until all child procs notifies us to continue, so that there are enough operations // outstanding to trigger a BACKEND_BUSY. @@ -283,15 +294,18 @@ fn keystore2_ops_prune_test() { // Create an operation in an untrusted_app context. Wait until the parent notifies to continue. // Once the parent notifies, this operation is expected to be completed successfully. let alias = format!("ks_reg_op_key_{}", getuid()); - let mut child_handle = execute_op_run_as_child( - TARGET_CTX, - Domain::APP, - -1, - Some(alias), - Uid::from_raw(uid), - Gid::from_raw(gid), - ForcedOp(false), - ); + // SAFETY: The test is run in a separate process with no other threads. + let mut child_handle = unsafe { + execute_op_run_as_child( + TARGET_CTX, + Domain::APP, + -1, + Some(alias), + Uid::from_raw(uid), + Gid::from_raw(gid), + ForcedOp(false), + ) + }; // Wait until child process notifies us to continue, so that an operation from child process is // outstanding to complete the operation. @@ -312,7 +326,6 @@ fn keystore2_ops_prune_test() { // Create multiple operations in this process to trigger cannibalizing sibling operations. let mut ops: Vec<binder::Result<CreateOperationResponse>> = (0..MAX_OPS) - .into_iter() .map(|_| { sec_level.createOperation( &key_metadata.key, @@ -379,6 +392,7 @@ fn keystore2_forced_op_perm_denied_test() { let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; for context in TARGET_CTXS.iter() { + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as(context, Uid::from_raw(uid), Gid::from_raw(gid), move || { let alias = format!("ks_app_forced_op_test_key_{}", getuid()); @@ -408,6 +422,7 @@ fn keystore2_forced_op_success_test() { let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as(TARGET_CTX, Uid::from_raw(uid), Gid::from_raw(gid), move || { let alias = format!("ks_vold_forced_op_key_{}", getuid()); diff --git a/keystore2/tests/keystore2_client_test_utils.rs b/keystore2/tests/keystore2_client_test_utils.rs index 58e6b7d3..7534da3a 100644 --- a/keystore2/tests/keystore2_client_test_utils.rs +++ b/keystore2/tests/keystore2_client_test_utils.rs @@ -15,9 +15,14 @@ use nix::unistd::{Gid, Uid}; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::process::{Command, Output}; + +use openssl::bn::BigNum; use openssl::encrypt::Encrypter; use openssl::error::ErrorStack; use openssl::hash::MessageDigest; +use openssl::nid::Nid; use openssl::pkey::PKey; use openssl::pkey::Public; use openssl::rsa::Padding; @@ -66,6 +71,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 +81,27 @@ 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::<dyn IPackageManagerNative>(PACKAGE_MANAGER_NATIVE_SERVICE) + .expect("Failed to get package manager native service."); + + pm.hasSystemFeature(DEVICE_ID_ATTESTATION_FEATURE, 0).expect("hasSystemFeature failed.") +} + +/// Determines whether to skip device id attestation tests on GSI build with API level < 34. +pub fn skip_device_id_attest_tests() -> bool { + // b/298586194, there are some devices launched with Android T, and they will be receiving + // only system update and not vendor update, newly added attestation properties + // (ro.product.*_for_attestation) reading logic would not be available for such devices + // hence skipping this test for such scenario. + + // This file is only present on GSI builds. + let gsi_marker = PathBuf::from("/system/system_ext/etc/init/init.gsi.rc"); + + get_vsr_api_level() < 34 && gsi_marker.as_path().is_file() +} + #[macro_export] macro_rules! skip_test_if_no_app_attest_key_feature { () => { @@ -84,10 +111,31 @@ macro_rules! skip_test_if_no_app_attest_key_feature { }; } -/// 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") - .expect("Could not check for declared keymint interface") +#[macro_export] +macro_rules! skip_test_if_no_device_id_attestation_feature { + () => { + if !device_id_attestation_feature_exists() { + return; + } + }; +} + +#[macro_export] +macro_rules! skip_device_id_attestation_tests { + () => { + if skip_device_id_attest_tests() { + return; + } + }; +} + +#[macro_export] +macro_rules! skip_tests_if_keymaster_impl_present { + () => { + if !key_generations::has_default_keymint() { + return; + } + }; } /// Generate EC key and grant it to the list of users with given access vector. @@ -246,7 +294,11 @@ pub fn perform_sample_asym_sign_verify_op( } /// Create new operation on child proc and perform simple operation after parent notification. -pub fn execute_op_run_as_child( +/// +/// # Safety +/// +/// Must only be called from a single-threaded process. +pub unsafe fn execute_op_run_as_child( target_ctx: &'static str, domain: Domain, nspace: i64, @@ -255,6 +307,7 @@ pub fn execute_op_run_as_child( agid: Gid, forced_op: ForcedOp, ) -> run_as::ChildHandle<TestOutcome, BarrierReached> { + // SAFETY: The caller guarantees that there are no other threads. unsafe { run_as::run_as_child(target_ctx, auid, agid, move |reader, writer| { let result = key_generations::map_ks_error(create_signing_operation( @@ -376,6 +429,17 @@ pub fn delete_app_key( }) } +/// Deletes all entries from keystore. +pub fn delete_all_entries(keystore2: &binder::Strong<dyn IKeystoreService>) { + while keystore2.getNumberOfEntries(Domain::APP, -1).unwrap() != 0 { + let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| { + delete_app_key(keystore2, &alias).unwrap(); + }); + } + assert!(keystore2.getNumberOfEntries(Domain::APP, -1).unwrap() == 0); +} + /// Encrypt the secure key with given transport key. pub fn encrypt_secure_key( sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, @@ -417,3 +481,126 @@ pub fn encrypt_transport_key( Ok(encoded.to_vec()) } + +/// List aliases using given `startingPastAlias` and verify that the fetched list is matching with +/// the expected list of aliases. +pub fn verify_aliases( + keystore2: &binder::Strong<dyn IKeystoreService>, + starting_past_alias: Option<&str>, + expected_aliases: Vec<String>, +) { + let key_descriptors = + keystore2.listEntriesBatched(Domain::APP, -1, starting_past_alias).unwrap(); + + assert_eq!(key_descriptors.len(), expected_aliases.len()); + assert!(key_descriptors + .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<u8> { + match rustutils::system_properties::read(name) { + Ok(Some(value)) => { + return value.as_bytes().to_vec(); + } + _ => { + vec![] + } + } +} + +fn get_integer_system_prop(name: &str) -> Option<i32> { + let val = get_system_prop(name); + if val.is_empty() { + return None; + } + let val = std::str::from_utf8(&val).ok()?; + val.parse::<i32>().ok() +} + +pub fn get_vsr_api_level() -> i32 { + if let Some(api_level) = get_integer_system_prop("ro.vendor.api_level") { + return api_level; + } + + let vendor_api_level = get_integer_system_prop("ro.board.api_level") + .or_else(|| get_integer_system_prop("ro.board.first_api_level")); + let product_api_level = get_integer_system_prop("ro.product.first_api_level") + .or_else(|| get_integer_system_prop("ro.build.version.sdk")); + + match (vendor_api_level, product_api_level) { + (Some(v), Some(p)) => std::cmp::min(v, p), + (Some(v), None) => v, + (None, Some(p)) => p, + _ => panic!("Could not determine VSR API level"), + } +} + +/// Determines whether the SECOND-IMEI can be used as device attest-id. +pub fn is_second_imei_id_attestation_required( + keystore2: &binder::Strong<dyn IKeystoreService>, +) -> bool { + keystore2.getInterfaceVersion().unwrap() >= 3 && get_vsr_api_level() > 33 +} + +/// Run a service command and collect the output. +pub fn run_service_command(command: &[&str]) -> std::io::Result<Output> { + Command::new("cmd").args(command).output() +} + +/// Get IMEI from telephony service. +pub fn get_imei(slot: i32) -> Option<Vec<u8>> { + 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<Vec<u8>> { + match attest_id { + Tag::ATTESTATION_ID_IMEI => get_imei(0), + Tag::ATTESTATION_ID_SECOND_IMEI => get_imei(1), + Tag::ATTESTATION_ID_SERIAL => Some(get_system_prop(format!("ro.{}", prop_name).as_str())), + _ => { + let prop_val = + get_system_prop(format!("ro.product.{}_for_attestation", prop_name).as_str()); + if !prop_val.is_empty() { + Some(prop_val) + } else { + let prop_val = get_system_prop(format!("ro.product.vendor.{}", prop_name).as_str()); + if !prop_val.is_empty() { + Some(prop_val) + } else { + Some(get_system_prop(format!("ro.product.{}", prop_name).as_str())) + } + } + } + } +} + +pub fn verify_certificate_subject_name(cert_bytes: &[u8], expected_subject: &[u8]) { + let cert = X509::from_der(cert_bytes).unwrap(); + let subject = cert.subject_name(); + let cn = subject.entries_by_nid(Nid::COMMONNAME).next().unwrap(); + assert_eq!(cn.data().as_slice(), expected_subject); +} + +pub fn verify_certificate_serial_num(cert_bytes: &[u8], expected_serial_num: &BigNum) { + let cert = X509::from_der(cert_bytes).unwrap(); + let serial_num = cert.serial_number(); + assert_eq!(serial_num.to_bn().as_ref().unwrap(), expected_serial_num); +} diff --git a/keystore2/tests/keystore2_client_tests.rs b/keystore2/tests/keystore2_client_tests.rs index 07a298a8..a0c140a0 100644 --- a/keystore2/tests/keystore2_client_tests.rs +++ b/keystore2/tests/keystore2_client_tests.rs @@ -12,17 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod ffi_test_utils; pub mod keystore2_client_3des_key_tests; pub mod keystore2_client_aes_key_tests; pub mod keystore2_client_attest_key_tests; +pub mod keystore2_client_authorizations_tests; pub mod keystore2_client_delete_key_tests; +pub mod keystore2_client_device_unique_attestation_tests; pub mod keystore2_client_ec_key_tests; pub mod keystore2_client_grant_key_tests; pub mod keystore2_client_hmac_key_tests; pub mod keystore2_client_import_keys_tests; pub mod keystore2_client_key_agreement_tests; pub mod keystore2_client_key_id_domain_tests; +pub mod keystore2_client_keystore_engine_tests; pub mod keystore2_client_list_entries_tests; pub mod keystore2_client_operation_tests; pub mod keystore2_client_rsa_key_tests; diff --git a/keystore2/tests/keystore2_client_update_subcomponent_tests.rs b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs index 0be092f8..d9576a84 100644 --- a/keystore2/tests/keystore2_client_update_subcomponent_tests.rs +++ b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs @@ -167,6 +167,7 @@ fn keystore2_update_subcomponent_fails_permission_denied() { static GRANTEE_2_GID: u32 = GRANTEE_2_UID; // Generate a key and grant it to multiple users with different access permissions. + // SAFETY: The test is run in a separate process with no other threads. let mut granted_keys = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { let keystore2 = get_keystore_service(); @@ -205,6 +206,7 @@ fn keystore2_update_subcomponent_fails_permission_denied() { // Grantee context, try to update the key public certs, permission denied error is expected. let granted_key1_nspace = granted_keys.remove(0); + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, @@ -234,6 +236,7 @@ fn keystore2_update_subcomponent_fails_permission_denied() { // Grantee context, update granted key public certs. Update should happen successfully. let granted_key2_nspace = granted_keys.remove(0); + // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as( GRANTEE_CTX, diff --git a/keystore2/tests/legacy_blobs/Android.bp b/keystore2/tests/legacy_blobs/Android.bp index 92f2cc34..0f310f51 100644 --- a/keystore2/tests/legacy_blobs/Android.bp +++ b/keystore2/tests/legacy_blobs/Android.bp @@ -31,17 +31,17 @@ rust_test { test_config: "AndroidTest.xml", rustlibs: [ - "libkeystore2_with_test_utils", - "libkeystore2_crypto_rust", - "android.security.maintenance-rust", "android.security.authorization-rust", - "librustutils", - "libkeystore2_test_utils", - "libnix", + "android.security.maintenance-rust", "libanyhow", "libbinder_rs", + "libkeystore2_crypto_rust", + "libkeystore2_test_utils", + "libkeystore2_with_test_utils", "liblazy_static", "liblibc", + "libnix", + "librustutils", "libserde", "libthiserror", ], diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs index 63122fe0..3be99ee3 100644 --- a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs +++ b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs @@ -25,12 +25,10 @@ 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_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::IKeystoreMaintenance; use android_security_authorization::aidl::android::security::authorization::{ - IKeystoreAuthorization::IKeystoreAuthorization, LockScreenEvent::LockScreenEvent, + IKeystoreAuthorization::IKeystoreAuthorization, }; use keystore2::key_parameter::KeyParameter as KsKeyparameter; @@ -48,6 +46,10 @@ static USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance"; static AUTH_SERVICE_NAME: &str = "android.security.authorization"; const SELINUX_SHELL_NAMESPACE: i64 = 1; +fn rkp_only() -> bool { + matches!(rustutils::system_properties::read("remote_provisioning.tee.rkp_only"), Ok(Some(v)) if v == "1") +} + fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> { binder::get_interface(USER_MANAGER_SERVICE_NAME).unwrap() } @@ -164,19 +166,19 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT) .unwrap(); // Generate Key BLOB and prepare legacy keystore blob files. - let att_challenge: &[u8] = b"foo"; + let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") }; let key_metadata = key_generations::generate_ec_p256_signing_key( &sec_level, Domain::BLOB, SELINUX_SHELL_NAMESPACE, None, - Some(att_challenge), + att_challenge, ) .expect("Failed to generate key blob"); // Create keystore file layout for user_99. let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); + let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); let super_key = TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()); @@ -214,14 +216,12 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { .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(); + if let Some(chain) = key_metadata.certificateChain.as_ref() { + let mut path_buf = PathBuf::from("/data/misc/keystore/user_99"); + path_buf.push("9910001_CACERT_authbound"); + if !path_buf.as_path().is_file() { + make_cert_blob_file(path_buf.as_path(), chain).unwrap(); + } } // Keystore2 disables the legacy importer when it finds the legacy database empty. @@ -231,8 +231,7 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { keystore2_restart_service(); let auth_service = get_authorization(); - match auth_service.onLockScreenEvent(LockScreenEvent::UNLOCK, 99, Some(PASSWORD), None) - { + match auth_service.onDeviceUnlocked(99, Some(PASSWORD)) { Ok(result) => { println!("Unlock Result: {:?}", result); } @@ -241,9 +240,6 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { } } - 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); @@ -252,7 +248,7 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { KeygenResult { cert: key_metadata.certificate.unwrap(), - cert_chain: key_metadata.certificateChain.unwrap(), + cert_chain: key_metadata.certificateChain.unwrap_or_default(), key_parameters: key_params, } }) @@ -281,7 +277,7 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { gen_key_result.cert ); assert_eq!( - key_entry_response.metadata.certificateChain.unwrap(), + key_entry_response.metadata.certificateChain.unwrap_or_default(), gen_key_result.cert_chain ); assert_eq!(key_entry_response.metadata.key.domain, Domain::KEY_ID); @@ -421,19 +417,19 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT) .unwrap(); // Generate Key BLOB and prepare legacy keystore blob files. - let att_challenge: &[u8] = b"foo"; + let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") }; let key_metadata = key_generations::generate_ec_p256_signing_key( &sec_level, Domain::BLOB, SELINUX_SHELL_NAMESPACE, None, - Some(att_challenge), + att_challenge, ) .expect("Failed to generate key blob"); // Create keystore file layout for user_98. let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap()); + let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); let super_key = TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()); @@ -474,15 +470,12 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { .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(); + if let Some(chain) = key_metadata.certificateChain.as_ref() { + let mut path_buf = PathBuf::from("/data/misc/keystore/user_98"); + path_buf.push("9810001_CACERT_authboundcertenc"); + if !path_buf.as_path().is_file() { + make_encrypted_ca_cert_file(path_buf.as_path(), &super_key, chain).unwrap(); + } } // Keystore2 disables the legacy importer when it finds the legacy database empty. @@ -492,8 +485,7 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { keystore2_restart_service(); let auth_service = get_authorization(); - match auth_service.onLockScreenEvent(LockScreenEvent::UNLOCK, 98, Some(PASSWORD), None) - { + match auth_service.onDeviceUnlocked(98, Some(PASSWORD)) { Ok(result) => { println!("Unlock Result: {:?}", result); } @@ -502,9 +494,6 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { } } - 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); @@ -513,7 +502,7 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { KeygenResult { cert: key_metadata.certificate.unwrap(), - cert_chain: key_metadata.certificateChain.unwrap(), + cert_chain: key_metadata.certificateChain.unwrap_or_default(), key_parameters: key_params, } }) @@ -542,7 +531,7 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { gen_key_result.cert ); assert_eq!( - key_entry_response.metadata.certificateChain.unwrap(), + key_entry_response.metadata.certificateChain.unwrap_or_default(), gen_key_result.cert_chain ); diff --git a/keystore2/watchdog/Android.bp b/keystore2/watchdog/Android.bp new file mode 100644 index 00000000..5074388c --- /dev/null +++ b/keystore2/watchdog/Android.bp @@ -0,0 +1,49 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "system_security_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_security_license"], +} + +rust_defaults { + name: "libwatchdog_defaults", + crate_name: "watchdog_rs", + srcs: ["src/lib.rs"], + rustlibs: [ + "liblog_rust", + ], +} + +rust_library { + name: "libwatchdog_rs", + defaults: ["libwatchdog_defaults"], + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], +} + +rust_test { + name: "libwatchdog_rs.test", + defaults: ["libwatchdog_defaults"], + test_suites: ["general-tests"], + rustlibs: [ + "libandroid_logger", + ], +} diff --git a/keystore2/src/watchdog.rs b/keystore2/watchdog/src/lib.rs index 01043c55..fa4620a8 100644 --- a/keystore2/src/watchdog.rs +++ b/keystore2/watchdog/src/lib.rs @@ -335,7 +335,7 @@ mod tests { android_logger::init_once( android_logger::Config::default() .with_tag("keystore2_watchdog_tests") - .with_min_level(log::Level::Debug), + .with_max_level(log::LevelFilter::Debug), ); let wd = Watchdog::new(Watchdog::NOISY_REPORT_TIMEOUT.checked_mul(3).unwrap()); diff --git a/ondevice-signing/Android.bp b/ondevice-signing/Android.bp index f56cfab3..6901b174 100644 --- a/ondevice-signing/Android.bp +++ b/ondevice-signing/Android.bp @@ -142,6 +142,8 @@ cc_binary { "libfsverity", "liblogwrap", "libprotobuf-cpp-lite", + "libstatspull", + "libstatssocket", "libutils", ], } diff --git a/ondevice-signing/CertUtils.cpp b/ondevice-signing/CertUtils.cpp index 8fe0816c..bb2da5a1 100644 --- a/ondevice-signing/CertUtils.cpp +++ b/ondevice-signing/CertUtils.cpp @@ -21,12 +21,10 @@ #include <openssl/bn.h> #include <openssl/crypto.h> -#include <openssl/pkcs7.h> #include <openssl/rsa.h> #include <openssl/x509.h> #include <openssl/x509v3.h> -#include <optional> #include <vector> #include "KeyConstants.h" @@ -56,12 +54,6 @@ static Result<bssl::UniquePtr<X509>> loadX509(const std::string& path) { return cert; } -static X509V3_CTX makeContext(X509* issuer, X509* subject) { - X509V3_CTX context = {}; - X509V3_set_ctx(&context, issuer, subject, nullptr, nullptr, 0); - return context; -} - static bool add_ext(X509V3_CTX* context, X509* cert, int nid, const char* value) { bssl::UniquePtr<X509_EXTENSION> ex(X509V3_EXT_nconf_nid(nullptr, context, nid, value)); if (!ex) { @@ -93,20 +85,6 @@ static Result<bssl::UniquePtr<RSA>> getRsaFromModulus(const std::vector<uint8_t> return rsaPubkey; } -static Result<bssl::UniquePtr<RSA>> -getRsaFromRsaPublicKey(const std::vector<uint8_t>& rsaPublicKey) { - auto derBytes = rsaPublicKey.data(); - bssl::UniquePtr<RSA> rsaKey(d2i_RSAPublicKey(nullptr, &derBytes, rsaPublicKey.size())); - if (rsaKey.get() == nullptr) { - return Error() << "Failed to parse RsaPublicKey"; - } - if (derBytes != rsaPublicKey.data() + rsaPublicKey.size()) { - return Error() << "Key has unexpected trailing data"; - } - - return rsaKey; -} - static Result<bssl::UniquePtr<EVP_PKEY>> modulusToRsaPkey(const std::vector<uint8_t>& publicKey) { // "publicKey" corresponds to the raw public key bytes - need to create // a new RSA key with the correct exponent. @@ -122,21 +100,6 @@ static Result<bssl::UniquePtr<EVP_PKEY>> modulusToRsaPkey(const std::vector<uint return public_key; } -static Result<bssl::UniquePtr<EVP_PKEY>> -rsaPublicKeyToRsaPkey(const std::vector<uint8_t>& rsaPublicKey) { - // rsaPublicKey contains both modulus and exponent, DER-encoded. - auto rsaKey = getRsaFromRsaPublicKey(rsaPublicKey); - if (!rsaKey.ok()) { - return rsaKey.error(); - } - - bssl::UniquePtr<EVP_PKEY> public_key(EVP_PKEY_new()); - if (!EVP_PKEY_assign_RSA(public_key.get(), rsaKey->release())) { - return Error() << "Failed to assign key"; - } - return public_key; -} - Result<void> verifySignature(const std::string& message, const std::string& signature, const std::vector<uint8_t>& publicKey) { auto rsaKey = getRsaFromModulus(publicKey); @@ -156,34 +119,14 @@ Result<void> verifySignature(const std::string& message, const std::string& sign return {}; } -Result<void> verifyRsaPublicKeySignature(const std::string& message, const std::string& signature, - const std::vector<uint8_t>& rsaPublicKey) { - auto rsaKey = getRsaFromRsaPublicKey(rsaPublicKey); - if (!rsaKey.ok()) { - return rsaKey.error(); - } - - uint8_t hashBuf[SHA256_DIGEST_LENGTH]; - SHA256(reinterpret_cast<const uint8_t*>(message.data()), message.size(), hashBuf); - - bool success = RSA_verify(NID_sha256, hashBuf, sizeof(hashBuf), - reinterpret_cast<const uint8_t*>(signature.data()), signature.size(), - rsaKey->get()); - if (!success) { - return Error() << "Failed to verify signature"; +Result<void> createSelfSignedCertificate( + const std::vector<uint8_t>& publicKey, + const std::function<Result<std::string>(const std::string&)>& signFunction, + const std::string& path) { + auto rsa_pkey = modulusToRsaPkey(publicKey); + if (!rsa_pkey.ok()) { + return rsa_pkey.error(); } - return {}; -} - -static Result<void> createCertificate( - const CertSubject& subject, EVP_PKEY* publicKey, - const std::function<android::base::Result<std::string>(const std::string&)>& signFunction, - const std::optional<std::string>& issuerCertPath, const std::string& path) { - - // If an issuer cert is specified, we are signing someone else's key. - // Otherwise we are signing our key - a self-signed certificate. - bool selfSigned = !issuerCertPath; - bssl::UniquePtr<X509> x509(X509_new()); if (!x509) { return Error() << "Unable to allocate x509 container"; @@ -191,7 +134,7 @@ static Result<void> createCertificate( X509_set_version(x509.get(), 2); X509_gmtime_adj(X509_get_notBefore(x509.get()), 0); X509_gmtime_adj(X509_get_notAfter(x509.get()), kCertLifetimeSeconds); - ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), subject.serialNumber); + ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), kRootSubject.serialNumber); bssl::UniquePtr<X509_ALGOR> algor(X509_ALGOR_new()); if (!algor || @@ -201,7 +144,7 @@ static Result<void> createCertificate( return Error() << "Unable to set x509 signature algorithm"; } - if (!X509_set_pubkey(x509.get(), publicKey)) { + if (!X509_set_pubkey(x509.get(), rsa_pkey.value().get())) { return Error() << "Unable to set x509 public key"; } @@ -211,44 +154,15 @@ static Result<void> createCertificate( } addNameEntry(subjectName, "C", kIssuerCountry); addNameEntry(subjectName, "O", kIssuerOrg); - addNameEntry(subjectName, "CN", subject.commonName); - - if (selfSigned) { - if (!X509_set_issuer_name(x509.get(), subjectName)) { - return Error() << "Unable to set x509 issuer name"; - } - } else { - X509_NAME* issuerName = X509_get_issuer_name(x509.get()); - if (!issuerName) { - return Error() << "Unable to get x509 issuer name"; - } - addNameEntry(issuerName, "C", kIssuerCountry); - addNameEntry(issuerName, "O", kIssuerOrg); - addNameEntry(issuerName, "CN", kRootSubject.commonName); - } - - // Beware: context contains a pointer to issuerCert, so we need to keep it alive. - bssl::UniquePtr<X509> issuerCert; - X509V3_CTX context; - - if (selfSigned) { - context = makeContext(x509.get(), x509.get()); - } else { - auto certStatus = loadX509(*issuerCertPath); - if (!certStatus.ok()) { - return Error() << "Unable to load issuer cert: " << certStatus.error(); - } - issuerCert = std::move(certStatus.value()); - context = makeContext(issuerCert.get(), x509.get()); + addNameEntry(subjectName, "CN", kRootSubject.commonName); + if (!X509_set_issuer_name(x509.get(), subjectName)) { + return Error() << "Unable to set x509 issuer name"; } - // If it's a self-signed cert we use it for signing certs, otherwise only for signing data. - const char* basicConstraints = selfSigned ? "CA:TRUE" : "CA:FALSE"; - const char* keyUsage = - selfSigned ? "critical,keyCertSign,cRLSign,digitalSignature" : "critical,digitalSignature"; - - add_ext(&context, x509.get(), NID_basic_constraints, basicConstraints); - add_ext(&context, x509.get(), NID_key_usage, keyUsage); + X509V3_CTX context = {}; + X509V3_set_ctx(&context, x509.get(), x509.get(), nullptr, nullptr, 0); + add_ext(&context, x509.get(), NID_basic_constraints, "CA:TRUE"); + add_ext(&context, x509.get(), NID_key_usage, "critical,keyCertSign,cRLSign,digitalSignature"); add_ext(&context, x509.get(), NID_subject_key_identifier, "hash"); add_ext(&context, x509.get(), NID_authority_key_identifier, "keyid:always"); @@ -280,31 +194,7 @@ static Result<void> createCertificate( return {}; } -Result<void> createSelfSignedCertificate( - const std::vector<uint8_t>& publicKey, - const std::function<Result<std::string>(const std::string&)>& signFunction, - const std::string& path) { - auto rsa_pkey = modulusToRsaPkey(publicKey); - if (!rsa_pkey.ok()) { - return rsa_pkey.error(); - } - - return createCertificate(kRootSubject, rsa_pkey.value().get(), signFunction, {}, path); -} - -android::base::Result<void> createLeafCertificate( - const CertSubject& subject, const std::vector<uint8_t>& rsaPublicKey, - const std::function<android::base::Result<std::string>(const std::string&)>& signFunction, - const std::string& issuerCertPath, const std::string& path) { - auto rsa_pkey = rsaPublicKeyToRsaPkey(rsaPublicKey); - if (!rsa_pkey.ok()) { - return rsa_pkey.error(); - } - - return createCertificate(subject, rsa_pkey.value().get(), signFunction, issuerCertPath, path); -} - -Result<std::vector<uint8_t>> extractPublicKey(EVP_PKEY* pkey) { +static Result<std::vector<uint8_t>> extractPublicKey(EVP_PKEY* pkey) { if (pkey == nullptr) { return Error() << "Failed to extract public key from x509 cert"; } @@ -325,14 +215,6 @@ Result<std::vector<uint8_t>> extractPublicKey(EVP_PKEY* pkey) { return pubKey; } -Result<std::vector<uint8_t>> -extractPublicKeyFromSubjectPublicKeyInfo(const std::vector<uint8_t>& keyData) { - auto keyDataBytes = keyData.data(); - bssl::UniquePtr<EVP_PKEY> public_key(d2i_PUBKEY(nullptr, &keyDataBytes, keyData.size())); - - return extractPublicKey(public_key.get()); -} - Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t>& derCert) { auto derCertBytes = derCert.data(); bssl::UniquePtr<X509> decoded_cert(d2i_X509(nullptr, &derCertBytes, derCert.size())); @@ -351,121 +233,3 @@ Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::string& path) { } return extractPublicKey(X509_get_pubkey(cert.value().get())); } - -static Result<std::vector<uint8_t>> extractRsaPublicKey(EVP_PKEY* pkey) { - RSA* rsa = EVP_PKEY_get0_RSA(pkey); - if (rsa == nullptr) { - return Error() << "The public key is not an RSA key"; - } - - uint8_t* out = nullptr; - int size = i2d_RSAPublicKey(rsa, &out); - if (size < 0 || !out) { - return Error() << "Failed to convert to RSAPublicKey"; - } - - bssl::UniquePtr<uint8_t> buffer(out); - std::vector<uint8_t> result(out, out + size); - return result; -} - -Result<CertInfo> verifyAndExtractCertInfoFromX509(const std::string& path, - const std::vector<uint8_t>& publicKey) { - auto public_key = modulusToRsaPkey(publicKey); - if (!public_key.ok()) { - return public_key.error(); - } - - auto cert = loadX509(path); - if (!cert.ok()) { - return cert.error(); - } - X509* x509 = cert.value().get(); - - // Make sure we signed it. - if (X509_verify(x509, public_key.value().get()) != 1) { - return Error() << "Failed to verify certificate."; - } - - bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509)); - auto subject_key = extractRsaPublicKey(pkey.get()); - if (!subject_key.ok()) { - return subject_key.error(); - } - - // The pointers here are all owned by x509, and each function handles an - // error return from the previous call correctly. - X509_NAME* name = X509_get_subject_name(x509); - int index = X509_NAME_get_index_by_NID(name, NID_commonName, -1); - X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, index); - ASN1_STRING* asn1cn = X509_NAME_ENTRY_get_data(entry); - unsigned char* utf8cn; - int length = ASN1_STRING_to_UTF8(&utf8cn, asn1cn); - if (length < 0) { - return Error() << "Failed to read subject CN"; - } - - bssl::UniquePtr<unsigned char> utf8owner(utf8cn); - std::string cn(reinterpret_cast<char*>(utf8cn), static_cast<size_t>(length)); - - CertInfo cert_info{std::move(cn), std::move(subject_key.value())}; - return cert_info; -} - -Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signed_digest, - const CertSubject& signer) { - CBB out, outer_seq, wrapped_seq, seq, digest_algos_set, digest_algo, null; - CBB content_info, issuer_and_serial, signer_infos, signer_info, sign_algo, signature; - uint8_t *pkcs7_data, *name_der; - size_t pkcs7_data_len, name_der_len; - BIGNUM* serial = BN_new(); - int sig_nid = NID_rsaEncryption; - - X509_NAME* issuer_name = X509_NAME_new(); - if (!issuer_name) { - return Error() << "Unable to create x509 subject name"; - } - X509_NAME_add_entry_by_txt(issuer_name, "C", MBSTRING_ASC, - reinterpret_cast<const unsigned char*>(kIssuerCountry), -1, -1, 0); - X509_NAME_add_entry_by_txt(issuer_name, "O", MBSTRING_ASC, - reinterpret_cast<const unsigned char*>(kIssuerOrg), -1, -1, 0); - X509_NAME_add_entry_by_txt(issuer_name, "CN", MBSTRING_ASC, - reinterpret_cast<const unsigned char*>(kRootSubject.commonName), -1, - -1, 0); - - BN_set_word(serial, signer.serialNumber); - name_der_len = i2d_X509_NAME(issuer_name, &name_der); - CBB_init(&out, 1024); - - if (!CBB_add_asn1(&out, &outer_seq, CBS_ASN1_SEQUENCE) || - !OBJ_nid2cbb(&outer_seq, NID_pkcs7_signed) || - !CBB_add_asn1(&outer_seq, &wrapped_seq, - CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || - // See https://tools.ietf.org/html/rfc2315#section-9.1 - !CBB_add_asn1(&wrapped_seq, &seq, CBS_ASN1_SEQUENCE) || - !CBB_add_asn1_uint64(&seq, 1 /* version */) || - !CBB_add_asn1(&seq, &digest_algos_set, CBS_ASN1_SET) || - !CBB_add_asn1(&digest_algos_set, &digest_algo, CBS_ASN1_SEQUENCE) || - !OBJ_nid2cbb(&digest_algo, NID_sha256) || - !CBB_add_asn1(&digest_algo, &null, CBS_ASN1_NULL) || - !CBB_add_asn1(&seq, &content_info, CBS_ASN1_SEQUENCE) || - !OBJ_nid2cbb(&content_info, NID_pkcs7_data) || - !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET) || - !CBB_add_asn1(&signer_infos, &signer_info, CBS_ASN1_SEQUENCE) || - !CBB_add_asn1_uint64(&signer_info, 1 /* version */) || - !CBB_add_asn1(&signer_info, &issuer_and_serial, CBS_ASN1_SEQUENCE) || - !CBB_add_bytes(&issuer_and_serial, name_der, name_der_len) || - !BN_marshal_asn1(&issuer_and_serial, serial) || - !CBB_add_asn1(&signer_info, &digest_algo, CBS_ASN1_SEQUENCE) || - !OBJ_nid2cbb(&digest_algo, NID_sha256) || - !CBB_add_asn1(&digest_algo, &null, CBS_ASN1_NULL) || - !CBB_add_asn1(&signer_info, &sign_algo, CBS_ASN1_SEQUENCE) || - !OBJ_nid2cbb(&sign_algo, sig_nid) || !CBB_add_asn1(&sign_algo, &null, CBS_ASN1_NULL) || - !CBB_add_asn1(&signer_info, &signature, CBS_ASN1_OCTETSTRING) || - !CBB_add_bytes(&signature, signed_digest.data(), signed_digest.size()) || - !CBB_finish(&out, &pkcs7_data, &pkcs7_data_len)) { - return Error() << "Failed to create PKCS7 certificate."; - } - - return std::vector<uint8_t>(&pkcs7_data[0], &pkcs7_data[pkcs7_data_len]); -} diff --git a/ondevice-signing/OWNERS b/ondevice-signing/OWNERS index 72a8eb5f..f406fce3 100644 --- a/ondevice-signing/OWNERS +++ b/ondevice-signing/OWNERS @@ -1,3 +1,3 @@ maco@google.com ngeoffray@google.com -oth@google.com +jiakaiz@google.com diff --git a/ondevice-signing/VerityUtils.cpp b/ondevice-signing/VerityUtils.cpp index 0b631daa..43de67cb 100644 --- a/ondevice-signing/VerityUtils.cpp +++ b/ondevice-signing/VerityUtils.cpp @@ -19,12 +19,12 @@ #include <map> #include <span> #include <string> +#include <vector> #include <fcntl.h> #include <linux/fs.h> #include <sys/stat.h> #include <sys/types.h> -#include <sys/wait.h> #include "android-base/errors.h" #include <android-base/file.h> @@ -42,7 +42,6 @@ using android::base::Error; using android::base::Result; using android::base::unique_fd; -static const char* kFsVerityInitPath = "/system/bin/fsverity_init"; static const char* kFsVerityProcPath = "/proc/sys/fs/verity"; bool SupportsFsVerity() { @@ -285,41 +284,3 @@ Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path, return {}; } - -Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName) { - const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", keyName}; - - int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); - if (fd == -1) { - return ErrnoError() << "Failed to open " << path; - } - pid_t pid = fork(); - if (pid == 0) { - dup2(fd, STDIN_FILENO); - close(fd); - int argc = arraysize(argv); - char* argv_child[argc + 1]; - memcpy(argv_child, argv, argc * sizeof(char*)); - argv_child[argc] = nullptr; - execvp(argv_child[0], argv_child); - PLOG(ERROR) << "exec in ForkExecvp"; - _exit(EXIT_FAILURE); - } else { - close(fd); - } - if (pid == -1) { - return ErrnoError() << "Failed to fork."; - } - int status; - if (waitpid(pid, &status, 0) == -1) { - return ErrnoError() << "waitpid() failed."; - } - if (!WIFEXITED(status)) { - return Error() << kFsVerityInitPath << ": abnormal process exit"; - } - if (WEXITSTATUS(status) != 0) { - return Error() << kFsVerityInitPath << " exited with " << WEXITSTATUS(status); - } - - return {}; -} diff --git a/ondevice-signing/include/CertUtils.h b/ondevice-signing/include/CertUtils.h index fe703fa0..9b9d2cc4 100644 --- a/ondevice-signing/include/CertUtils.h +++ b/ondevice-signing/include/CertUtils.h @@ -34,39 +34,18 @@ struct CertSubject { unsigned serialNumber; }; -// These are all the certificates we ever sign (the first one being our -// self-signed cert). We shouldn't really re-use serial numbers for different -// certificates for the same subject but we do; only one should be in use at a -// time though. +// This is our self-signed cert. inline const CertSubject kRootSubject{"ODS", 1}; -inline const CertSubject kCompOsSubject{"CompOs", 2}; android::base::Result<void> createSelfSignedCertificate( const std::vector<uint8_t>& publicKey, const std::function<android::base::Result<std::string>(const std::string&)>& signFunction, const std::string& path); -android::base::Result<void> createLeafCertificate( - const CertSubject& subject, const std::vector<uint8_t>& publicKey, - const std::function<android::base::Result<std::string>(const std::string&)>& signFunction, - const std::string& issuerCertPath, const std::string& outPath); - -android::base::Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signedData, - const CertSubject& signer); - android::base::Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t>& x509); -android::base::Result<std::vector<uint8_t>> -extractPublicKeyFromSubjectPublicKeyInfo(const std::vector<uint8_t>& subjectKeyInfo); android::base::Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::string& path); -android::base::Result<CertInfo> -verifyAndExtractCertInfoFromX509(const std::string& path, const std::vector<uint8_t>& publicKey); - android::base::Result<void> verifySignature(const std::string& message, const std::string& signature, const std::vector<uint8_t>& publicKey); - -android::base::Result<void> verifyRsaPublicKeySignature(const std::string& message, - const std::string& signature, - const std::vector<uint8_t>& rsaPublicKey); diff --git a/ondevice-signing/include/VerityUtils.h b/ondevice-signing/include/VerityUtils.h index 626bbdb4..71f78cf3 100644 --- a/ondevice-signing/include/VerityUtils.h +++ b/ondevice-signing/include/VerityUtils.h @@ -22,7 +22,6 @@ #include <string> #include <vector> -android::base::Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName); android::base::Result<std::vector<uint8_t>> createDigest(const std::string& path); android::base::Result<std::string> enableFsVerity(int fd); bool SupportsFsVerity(); @@ -34,7 +33,7 @@ verifyAllFilesInVerity(const std::string& path); android::base::Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path); -// Enable verity on the provided file, using the given PKCS7 signature. +// Enable verity on the provided file. android::base::Result<void> enableFsVerity(const std::string& path); android::base::Result<void> diff --git a/ondevice-signing/odsign.rc b/ondevice-signing/odsign.rc index b96c62ff..b95cf9db 100644 --- a/ondevice-signing/odsign.rc +++ b/ondevice-signing/odsign.rc @@ -3,13 +3,10 @@ service odsign /system/bin/odsign user root group system disabled # does not start with the core class - # Explicitly specify empty capabilities, otherwise odsign will inherit all - # the capabilities from init. - # Note: whether a process can use capabilities is controlled by SELinux, so - # inheriting all the capabilities from init is not a security issue. - # However, for defense-in-depth and just for the sake of bookkeeping it's - # better to explicitly state that odsign doesn't need any capabilities. - capabilities + # We need SYS_NICE in order to allow the crosvm child process to use it. + # (b/322197421). odsign itself never uses it (and isn't allowed to by + # SELinux). + capabilities SYS_NICE # Note that odsign is not oneshot, but stopped manually when it exits. This # ensures that if odsign crashes during a module update, apexd will detect diff --git a/ondevice-signing/tests/Android.bp b/ondevice-signing/tests/Android.bp index 40272200..bcfe8e40 100644 --- a/ondevice-signing/tests/Android.bp +++ b/ondevice-signing/tests/Android.bp @@ -13,6 +13,7 @@ // limitations under the License. package { + default_team: "trendy_team_art_mainline", // 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" @@ -22,23 +23,23 @@ package { } cc_test { - name: "libsigningutils_test", - srcs: ["SigningUtilsTest.cpp"], - test_suites: ["device-tests"], - compile_multilib: "both", - defaults: [ - "odsign_flags_defaults", - ], - static_libs: [ - "libsigningutils", - ], - shared_libs: [ - "libbase", - "libcrypto", - ], - data: [ - "test_file", - "test_file.sig", - "SigningUtils.cert.der", - ], + name: "libsigningutils_test", + srcs: ["SigningUtilsTest.cpp"], + test_suites: ["device-tests"], + compile_multilib: "both", + defaults: [ + "odsign_flags_defaults", + ], + static_libs: [ + "libsigningutils", + ], + shared_libs: [ + "libbase", + "libcrypto", + ], + data: [ + "test_file", + "test_file.sig", + "SigningUtils.cert.der", + ], } diff --git a/prng_seeder/Android.bp b/prng_seeder/Android.bp index 763aaa01..4f9b7e14 100644 --- a/prng_seeder/Android.bp +++ b/prng_seeder/Android.bp @@ -13,6 +13,7 @@ // limitations under the License. package { + default_team: "trendy_team_java_core_libraries", // See: http://go/android-license-faq // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["system_security_license"], @@ -36,7 +37,7 @@ rust_defaults { edition: "2021", rustlibs: [ "libanyhow", - "libbssl_ffi", + "libbssl_sys", "libclap", "libcutils_socket_bindgen", "liblogger", @@ -70,7 +71,7 @@ rust_test { srcs: ["src/main.rs"], rustlibs: [ "libanyhow", - "libbssl_ffi", + "libbssl_sys", "libclap", "libcutils_socket_bindgen", "liblogger", diff --git a/prng_seeder/OWNERS b/prng_seeder/OWNERS index 9202b90e..51b7f38c 100644 --- a/prng_seeder/OWNERS +++ b/prng_seeder/OWNERS @@ -1,2 +1,2 @@ paulcrowley@google.com -prb@google.com
\ No newline at end of file +prb@google.com diff --git a/prng_seeder/src/conditioner.rs b/prng_seeder/src/conditioner.rs index eca8af88..ec1181bd 100644 --- a/prng_seeder/src/conditioner.rs +++ b/prng_seeder/src/conditioner.rs @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{fs::File, io::Read, os::unix::io::AsRawFd}; +use std::{fs::File, io::Read}; use anyhow::{ensure, Context, Result}; use log::debug; -use nix::fcntl::{fcntl, FcntlArg::F_SETFL, OFlag}; use tokio::io::AsyncReadExt; use crate::drbg; @@ -34,8 +33,6 @@ impl ConditionerBuilder { let mut et: drbg::Entropy = [0; drbg::ENTROPY_LEN]; hwrng.read_exact(&mut et).context("hwrng.read_exact in new")?; let rg = drbg::Drbg::new(&et)?; - fcntl(hwrng.as_raw_fd(), F_SETFL(OFlag::O_NONBLOCK)) - .context("setting O_NONBLOCK on hwrng")?; Ok(ConditionerBuilder { hwrng, rg }) } diff --git a/prng_seeder/src/cutils_socket.rs b/prng_seeder/src/cutils_socket.rs index ab2c8698..b408be60 100644 --- a/prng_seeder/src/cutils_socket.rs +++ b/prng_seeder/src/cutils_socket.rs @@ -19,7 +19,11 @@ use anyhow::{ensure, Result}; pub fn android_get_control_socket(name: &str) -> Result<UnixListener> { let name = CString::new(name)?; + // SAFETY: name is a valid C string, and android_get_control_socket doesn't retain it after it + // returns. let fd = unsafe { cutils_socket_bindgen::android_get_control_socket(name.as_ptr()) }; ensure!(fd >= 0, "android_get_control_socket failed"); + // SAFETY: android_get_control_socket either returns a valid and open FD or -1, and we checked + // that it's not -1. Ok(unsafe { UnixListener::from_raw_fd(fd) }) } diff --git a/prng_seeder/src/drbg.rs b/prng_seeder/src/drbg.rs index 89c5a888..71903530 100644 --- a/prng_seeder/src/drbg.rs +++ b/prng_seeder/src/drbg.rs @@ -13,7 +13,6 @@ // limitations under the License. use anyhow::{ensure, Result}; -use bssl_ffi as bssl_sys; pub const ENTROPY_LEN: usize = bssl_sys::CTR_DRBG_ENTROPY_LEN as usize; @@ -23,6 +22,9 @@ pub struct Drbg(*mut bssl_sys::CTR_DRBG_STATE); impl Drbg { pub fn new(entropy: &Entropy) -> Result<Drbg> { + // SAFETY: entropy must be a valid pointer because it comes from a reference, and a null + // pointer is allowed for personalization. CTR_DRBG_new doesn't retain the entropy pointer + // for use after it returns. let p = unsafe { bssl_sys::CTR_DRBG_new(entropy.as_ptr(), std::ptr::null(), 0) }; ensure!(!p.is_null(), "CTR_DRBG_new failed"); Ok(Drbg(p)) @@ -30,6 +32,9 @@ impl Drbg { pub fn reseed(&mut self, entropy: &Entropy) -> Result<()> { ensure!( + // SAFETY: We know that self.0 is valid because it was initialised from CTR_DRBG_new in + // Drbg::new above. The entropy pointer must be valid because it comes from a reference, + // and CTR_DRBG_reseed doesn't retain it after it returns. unsafe { bssl_sys::CTR_DRBG_reseed(self.0, entropy.as_ptr(), std::ptr::null(), 0) } == 1, "CTR_DRBG_reseed failed" @@ -39,6 +44,10 @@ impl Drbg { pub fn generate(&mut self, buf: &mut [u8]) -> Result<()> { ensure!( + // SAFETY: We know that self.0 is valid because it was initialised from CTR_DRBG_new in + // Drbg::new above. The out pointer and length must be valid and unaliased because they + // come from a mutable slice reference, and CTR_DRBG_generate doesn't retain them after + // it returns. unsafe { bssl_sys::CTR_DRBG_generate( self.0, @@ -56,10 +65,13 @@ impl Drbg { impl Drop for Drbg { fn drop(&mut self) { + // SAFETY: We know that self.0 is valid because it was initialised from CTR_DRBG_new in + // Drbg::new above, and this is the only place that frees it. unsafe { bssl_sys::CTR_DRBG_free(self.0); } } } +// SAFETY: CTR_DRBG functions can be called from any thread. unsafe impl Send for Drbg {} diff --git a/prng_seeder/src/main.rs b/prng_seeder/src/main.rs index 924481ac..cb7f38d7 100644 --- a/prng_seeder/src/main.rs +++ b/prng_seeder/src/main.rs @@ -31,7 +31,7 @@ use std::{ use anyhow::{ensure, Context, Result}; use clap::Parser; -use log::{error, info, Level}; +use log::{error, info, LevelFilter}; use nix::sys::signal; use tokio::{io::AsyncWriteExt, net::UnixListener as TokioUnixListener}; @@ -48,7 +48,9 @@ struct Cli { fn configure_logging() -> Result<()> { ensure!( logger::init( - logger::Config::default().with_tag_on_device("prng_seeder").with_min_level(Level::Info) + logger::Config::default() + .with_tag_on_device("prng_seeder") + .with_max_level(LevelFilter::Info) ), "log configuration failed" ); @@ -70,6 +72,7 @@ fn get_socket(path: &Path) -> Result<UnixListener> { fn setup() -> Result<(ConditionerBuilder, UnixListener)> { configure_logging()?; let cli = Cli::try_parse()?; + // SAFETY: Nothing else sets the signal handler, so either it was set here or it is the default. unsafe { signal::signal(signal::Signal::SIGPIPE, signal::SigHandler::SigIgn) } .context("In setup, setting SIGPIPE to SIG_IGN")?; diff --git a/provisioner/Android.bp b/provisioner/Android.bp index b5489738..ede1ae6c 100644 --- a/provisioner/Android.bp +++ b/provisioner/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_foundation_security_rust_pkvm_", // 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" @@ -23,44 +24,26 @@ package { default_applicable_licenses: ["system_security_license"], } -aidl_interface { - name: "android.security.provisioner", - unstable: true, - local_include_dir: "binder", - srcs: [ - "binder/android/security/provisioner/*.aidl", - ], - backend: { - java: { - platform_apis: true, - }, - cpp: { - enabled: false, - }, - ndk: { - enabled: false, - }, - }, -} - cc_defaults { name: "rkp_factory_extraction_defaults", defaults: [ "keymint_use_latest_hal_aidl_ndk_static", ], shared_libs: [ - "libbinder", "libbinder_ndk", "libcrypto", "liblog", ], static_libs: [ + "android.hardware.common-V2-ndk", + "android.hardware.drm-V1-ndk", "android.hardware.security.rkp-V3-ndk", "libbase", - "libcppbor_external", + "libcppbor", "libcppcose_rkp", "libjsoncpp", "libkeymint_remote_prov_support", + "libmediadrmrkp", ], } @@ -97,4 +80,39 @@ cc_binary { "libgflags", "librkp_factory_extraction", ], + dist: { + targets: [ + "dist_files", + "rkp_factory_extraction_tool", + ], + dest: "rkp_factory_extraction_tool", + }, + compile_multilib: "both", + multilib: { + lib64: { + suffix: "64", + }, + }, + target: { + android_arm: { + dist: { + dir: "rkp/arm", + }, + }, + android_arm64: { + dist: { + dir: "rkp/arm64", + }, + }, + android_x86: { + dist: { + dir: "rkp/x86", + }, + }, + android_x86_64: { + dist: { + dir: "rkp/x86_64", + }, + }, + }, } diff --git a/provisioner/binder/android/security/provisioner/IProvisionerService.aidl b/provisioner/binder/android/security/provisioner/IProvisionerService.aidl deleted file mode 100644 index f81e9abe..00000000 --- a/provisioner/binder/android/security/provisioner/IProvisionerService.aidl +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2020, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.provisioner; - -/** - * @hide - */ -interface IProvisionerService { - byte[] getCertificateRequest(in boolean testMode, - in int keyCount, - in byte[] endpointEncryptionKey, - in byte[] challenge) = 0; -} diff --git a/provisioner/rkp_factory_extraction_lib.cpp b/provisioner/rkp_factory_extraction_lib.cpp index d85e85f6..b7e1e340 100644 --- a/provisioner/rkp_factory_extraction_lib.cpp +++ b/provisioner/rkp_factory_extraction_lib.cpp @@ -17,6 +17,7 @@ #include "rkp_factory_extraction_lib.h" #include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <android-base/properties.h> #include <android/binder_manager.h> #include <cppbor.h> #include <cstddef> @@ -143,7 +144,7 @@ CborResult<Array> getCsrV1(std::string_view componentName, IRemotelyProvisionedC ::ndk::ScopedAStatus status = irpc->getHardwareInfo(&hwInfo); if (!status.isOk()) { std::cerr << "Failed to get hardware info for '" << componentName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + << "'. Description: " << status.getDescription() << "." << std::endl; exit(-1); } @@ -154,7 +155,7 @@ CborResult<Array> getCsrV1(std::string_view componentName, IRemotelyProvisionedC &keysToSignMac); if (!status.isOk()) { std::cerr << "Bundle extraction failed for '" << componentName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + << "'. Description: " << status.getDescription() << "." << std::endl; exit(-1); } return composeCertificateRequestV1(protectedData, verifiedDeviceInfo, challenge, keysToSignMac, @@ -170,7 +171,7 @@ void selfTestGetCsrV1(std::string_view componentName, IRemotelyProvisionedCompon ::ndk::ScopedAStatus status = irpc->getHardwareInfo(&hwInfo); if (!status.isOk()) { std::cerr << "Failed to get hardware info for '" << componentName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + << "'. Description: " << status.getDescription() << "." << std::endl; exit(-1); } @@ -186,7 +187,7 @@ void selfTestGetCsrV1(std::string_view componentName, IRemotelyProvisionedCompon &protectedData, &keysToSignMac); if (!status.isOk()) { std::cerr << "Error generating test cert chain for '" << componentName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + << "'. Description: " << status.getDescription() << "." << std::endl; exit(-1); } @@ -194,10 +195,16 @@ void selfTestGetCsrV1(std::string_view componentName, IRemotelyProvisionedCompon protectedData, *eekChain, eekId, hwInfo.supportedEekCurve, irpc, challenge); - std::cout << "Self test successful." << std::endl; + if (!result) { + std::cerr << "Self test failed for IRemotelyProvisionedComponent '" << componentName + << "'. Error message: '" << result.message() << "'." << std::endl; + exit(-1); + } } CborResult<Array> composeCertificateRequestV3(const std::vector<uint8_t>& csr) { + const std::string kFingerprintProp = "ro.build.fingerprint"; + auto [parsedCsr, _, csrErrMsg] = cppbor::parse(csr); if (!parsedCsr) { return {nullptr, csrErrMsg}; @@ -206,26 +213,18 @@ CborResult<Array> composeCertificateRequestV3(const std::vector<uint8_t>& csr) { return {nullptr, "CSR is not a CBOR array."}; } - return {std::unique_ptr<Array>(parsedCsr.release()->asArray()), ""}; -} - -CborResult<cppbor::Array> getCsrV3(std::string_view componentName, - IRemotelyProvisionedComponent* irpc) { - std::vector<uint8_t> csr; - std::vector<MacedPublicKey> emptyKeys; - const std::vector<uint8_t> challenge = generateChallenge(); - - auto status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr); - if (!status.isOk()) { - std::cerr << "Bundle extraction failed for '" << componentName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; - exit(-1); + if (!::android::base::WaitForPropertyCreation(kFingerprintProp)) { + return {nullptr, "Unable to read build fingerprint"}; } - return composeCertificateRequestV3(csr); + Map unverifiedDeviceInfo = + Map().add("fingerprint", ::android::base::GetProperty(kFingerprintProp, /*default=*/"")); + parsedCsr->asArray()->add(std::move(unverifiedDeviceInfo)); + return {std::unique_ptr<Array>(parsedCsr.release()->asArray()), ""}; } -void selfTestGetCsrV3(std::string_view componentName, IRemotelyProvisionedComponent* irpc) { +CborResult<cppbor::Array> getCsrV3(std::string_view componentName, + IRemotelyProvisionedComponent* irpc, bool selfTest) { std::vector<uint8_t> csr; std::vector<MacedPublicKey> emptyKeys; const std::vector<uint8_t> challenge = generateChallenge(); @@ -233,48 +232,38 @@ void selfTestGetCsrV3(std::string_view componentName, IRemotelyProvisionedCompon auto status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr); if (!status.isOk()) { std::cerr << "Bundle extraction failed for '" << componentName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + << "'. Description: " << status.getDescription() << "." << std::endl; exit(-1); } - auto result = verifyFactoryCsr(/*keysToSign=*/cppbor::Array(), csr, irpc, challenge); - if (!result) { - std::cerr << "Self test failed for '" << componentName - << "'. Error message: " << result.message() << "." << std::endl; - exit(-1); + if (selfTest) { + auto result = verifyFactoryCsr(/*keysToSign=*/cppbor::Array(), csr, irpc, challenge); + if (!result) { + std::cerr << "Self test failed for IRemotelyProvisionedComponent '" << componentName + << "'. Error message: '" << result.message() << "'." << std::endl; + exit(-1); + } } - std::cout << "Self test successful." << std::endl; + return composeCertificateRequestV3(csr); } -CborResult<Array> getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc) { +CborResult<Array> getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc, + bool selfTest) { RpcHardwareInfo hwInfo; auto status = irpc->getHardwareInfo(&hwInfo); if (!status.isOk()) { std::cerr << "Failed to get hardware info for '" << componentName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; + << "'. Description: " << status.getDescription() << "." << std::endl; exit(-1); } if (hwInfo.versionNumber < kVersionWithoutSuperencryption) { + if (selfTest) { + selfTestGetCsrV1(componentName, irpc); + } return getCsrV1(componentName, irpc); } else { - return getCsrV3(componentName, irpc); - } -} - -void selfTestGetCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc) { - RpcHardwareInfo hwInfo; - auto status = irpc->getHardwareInfo(&hwInfo); - if (!status.isOk()) { - std::cerr << "Failed to get hardware info for '" << componentName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; - exit(-1); - } - - if (hwInfo.versionNumber < kVersionWithoutSuperencryption) { - selfTestGetCsrV1(componentName, irpc); - } else { - selfTestGetCsrV3(componentName, irpc); + return getCsrV3(componentName, irpc, selfTest); } } diff --git a/provisioner/rkp_factory_extraction_lib.h b/provisioner/rkp_factory_extraction_lib.h index a2183380..ae8ea6b6 100644 --- a/provisioner/rkp_factory_extraction_lib.h +++ b/provisioner/rkp_factory_extraction_lib.h @@ -46,7 +46,8 @@ std::vector<uint8_t> generateChallenge(); // what went wrong. CborResult<cppbor::Array> getCsr(std::string_view componentName, - aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc); + aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc, + bool selfTest); // Generates a test certificate chain and validates it, exiting the process on error. void selfTestGetCsr( diff --git a/provisioner/rkp_factory_extraction_lib_test.cpp b/provisioner/rkp_factory_extraction_lib_test.cpp index 05509b36..3fe88da8 100644 --- a/provisioner/rkp_factory_extraction_lib_test.cpp +++ b/provisioner/rkp_factory_extraction_lib_test.cpp @@ -22,6 +22,7 @@ #include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h> #include <aidl/android/hardware/security/keymint/MacedPublicKey.h> #include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h> +#include <android-base/properties.h> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -179,7 +180,8 @@ TEST(LibRkpFactoryExtractionTests, GetCsrWithV2Hal) { SetArgPointee<6>(kFakeMac), // Return(ByMove(ScopedAStatus::ok())))); // - auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get()); + auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get(), + /*selfTest=*/false); ASSERT_THAT(csr, NotNull()) << csrErrMsg; ASSERT_THAT(csr->asArray(), Pointee(Property(&Array::size, Eq(4)))); @@ -248,12 +250,19 @@ TEST(LibRkpFactoryExtractionTests, GetCsrWithV3Hal) { .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(kCsr), Return(ByMove(ScopedAStatus::ok())))); - auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get()); + auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get(), + /*selfTest=*/false); ASSERT_THAT(csr, NotNull()) << csrErrMsg; - ASSERT_THAT(csr, Pointee(Property(&Array::size, Eq(4)))); + ASSERT_THAT(csr, Pointee(Property(&Array::size, Eq(5)))); EXPECT_THAT(csr->get(0 /* version */), Pointee(Eq(Uint(3)))); EXPECT_THAT(csr->get(1)->asMap(), NotNull()); EXPECT_THAT(csr->get(2)->asArray(), NotNull()); EXPECT_THAT(csr->get(3)->asArray(), NotNull()); + + const Map* unverifedDeviceInfo = csr->get(4)->asMap(); + ASSERT_THAT(unverifedDeviceInfo, NotNull()); + EXPECT_THAT(unverifedDeviceInfo->get("fingerprint"), NotNull()); + const Tstr fingerprint(android::base::GetProperty("ro.build.fingerprint", "")); + EXPECT_THAT(*unverifedDeviceInfo->get("fingerprint")->asTstr(), Eq(fingerprint)); } diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp index 2aeabe0a..0a3a59a6 100644 --- a/provisioner/rkp_factory_extraction_tool.cpp +++ b/provisioner/rkp_factory_extraction_tool.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <aidl/android/hardware/drm/IDrmFactory.h> #include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h> #include <android/binder_manager.h> #include <cppbor.h> @@ -26,8 +27,10 @@ #include <string> #include <vector> +#include "DrmRkpAdapter.h" #include "rkp_factory_extraction_lib.h" +using aidl::android::hardware::drm::IDrmFactory; using aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent; using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild; @@ -35,10 +38,12 @@ using namespace cppbor; using namespace cppcose; DEFINE_string(output_format, "build+csr", "How to format the output. Defaults to 'build+csr'."); -DEFINE_bool(self_test, false, - "If true, the tool does not output CSR data, but instead performs a self-test, " - "validating a test payload for correctness. This may be used to verify a device on the " - "factory line before attempting to upload the output to the device info service."); +DEFINE_bool(self_test, true, + "If true, this tool performs a self-test, validating the payload for correctness. " + "This checks that the device on the factory line is producing valid output " + "before attempting to upload the output to the device info service."); +DEFINE_string(serialno_prop, "ro.serialno", + "The property of getting serial number. Defaults to 'ro.serialno'."); namespace { @@ -47,12 +52,16 @@ constexpr std::string_view kBinaryCsrOutput = "csr"; // Just the raw csr as constexpr std::string_view kBuildPlusCsr = "build+csr"; // Text-encoded (JSON) build // fingerprint plus CSR. +std::string getFullServiceName(const char* descriptor, const char* name) { + return std::string(descriptor) + "/" + name; +} + void writeOutput(const std::string instance_name, const Array& csr) { if (FLAGS_output_format == kBinaryCsrOutput) { auto bytes = csr.encode(); std::copy(bytes.begin(), bytes.end(), std::ostream_iterator<char>(std::cout)); } else if (FLAGS_output_format == kBuildPlusCsr) { - auto [json, error] = jsonEncodeCsrWithBuild(instance_name, csr); + auto [json, error] = jsonEncodeCsrWithBuild(instance_name, csr, FLAGS_serialno_prop); if (!error.empty()) { std::cerr << "Error JSON encoding the output: " << error; exit(1); @@ -67,12 +76,21 @@ void writeOutput(const std::string instance_name, const Array& csr) { } } +void getCsrForIRpc(const char* descriptor, const char* name, IRemotelyProvisionedComponent* irpc) { + auto [request, errMsg] = getCsr(name, irpc, FLAGS_self_test); + auto fullName = getFullServiceName(descriptor, name); + if (!request) { + std::cerr << "Unable to build CSR for '" << fullName << ": " << errMsg << std::endl; + exit(-1); + } + + writeOutput(std::string(name), *request); +} + // Callback for AServiceManager_forEachDeclaredInstance that writes out a CSR // for every IRemotelyProvisionedComponent. void getCsrForInstance(const char* name, void* /*context*/) { - const std::vector<uint8_t> challenge = generateChallenge(); - - auto fullName = std::string(IRemotelyProvisionedComponent::descriptor) + "/" + name; + auto fullName = getFullServiceName(IRemotelyProvisionedComponent::descriptor, name); AIBinder* rkpAiBinder = AServiceManager_getService(fullName.c_str()); ::ndk::SpAIBinder rkp_binder(rkpAiBinder); auto rkp_service = IRemotelyProvisionedComponent::fromBinder(rkp_binder); @@ -81,17 +99,7 @@ void getCsrForInstance(const char* name, void* /*context*/) { exit(-1); } - if (FLAGS_self_test) { - selfTestGetCsr(name, rkp_service.get()); - } else { - auto [request, errMsg] = getCsr(name, rkp_service.get()); - if (!request) { - std::cerr << "Unable to build CSR for '" << fullName << ": " << errMsg << std::endl; - exit(-1); - } - - writeOutput(std::string(name), *request); - } + getCsrForIRpc(IRemotelyProvisionedComponent::descriptor, name, rkp_service.get()); } } // namespace @@ -102,5 +110,10 @@ int main(int argc, char** argv) { AServiceManager_forEachDeclaredInstance(IRemotelyProvisionedComponent::descriptor, /*context=*/nullptr, getCsrForInstance); + // Append drm csr's + for (auto const& e : android::mediadrm::getDrmRemotelyProvisionedComponents()) { + getCsrForIRpc(IDrmFactory::descriptor, e.first.c_str(), e.second.get()); + } + return 0; } diff --git a/provisioner/support/Android.bp b/provisioner/support/Android.bp new file mode 100644 index 00000000..24cfd03c --- /dev/null +++ b/provisioner/support/Android.bp @@ -0,0 +1,64 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_team: "trendy_team_android_hardware_backed_security", + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_security_license"], +} + +cc_defaults { + name: "librkp_support_defaults", + static_libs: [ + "android.hardware.security.rkp-V3-cpp", + "android.security.rkp_aidl-cpp", + ], + shared_libs: [ + "libbase", + "libbinder", + "libutils", + ], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], +} + +cc_library { + name: "librkp_support", + defaults: ["librkp_support_defaults"], + srcs: [ + "rkpd_client.cpp", + ], + export_include_dirs: ["include"], +} + +cc_test { + name: "librkp_support_test", + defaults: [ + "librkp_support_defaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: ["test.cpp"], + static_libs: [ + "librkp_support", + ], + test_suites: ["general-tests"], + require_root: true, +} diff --git a/provisioner/support/TEST_MAPPING b/provisioner/support/TEST_MAPPING new file mode 100644 index 00000000..fc301043 --- /dev/null +++ b/provisioner/support/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "librkp_support_test" + } + ] +} diff --git a/identity/RemotelyProvisionedKey.h b/provisioner/support/include/rkp/support/rkpd_client.h index e7ddfca5..5a7fe6e4 100644 --- a/identity/RemotelyProvisionedKey.h +++ b/provisioner/support/include/rkp/support/rkpd_client.h @@ -20,19 +20,20 @@ #include <optional> #include <android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <android/security/rkp/RemotelyProvisionedKey.h> -namespace android { -namespace security { -namespace identity { +namespace android::security::rkp::support { using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; using ::android::security::rkp::RemotelyProvisionedKey; -std::optional<std::string> getRpcId(const sp<IRemotelyProvisionedComponent>& rpc); - +// Callers of getRpcKeyFuture() and getRpcKey() need at least two threads to +// retrieve the key, one to asynchronously handle binder callbacks and one to +// wait on the future. std::optional<std::future<std::optional<RemotelyProvisionedKey>>> getRpcKeyFuture(const sp<IRemotelyProvisionedComponent>& rpc, int32_t keyId); -} // namespace identity -} // namespace security -} // namespace android +std::optional<RemotelyProvisionedKey> getRpcKey(const sp<IRemotelyProvisionedComponent>& rpc, + int32_t keyId, int32_t timeout_sec = 10); + +} // namespace android::security::rkp::support diff --git a/identity/RemotelyProvisionedKey.cpp b/provisioner/support/rkpd_client.cpp index 784a6803..de1e3bbe 100644 --- a/identity/RemotelyProvisionedKey.cpp +++ b/provisioner/support/rkpd_client.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "credstore" +#define LOG_TAG "rkpd_client" #include <atomic> @@ -25,13 +25,9 @@ #include <android/security/rkp/IRemoteProvisioning.h> #include <binder/IServiceManager.h> #include <binder/Status.h> -#include <vintf/VintfObject.h> +#include <rkp/support/rkpd_client.h> -#include "RemotelyProvisionedKey.h" - -namespace android { -namespace security { -namespace identity { +namespace android::security::rkp::support { namespace { using ::android::binder::Status; @@ -46,13 +42,28 @@ using ::android::security::rkp::RemotelyProvisionedKey; constexpr const char* kRemoteProvisioningServiceName = "remote_provisioning"; +std::optional<std::string> getRpcId(const sp<IRemotelyProvisionedComponent>& rpc) { + RpcHardwareInfo rpcHwInfo; + Status status = rpc->getHardwareInfo(&rpcHwInfo); + if (!status.isOk()) { + LOG(ERROR) << "Error getting remotely provisioned component hardware info: " << status; + return std::nullopt; + } + + if (!rpcHwInfo.uniqueId) { + LOG(ERROR) << "Remotely provisioned component is missing a unique id. " + << "This is a bug in the vendor implementation."; + return std::nullopt; + } + + return *rpcHwInfo.uniqueId; +} + std::optional<String16> findRpcNameById(std::string_view targetRpcId) { - auto deviceManifest = vintf::VintfObject::GetDeviceHalManifest(); - auto instances = deviceManifest->getAidlInstances("android.hardware.security.keymint", - "IRemotelyProvisionedComponent"); - for (const std::string& instance : instances) { - auto rpcName = - IRemotelyProvisionedComponent::descriptor + String16("/") + String16(instance.c_str()); + auto instances = android::defaultServiceManager()->getDeclaredInstances( + IRemotelyProvisionedComponent::descriptor); + for (const auto& instance : instances) { + auto rpcName = IRemotelyProvisionedComponent::descriptor + String16("/") + instance; sp<IRemotelyProvisionedComponent> rpc = android::waitForService<IRemotelyProvisionedComponent>(rpcName); @@ -157,24 +168,6 @@ class GetRegistrationCallback : public BnGetRegistrationCallback { } // namespace -std::optional<std::string> getRpcId(const sp<IRemotelyProvisionedComponent>& rpc) { - RpcHardwareInfo rpcHwInfo; - Status status = rpc->getHardwareInfo(&rpcHwInfo); - if (!status.isOk()) { - LOG(ERROR) << "Error getting remotely provisioned component hardware info: " << status; - return std::nullopt; - } - - if (!rpcHwInfo.uniqueId) { - LOG(ERROR) << "Remotely provisioned component is missing a unique id, which is " - << "required for credential key remotely provisioned attestation keys. " - << "This is a bug in the vendor implementation."; - return std::nullopt; - } - - return *rpcHwInfo.uniqueId; -} - std::optional<std::future<std::optional<RemotelyProvisionedKey>>> getRpcKeyFuture(const sp<IRemotelyProvisionedComponent>& rpc, int32_t keyId) { std::promise<std::optional<RemotelyProvisionedKey>> keyPromise; @@ -203,6 +196,21 @@ getRpcKeyFuture(const sp<IRemotelyProvisionedComponent>& rpc, int32_t keyId) { return keyFuture; } -} // namespace identity -} // namespace security -} // namespace android +std::optional<RemotelyProvisionedKey> getRpcKey(const sp<IRemotelyProvisionedComponent>& rpc, + int32_t keyId, int32_t timeout_sec) { + auto rpcKeyFuture = getRpcKeyFuture(rpc, keyId); + if (!rpcKeyFuture) { + LOG(ERROR) << "Failed getRpcKeyFuture()"; + return std::nullopt; + } + + auto timeout = std::chrono::seconds(timeout_sec); + if (rpcKeyFuture->wait_for(timeout) != std::future_status::ready) { + LOG(ERROR) << "Waiting for remotely provisioned attestation key timed out"; + return std::nullopt; + } + + return rpcKeyFuture->get(); +} + +} // namespace android::security::rkp::support diff --git a/provisioner/support/test.cpp b/provisioner/support/test.cpp new file mode 100644 index 00000000..0e6e2f46 --- /dev/null +++ b/provisioner/support/test.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <android/hardware/security/keymint/IRemotelyProvisionedComponent.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <gtest/gtest.h> +#include <rkp/support/rkpd_client.h> + +using ::android::getAidlHalInstanceNames; +using ::android::sp; +using ::android::String16; +using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; +using ::android::security::rkp::RemotelyProvisionedKey; +using ::android::security::rkp::support::getRpcKey; + +// TODO(b/272600606): Add tests for error cases +class RkpdClientTest : public testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + auto rpcName = String16(GetParam().c_str()); + String16 avfName = String16(IRemotelyProvisionedComponent::descriptor) + String16("/avf"); + if (avfName == rpcName) { + GTEST_SKIP() << "Skipping test for avf"; + } + rpc_ = android::waitForService<IRemotelyProvisionedComponent>(rpcName); + ASSERT_NE(rpc_, nullptr); + } + + sp<IRemotelyProvisionedComponent> rpc_; +}; + +TEST_P(RkpdClientTest, getRpcKey) { + std::optional<RemotelyProvisionedKey> key = getRpcKey(rpc_, /*keyId=*/0); + + ASSERT_TRUE(key.has_value()) << "Failed to get remotely provisioned attestation key"; + ASSERT_FALSE(key->keyBlob.empty()) << "Key blob is empty"; + ASSERT_FALSE(key->encodedCertChain.empty()) << "Certificate is empty"; +} + +INSTANTIATE_TEST_SUITE_P( + PerInstance, RkpdClientTest, + testing::ValuesIn(getAidlHalInstanceNames(IRemotelyProvisionedComponent::descriptor)), + ::android::PrintInstanceNameToString); + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + + // We need one thread to issue requests to RKPD and one to handle + // asynchronous responses from RKPD. + android::ProcessState::self()->setThreadPoolMaxThreadCount(2); + android::ProcessState::self()->startThreadPool(); + return RUN_ALL_TESTS(); +} |