diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-06-15 21:51:24 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-06-15 21:51:24 +0000 |
commit | 9c82adfa8ddf2ba7feee165b115e1ea2393aee29 (patch) | |
tree | 4092577b8206991d7a58795c34f03909f3a2d8c0 | |
parent | ef2e840501d24a0785124f3bf568fe051e38309a (diff) | |
parent | cd5ee701bcf1d5d23769c22c0bbad98a3c4fa431 (diff) | |
download | security-android12-mainline-tzdata3-release.tar.gz |
Snap for 8730993 from cd5ee701bcf1d5d23769c22c0bbad98a3c4fa431 to mainline-tzdata3-releaseaml_tz3_314012070aml_tz3_314012050aml_tz3_314012010aml_tz3_313110000aml_tz3_312511020aml_tz3_312511010aml_tz3_312410020aml_tz3_312410010android12-mainline-tzdata3-releaseaml_tz3_314012010
Change-Id: I08e25e166197e06dd7718380e3c67ed2a5ba4bc6
167 files changed, 3057 insertions, 16439 deletions
@@ -1,10 +1,6 @@ -alanstokes@google.com +swillden@google.com cbrubaker@google.com -hasinitg@google.com -jbires@google.com jdanis@google.com -jeffv@google.com +hasinitg@google.com kroot@google.com -sethmo@google.com -swillden@google.com zeuthen@google.com diff --git a/diced/Android.bp b/diced/Android.bp deleted file mode 100644 index e13d863d..00000000 --- a/diced/Android.bp +++ /dev/null @@ -1,228 +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 { - // 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_library { - name: "libdiced_utils", - crate_name: "diced_utils", - srcs: ["src/utils.rs"], - vendor_available: true, - - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libkeystore2_crypto_rust", - ], -} - -rust_test { - name: "diced_utils_test", - crate_name: "diced_utils_test", - srcs: ["src/utils.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libkeystore2_crypto_rust", - ], -} - -rust_library { - name: "libdiced_sample_inputs", - crate_name: "diced_sample_inputs", - srcs: ["src/sample_inputs.rs"], - vendor_available: true, - - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libdiced_utils", - "libkeystore2_crypto_rust", - ], -} - -rust_test { - name: "diced_sample_inputs_test", - crate_name: "diced_sample_inputs_test", - srcs: ["src/sample_inputs.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libdiced_utils", - "libkeystore2_crypto_rust", - ], -} - -rust_library { - name: "libdiced", - crate_name: "diced", - srcs: ["src/lib.rs"], - - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "android.security.dice-rust", - "libdiced_open_dice_cbor", - "libanyhow", - "libbinder_rs", - "libdiced_utils", - "libkeystore2_crypto_rust", - "libkeystore2_selinux", - "liblibc", - "liblog_rust", - "libthiserror", - ], -} - -rust_library { - name: "libdiced_vendor", - crate_name: "diced", - srcs: ["src/lib_vendor.rs"], - - vendor_available: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libdiced_open_dice_cbor", - "libanyhow", - "libbinder_rs", - "libdiced_utils", - "libkeystore2_crypto_rust", - "liblibc", - "liblog_rust", - "libnix", - "libserde", - "libserde_cbor", - "libthiserror", - ], -} - -rust_binary { - name: "diced", - srcs: ["src/diced_main.rs"], - prefer_rlib: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libandroid_logger", - "libbinder_rs", - "libdiced", - "libdiced_open_dice_cbor", - "libdiced_sample_inputs", - "libdiced_utils", - "liblog_rust", - ], - init_rc: ["diced.rc"], -} - -rust_binary { - name: "diced.microdroid", - srcs: ["src/diced_main.rs"], - prefer_rlib: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libandroid_logger", - "libbinder_rs", - "libdiced", - "libdiced_open_dice_cbor", - "libdiced_sample_inputs", - "libdiced_utils", - "liblog_rust", - ], - init_rc: ["diced.microdroid.rc"], - bootstrap: true, -} - -rust_test { - name: "diced_test", - crate_name: "diced_test", - srcs: ["src/lib.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "android.security.dice-rust", - "libanyhow", - "libbinder_rs", - "libdiced_open_dice_cbor", - "libdiced_utils", - "libkeystore2_crypto_rust", - "libkeystore2_selinux", - "libkeystore2_vintf_rust", - "liblibc", - "liblog_rust", - "libnix", - "libserde", - "libserde_cbor", - "libthiserror", - ], -} - -rust_test { - name: "diced_vendor_test", - crate_name: "diced_vendor_test", - srcs: ["src/lib_vendor.rs"], - test_suites: ["general-tests"], - auto_gen_config: true, - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "libanyhow", - "libdiced_open_dice_cbor", - "libdiced_sample_inputs", - "libdiced_utils", - "libbinder_rs", - "libkeystore2_crypto_rust", - "liblibc", - "liblog_rust", - "libnix", - "libserde", - "libserde_cbor", - "libthiserror", - ], -} - -rust_test { - name: "diced_client_test", - srcs: [ - "src/diced_client_test.rs", - ], - require_root: true, - auto_gen_config: true, - test_suites: [ - "general-tests", - ], - - rustlibs: [ - "android.hardware.security.dice-V1-rust", - "android.security.dice-rust", - "libanyhow", - "libbinder_rs", - "libdiced_open_dice_cbor", - "libdiced_sample_inputs", - "libdiced_utils", - "libnix", - ], -} diff --git a/diced/aidl/Android.bp b/diced/aidl/Android.bp deleted file mode 100644 index 75c18564..00000000 --- a/diced/aidl/Android.bp +++ /dev/null @@ -1,50 +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 { - // 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"], -} - -aidl_interface { - name: "android.security.dice", - srcs: [ "android/security/dice/*.aidl" ], - unstable: true, - imports: ["android.hardware.security.dice-V1"], - backend: { - java: { - enabled: false, - platform_apis: false, - }, - rust: { - enabled: true, - apex_available: [ - "//apex_available:platform", - "com.android.compos", - ], - }, - ndk: { - enabled: true, - apps_enabled: false, - apex_available: [ - "//apex_available:platform", - "com.android.compos", - ], - } - }, -} diff --git a/diced/aidl/android/security/dice/IDiceMaintenance.aidl b/diced/aidl/android/security/dice/IDiceMaintenance.aidl deleted file mode 100644 index c81fdea1..00000000 --- a/diced/aidl/android/security/dice/IDiceMaintenance.aidl +++ /dev/null @@ -1,39 +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.dice; - -import android.hardware.security.dice.InputValues; - -/** - * The maintenance allows callers to prompt the DICE node to demote itself. - * - * @hide - */ -@SensitiveData -interface IDiceMaintenance { - /** - * The implementation must demote itself by deriving new effective artifacts - * based on the list of input data passed to the function. - * As opposed to the IDiceNode::demote, this function effects all clients of - * the implementation. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the caller does not have the demote_self permission. - * May produce any ResponseCode if anything went wrong. - */ - void demoteSelf(in InputValues[] input_values); -} diff --git a/diced/aidl/android/security/dice/IDiceNode.aidl b/diced/aidl/android/security/dice/IDiceNode.aidl deleted file mode 100644 index 2b3ef764..00000000 --- a/diced/aidl/android/security/dice/IDiceNode.aidl +++ /dev/null @@ -1,96 +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.dice; - -import android.hardware.security.dice.Bcc; -import android.hardware.security.dice.BccHandover; -import android.hardware.security.dice.InputValues; -import android.hardware.security.dice.Signature; - -/** - * An implementation of IDiceNode provides access to DICE secrets to its clients. It - * uses binder's caller UID and security context to identify its callers and assures - * That clients can only access their specific DICE secrets. - * It may operate in two different modes, resident mode and proxy mode. - * - * ## Resident mode. - * In resident mode, the node is in possession of the secrets corresponding to its level in - * the dice tree. It can act as root of the sub tree that it serves. The secrets are memory - * resident in the node. It identifies its callers and prepends the caller's identity to the - * request's vector of input values. It then derives the required secrets by iterating through - * the request's vector of input values in ascending order. - * - * ## Proxy mode. - * In proxy mode, the node has a connection to a parent node. It serves its callers by verifying - * their identity, by prefixing the client's vector of input values with client's identity, and - * forwarding the request to the next level up. - * - * The modes are implementation details that are completely transparent to the clients. - * - * Privacy: Unprivileged apps may not use this service ever because it may provide access to a - * device specific id that is stable across reinstalls, reboots, and applications. - * - * @hide - */ -@SensitiveData -interface IDiceNode { - /** - * Uses the a key derived from the caller's attestation secret to sign the payload using - * RFC 8032 PureEd25519 and returns the signature. The payload is limited to 1024 bytes. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the caller does not have the use_sign permission. - */ - Signature sign(in InputValues[] id, in byte[] payload); - - /** - * Returns the attestation certificate chain of the caller if `inputValues` is empty or the - * chain to the given child of the caller identified by the `inputValues` vector. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the caller does not have the get_attestation_chain - * permission. - */ - Bcc getAttestationChain(in InputValues[] inputValues); - - /** - * This function allows a client to become a resident node. Called with empty InputValues - * vectors, an implementation returns the client's DICE secrets. If inputValues is - * not empty, the appropriate derivations are performed starting from the client's level. - * The function must never return secrets pertaining to the implementation or a parent - * thereof in the DICE hierarchy. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the implementation does not allow resident nodes - * at the client's level. - */ - BccHandover derive(in InputValues[] inputValues); - - /** - * The client demotes itself to the given identity. When serving the calling client, - * the implementation must append the given identities. Essentially, the client assumes - * the identity of one of its children. This operation is not reversible, i.e., there - * is no promotion. Further demotion is possible. - * - * If the operation fails for any reason. No further services must be provided. Ideally, - * a device shutdown/reboot is triggered. - * - * ## Error as service specific exception: - * ResponseCode::PERMISSION_DENIED if the caller does not have the demote permission. - */ - void demote(in InputValues[] inputValues); -} diff --git a/diced/aidl/android/security/dice/ResponseCode.aidl b/diced/aidl/android/security/dice/ResponseCode.aidl deleted file mode 100644 index 7c660580..00000000 --- a/diced/aidl/android/security/dice/ResponseCode.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.dice; - -@Backing(type="int") -/** - * Service specific error codes. - * @hide - */ -enum ResponseCode { - /** - * The caller has insufficient privilege to access the DICE API. - */ - PERMISSION_DENIED = 1, - /** - * An unexpected error occurred, likely with IO or IPC. - */ - SYSTEM_ERROR = 2, - /** - * Returned if the called function is not implemented. - */ - NOT_IMPLEMENTED = 3, -} diff --git a/diced/diced.microdroid.rc b/diced/diced.microdroid.rc deleted file mode 100644 index 2226f473..00000000 --- a/diced/diced.microdroid.rc +++ /dev/null @@ -1,13 +0,0 @@ -# Start the Diced service. -# -# See system/core/init/README.md for information on the init.rc language. - -service diced /system/bin/diced.microdroid - class main - user diced - group diced - # The diced service must not be allowed to restart. - # If it crashes for any reason security critical state is lost. - # The only remedy is to restart the device. - oneshot - writepid /dev/cpuset/foreground/tasks diff --git a/diced/diced.rc b/diced/diced.rc deleted file mode 100644 index 8c43fa5e..00000000 --- a/diced/diced.rc +++ /dev/null @@ -1,13 +0,0 @@ -# Start the Diced service. -# -# See system/core/init/README.md for information on the init.rc language. - -service diced /system/bin/diced - class main - user diced - group diced - # The diced service must not be allowed to restart. - # If it crashes for any reason security critical state is lost. - # The only remedy is to restart the device. - oneshot - writepid /dev/cpuset/foreground/tasks diff --git a/diced/open_dice_cbor/Android.bp b/diced/open_dice_cbor/Android.bp deleted file mode 100644 index 3e67045a..00000000 --- a/diced/open_dice_cbor/Android.bp +++ /dev/null @@ -1,55 +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 { - default_applicable_licenses: ["system_security_license"], -} - -rust_library { - name: "libdiced_open_dice_cbor", - crate_name: "diced_open_dice_cbor", - srcs: ["lib.rs"], - - rustlibs: [ - // For ZVec - "libkeystore2_crypto_rust", - "libopen_dice_bcc_bindgen", - "libopen_dice_cbor_bindgen", - "libthiserror", - ], - static_libs: [ - "libopen_dice_bcc", - "libopen_dice_cbor", - ], - vendor_available: true, -} - -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_sample_inputs", - "libkeystore2_crypto_rust", - "libopen_dice_bcc_bindgen", - "libopen_dice_cbor_bindgen", - "libthiserror", - ], - static_libs: [ - "libopen_dice_bcc", - "libopen_dice_cbor", - ], -} diff --git a/diced/open_dice_cbor/lib.rs b/diced/open_dice_cbor/lib.rs deleted file mode 100644 index 7122ca51..00000000 --- a/diced/open_dice_cbor/lib.rs +++ /dev/null @@ -1,1037 +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 safe wrappers around the public API of libopen-dice. -//! ## Example: -//! ``` -//! use diced_open_dice_cbor as dice; -//! -//! let context = dice::dice::OpenDiceCborContext::new() -//! let parent_cdi_attest = [1u8, dice::CDI_SIZE]; -//! let parent_cdi_seal = [2u8, dice::CDI_SIZE]; -//! let input_values = dice::InputValuesOwned { -//! code_hash: [3u8, dice::HASH_SIZE], -//! config: dice::ConfigOwned::Descriptor("My descriptor".as_bytes().to_vec()), -//! authority_hash: [0u8, dice::HASH_SIZE], -//! mode: dice::Mode::Normal, -//! hidden: [0u8, dice::HIDDEN_SIZE], -//! }; -//! let (cdi_attest, cdi_seal, cert_chain) = context -//! .main_flow(&parent_cdi_attest, &parent_cdi_seal, &input_values)?; -//! ``` - -use keystore2_crypto::{zvec, ZVec}; -use open_dice_bcc_bindgen::BccMainFlow; -use open_dice_cbor_bindgen::{ - DiceConfigType, DiceDeriveCdiCertificateId, DiceDeriveCdiPrivateKeySeed, - DiceGenerateCertificate, DiceHash, DiceInputValues, DiceKdf, DiceKeypairFromSeed, DiceMainFlow, - DiceMode, DiceResult, DiceSign, DiceVerify, 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 open_dice_cbor_bindgen::{ - DiceConfigType_kDiceConfigTypeDescriptor as DICE_CONFIG_TYPE_DESCRIPTOR, - DiceConfigType_kDiceConfigTypeInline as DICE_CONFIG_TYPE_INLINE, - DiceMode_kDiceModeDebug as DICE_MODE_DEBUG, - DiceMode_kDiceModeMaintenance as DICE_MODE_RECOVERY, - DiceMode_kDiceModeNormal as DICE_MODE_NORMAL, - DiceMode_kDiceModeNotInitialized as DICE_MODE_NOT_CONFIGURED, - DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL, - DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT, - DiceResult_kDiceResultOk as DICE_RESULT_OK, - DiceResult_kDiceResultPlatformError as DICE_RESULT_PLATFORM_ERROR, -}; -use std::ffi::{c_void, NulError}; - -/// 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. -pub const INLINE_CONFIG_SIZE: usize = DICE_INLINE_CONFIG_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 CDI. -pub const CDI_SIZE: usize = DICE_CDI_SIZE as usize; -/// The size of an ID. -pub const ID_SIZE: usize = DICE_ID_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; - -/// Open dice wrapper error type. -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum Error { - /// The libopen-dice backend reported InvalidInput. - #[error("Open dice backend: Invalid input")] - InvalidInput, - /// The libopen-dice backend reported BufferTooSmall. - #[error("Open dice backend: Buffer too small")] - BufferTooSmall, - /// The libopen-dice backend reported PlatformError. - #[error("Open dice backend: Platform error")] - PlatformError, - /// The libopen-dice backend reported an error that is outside of the defined range of errors. - /// The returned error code is embedded in this value. - #[error("Open dice backend returned an unexpected error code: {0:?}")] - Unexpected(u32), - - /// The allocation of a ZVec failed. Most likely due to a failure during the call to mlock. - #[error("ZVec allocation failed")] - ZVec(#[from] zvec::Error), - - /// Functions that have to convert str to CString may fail if the string has an interior - /// nul byte. - #[error("Input string has an interior nul byte.")] - CStrNulError(#[from] NulError), -} - -/// Open dice result type. -pub type Result<T> = std::result::Result<T, Error>; - -impl From<DiceResult> for Error { - fn from(result: DiceResult) -> Self { - match result { - DICE_RESULT_INVALID_INPUT => Error::InvalidInput, - DICE_RESULT_BUFFER_TOO_SMALL => Error::BufferTooSmall, - DICE_RESULT_PLATFORM_ERROR => Error::PlatformError, - r => Error::Unexpected(r), - } - } -} - -fn check_result(result: DiceResult) -> Result<()> { - if result == DICE_RESULT_OK { - Ok(()) - } else { - Err(result.into()) - } -} - -/// Configuration descriptor for dice input values. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Config<'a> { - /// A reference to an inline descriptor. - Inline(&'a [u8; INLINE_CONFIG_SIZE]), - /// A reference to a free form descriptor that will be hashed by the implementation. - Descriptor(&'a [u8]), -} - -enum ConfigOwned { - Inline([u8; INLINE_CONFIG_SIZE]), - Descriptor(Vec<u8>), -} - -impl Config<'_> { - fn get_type(&self) -> DiceConfigType { - match self { - Self::Inline(_) => DICE_CONFIG_TYPE_INLINE, - Self::Descriptor(_) => DICE_CONFIG_TYPE_DESCRIPTOR, - } - } - - fn get_inline(&self) -> [u8; INLINE_CONFIG_SIZE] { - match self { - Self::Inline(inline) => **inline, - _ => [0u8; INLINE_CONFIG_SIZE], - } - } - - fn get_descriptor_as_ptr(&self) -> *const u8 { - match self { - Self::Descriptor(descriptor) => descriptor.as_ptr(), - _ => std::ptr::null(), - } - } - - fn get_descriptor_size(&self) -> usize { - match self { - Self::Descriptor(descriptor) => descriptor.len(), - _ => 0, - } - } -} - -impl From<Config<'_>> for ConfigOwned { - fn from(config: Config) -> Self { - match config { - Config::Inline(inline) => ConfigOwned::Inline(*inline), - Config::Descriptor(descriptor) => ConfigOwned::Descriptor(descriptor.to_owned()), - } - } -} - -/// DICE modes as defined here: -/// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md#mode-value-details -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Mode { - /// See documentation linked above. - NotConfigured = 0, - /// See documentation linked above. - Normal = 1, - /// See documentation linked above. - Debug = 2, - /// See documentation linked above. - Recovery = 3, -} - -impl Mode { - fn get_internal(&self) -> DiceMode { - match self { - Self::NotConfigured => DICE_MODE_NOT_CONFIGURED, - Self::Normal => DICE_MODE_NORMAL, - Self::Debug => DICE_MODE_DEBUG, - Self::Recovery => DICE_MODE_RECOVERY, - } - } -} - -/// This trait allows API users to supply DICE input values without copying. -pub trait InputValues { - /// Returns the code hash. - fn code_hash(&self) -> &[u8; HASH_SIZE]; - /// Returns the config. - fn config(&self) -> Config; - /// Returns the authority hash. - fn authority_hash(&self) -> &[u8; HASH_SIZE]; - /// Returns the authority descriptor. - fn authority_descriptor(&self) -> Option<&[u8]>; - /// Returns the mode. - fn mode(&self) -> Mode; - /// Returns the hidden value. - fn hidden(&self) -> &[u8; HIDDEN_SIZE]; -} - -/// An owning convenience type implementing `InputValues`. -pub struct InputValuesOwned { - code_hash: [u8; HASH_SIZE], - config: ConfigOwned, - authority_hash: [u8; HASH_SIZE], - authority_descriptor: Option<Vec<u8>>, - mode: Mode, - hidden: [u8; HIDDEN_SIZE], -} - -impl InputValuesOwned { - /// Construct a new instance of InputValuesOwned. - pub fn new( - code_hash: [u8; HASH_SIZE], - config: Config, - authority_hash: [u8; HASH_SIZE], - authority_descriptor: Option<Vec<u8>>, - mode: Mode, - hidden: [u8; HIDDEN_SIZE], - ) -> Self { - Self { - code_hash, - config: config.into(), - authority_hash, - authority_descriptor, - mode, - hidden, - } - } -} - -impl InputValues for InputValuesOwned { - fn code_hash(&self) -> &[u8; HASH_SIZE] { - &self.code_hash - } - fn config(&self) -> Config { - match &self.config { - ConfigOwned::Inline(inline) => Config::Inline(inline), - ConfigOwned::Descriptor(descriptor) => Config::Descriptor(descriptor.as_slice()), - } - } - fn authority_hash(&self) -> &[u8; HASH_SIZE] { - &self.authority_hash - } - fn authority_descriptor(&self) -> Option<&[u8]> { - self.authority_descriptor.as_deref() - } - fn mode(&self) -> Mode { - self.mode - } - fn hidden(&self) -> &[u8; HIDDEN_SIZE] { - &self.hidden - } -} - -fn call_with_input_values<T: InputValues + ?Sized, F, R>(input_values: &T, f: F) -> Result<R> -where - F: FnOnce(*const DiceInputValues) -> Result<R>, -{ - let input_values = DiceInputValues { - code_hash: *input_values.code_hash(), - code_descriptor: std::ptr::null(), - code_descriptor_size: 0, - config_type: input_values.config().get_type(), - config_value: input_values.config().get_inline(), - config_descriptor: input_values.config().get_descriptor_as_ptr(), - config_descriptor_size: input_values.config().get_descriptor_size(), - authority_hash: *input_values.authority_hash(), - authority_descriptor: input_values - .authority_descriptor() - .map_or_else(std::ptr::null, <[u8]>::as_ptr), - authority_descriptor_size: input_values.authority_descriptor().map_or(0, <[u8]>::len), - mode: input_values.mode().get_internal(), - hidden: *input_values.hidden(), - }; - - f(&input_values as *const DiceInputValues) -} - -/// Multiple of the open dice function required preallocated output buffer -/// which may be too small, this function implements the retry logic to handle -/// too small buffer allocations. -/// The callback `F` must expect a mutable reference to a buffer and a size hint -/// field. The callback is called repeatedly as long as it returns -/// `Err(Error::BufferTooSmall)`. If the size hint remains 0, the buffer size is -/// doubled with each iteration. If the size hint is set by the callback, the buffer -/// will be set to accommodate at least this many bytes. -/// If the callback returns `Ok(())`, the buffer is truncated to the size hint -/// exactly. -/// The function panics if the callback returns `Ok(())` and the size hint is -/// larger than the buffer size. -fn retry_while_adjusting_output_buffer<F>(mut f: F) -> Result<Vec<u8>> -where - F: FnMut(&mut Vec<u8>, &mut usize) -> Result<()>, -{ - let mut buffer = vec![0; INITIAL_OUT_BUFFER_SIZE]; - let mut actual_size: usize = 0; - loop { - match f(&mut buffer, &mut actual_size) { - // If Error::BufferTooSmall was returned, the allocated certificate - // buffer was to small for the output. So the buffer is resized to the actual - // size, and a second attempt is made with the new buffer. - Err(Error::BufferTooSmall) => { - let new_size = if actual_size == 0 { - // Due to an off spec implementation of open dice cbor, actual size - // does not return the required size if the buffer was too small. So - // we have to try and approach it gradually. - buffer.len() * 2 - } else { - actual_size - }; - buffer.resize(new_size, 0); - continue; - } - Err(e) => return Err(e), - Ok(()) => { - if actual_size > buffer.len() { - panic!( - "actual_size larger than buffer size: open-dice function - may have written past the end of the buffer." - ); - } - // Truncate the certificate buffer to the actual size because it may be - // smaller than the original allocation. - buffer.truncate(actual_size); - return Ok(buffer); - } - } - } -} - -/// Some libopen-dice variants use a context. Developers that want to customize these -/// bindings may want to implement their own Context factory that creates a context -/// useable by their preferred backend. -pub trait Context { - /// # Safety - /// The return value of get_context is passed to any open dice function. - /// Implementations must explain why the context pointer returned is safe - /// to be used by the open dice library. - unsafe fn get_context(&mut self) -> *mut c_void; -} - -impl<T: Context + Send> ContextImpl for T {} - -/// This represents a context for the open dice library. The wrapped open dice instance, which -/// is based on boringssl and cbor, does not use a context, so that this type is empty. -#[derive(Default)] -pub struct OpenDiceCborContext(); - -impl OpenDiceCborContext { - /// Construct a new instance of OpenDiceCborContext. - pub fn new() -> Self { - Default::default() - } -} - -impl Context for OpenDiceCborContext { - unsafe fn get_context(&mut self) -> *mut c_void { - // # Safety - // The open dice cbor implementation does not use a context. It is safe - // to return NULL. - std::ptr::null_mut() - } -} - -/// Type alias for ZVec indicating that it holds a CDI_ATTEST secret. -pub type CdiAttest = ZVec; - -/// Type alias for ZVec indicating that it holds a CDI_SEAL secret. -pub type CdiSeal = ZVec; - -/// Type alias for Vec<u8> indicating that it hold a DICE certificate. -pub type Cert = Vec<u8>; - -/// Type alias for Vec<u8> indicating that it holds a BCC certificate chain. -pub type Bcc = Vec<u8>; - -const INITIAL_OUT_BUFFER_SIZE: usize = 1024; - -/// ContextImpl is a mixin trait that implements the safe wrappers around the open dice -/// library calls. Implementations must implement Context::get_context(). As of -/// this writing, the only implementation is OpenDiceCborContext, which returns NULL. -pub trait ContextImpl: Context + Send { - /// Safe wrapper around open-dice DiceDeriveCdiPrivateKeySeed, see open dice - /// documentation for details. - fn derive_cdi_private_key_seed(&mut self, cdi_attest: &[u8; CDI_SIZE]) -> Result<ZVec> { - let mut seed = ZVec::new(PRIVATE_KEY_SEED_SIZE)?; - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument is expected to be a const array of size CDI_SIZE. - // * The third argument is expected to be a non const array of size - // PRIVATE_KEY_SEED_SIZE which is fulfilled if the call to ZVec::new above - // succeeds. - // * No pointers are expected to be valid beyond the scope of the function - // call. - check_result(unsafe { - DiceDeriveCdiPrivateKeySeed(self.get_context(), cdi_attest.as_ptr(), seed.as_mut_ptr()) - })?; - Ok(seed) - } - - /// Safe wrapper around open-dice DiceDeriveCdiCertificateId, see open dice - /// documentation for details. - fn derive_cdi_certificate_id(&mut self, cdi_public_key: &[u8]) -> Result<ZVec> { - let mut id = ZVec::new(ID_SIZE)?; - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument is expected to be a const array with a size given by the - // third argument. - // * The fourth argument is expected to be a non const array of size - // ID_SIZE which is fulfilled if the call to ZVec::new above succeeds. - // * No pointers are expected to be valid beyond the scope of the function - // call. - check_result(unsafe { - DiceDeriveCdiCertificateId( - self.get_context(), - cdi_public_key.as_ptr(), - cdi_public_key.len(), - id.as_mut_ptr(), - ) - })?; - Ok(id) - } - - /// Safe wrapper around open-dice DiceMainFlow, see open dice - /// documentation for details. - /// Returns a tuple of: - /// * The next attestation CDI, - /// * the next seal CDI, and - /// * the next attestation certificate. - /// `(next_attest_cdi, next_seal_cdi, next_attestation_cert)` - fn main_flow<T: InputValues + ?Sized>( - &mut self, - current_cdi_attest: &[u8; CDI_SIZE], - current_cdi_seal: &[u8; CDI_SIZE], - input_values: &T, - ) -> Result<(CdiAttest, CdiSeal, Cert)> { - let mut next_attest = CdiAttest::new(CDI_SIZE)?; - let mut next_seal = CdiSeal::new(CDI_SIZE)?; - - // SAFETY (DiceMainFlow): - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are const arrays of size CDI_SIZE. - // This is fulfilled as per the definition of the arguments `current_cdi_attest` - // and `current_cdi_seal. - // * The fourth argument is a pointer to `DiceInputValues`. It, and its indirect - // references must be valid for the duration of the function call which - // is guaranteed by `call_with_input_values` which puts `DiceInputValues` - // on the stack and initializes it from the `input_values` argument which - // implements the `InputValues` trait. - // * The fifth and sixth argument are the length of and the pointer to the - // allocated certificate buffer respectively. They are used to return - // the generated certificate. - // * The seventh argument is a pointer to a mutable usize object. It is - // used to return the actual size of the output certificate. - // * The eighth argument and the ninth argument are pointers to mutable buffers of size - // CDI_SIZE. This is fulfilled if the allocation above succeeded. - // * No pointers are expected to be valid beyond the scope of the function - // call. - call_with_input_values(input_values, |input_values| { - let cert = retry_while_adjusting_output_buffer(|cert, actual_size| { - check_result(unsafe { - DiceMainFlow( - self.get_context(), - current_cdi_attest.as_ptr(), - current_cdi_seal.as_ptr(), - input_values, - cert.len(), - cert.as_mut_ptr(), - actual_size as *mut _, - next_attest.as_mut_ptr(), - next_seal.as_mut_ptr(), - ) - }) - })?; - Ok((next_attest, next_seal, cert)) - }) - } - - /// Safe wrapper around open-dice DiceHash, see open dice - /// documentation for details. - fn hash(&mut self, input: &[u8]) -> Result<Vec<u8>> { - let mut output: Vec<u8> = vec![0; HASH_SIZE]; - - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are the pointer to and length of the given - // input buffer respectively. - // * The fourth argument must be a pointer to a mutable buffer of size HASH_SIZE - // which is fulfilled by the allocation above. - check_result(unsafe { - DiceHash(self.get_context(), input.as_ptr(), input.len(), output.as_mut_ptr()) - })?; - Ok(output) - } - - /// Safe wrapper around open-dice DiceKdf, see open dice - /// documentation for details. - fn kdf(&mut self, length: usize, input_key: &[u8], salt: &[u8], info: &[u8]) -> Result<ZVec> { - let mut output = ZVec::new(length)?; - - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument is primitive. - // * The third argument and the fourth argument are the pointer to and length of the given - // input key. - // * The fifth argument and the sixth argument are the pointer to and length of the given - // salt. - // * The seventh argument and the eighth argument are the pointer to and length of the - // given info field. - // * The ninth argument is a pointer to the output buffer which must have the - // length given by the `length` argument (see second argument). This is - // fulfilled if the allocation of `output` succeeds. - // * All pointers must be valid for the duration of the function call, but not - // longer. - check_result(unsafe { - DiceKdf( - self.get_context(), - length, - input_key.as_ptr(), - input_key.len(), - salt.as_ptr(), - salt.len(), - info.as_ptr(), - info.len(), - output.as_mut_ptr(), - ) - })?; - Ok(output) - } - - /// Safe wrapper around open-dice DiceKeyPairFromSeed, see open dice - /// documentation for details. - fn keypair_from_seed(&mut self, seed: &[u8; PRIVATE_KEY_SEED_SIZE]) -> Result<(Vec<u8>, ZVec)> { - let mut private_key = ZVec::new(PRIVATE_KEY_SIZE)?; - let mut public_key = vec![0u8; PUBLIC_KEY_SIZE]; - - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument is a pointer to a const buffer of size `PRIVATE_KEY_SEED_SIZE` - // fulfilled by the definition of the argument. - // * The third argument and the fourth argument are mutable buffers of size - // `PRIVATE_KEY_SIZE` and `PUBLIC_KEY_SIZE` respectively. This is fulfilled by the - // allocations above. - // * All pointers must be valid for the duration of the function call but not beyond. - check_result(unsafe { - DiceKeypairFromSeed( - self.get_context(), - seed.as_ptr(), - public_key.as_mut_ptr(), - private_key.as_mut_ptr(), - ) - })?; - Ok((public_key, private_key)) - } - - /// Safe wrapper around open-dice DiceSign, see open dice - /// documentation for details. - fn sign(&mut self, message: &[u8], private_key: &[u8; PRIVATE_KEY_SIZE]) -> Result<Vec<u8>> { - let mut signature = vec![0u8; SIGNATURE_SIZE]; - - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are the pointer to and length of the given - // message buffer. - // * The fourth argument is a const buffer of size `PRIVATE_KEY_SIZE`. This is fulfilled - // by the definition of `private key`. - // * The fifth argument is mutable buffer of size `SIGNATURE_SIZE`. This is fulfilled - // by the allocation above. - // * All pointers must be valid for the duration of the function call but not beyond. - check_result(unsafe { - DiceSign( - self.get_context(), - message.as_ptr(), - message.len(), - private_key.as_ptr(), - signature.as_mut_ptr(), - ) - })?; - Ok(signature) - } - - /// Safe wrapper around open-dice DiceVerify, see open dice - /// documentation for details. - fn verify( - &mut self, - message: &[u8], - signature: &[u8; SIGNATURE_SIZE], - public_key: &[u8; PUBLIC_KEY_SIZE], - ) -> Result<()> { - // SAFETY: - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are the pointer to and length of the given - // message buffer. - // * The fourth argument is a const buffer of size `SIGNATURE_SIZE`. This is fulfilled - // by the definition of `signature`. - // * The fifth argument is a const buffer of size `PUBLIC_KEY_SIZE`. This is fulfilled - // by the definition of `public_key`. - // * All pointers must be valid for the duration of the function call but not beyond. - check_result(unsafe { - DiceVerify( - self.get_context(), - message.as_ptr(), - message.len(), - signature.as_ptr(), - public_key.as_ptr(), - ) - }) - } - - /// Safe wrapper around open-dice DiceGenerateCertificate, see open dice - /// documentation for details. - fn generate_certificate<T: InputValues>( - &mut self, - subject_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], - authority_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE], - input_values: &T, - ) -> Result<Vec<u8>> { - // SAFETY (DiceMainFlow): - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are const arrays of size - // `PRIVATE_KEY_SEED_SIZE`. This is fulfilled as per the definition of the arguments. - // * The fourth argument is a pointer to `DiceInputValues` it, and its indirect - // references must be valid for the duration of the function call which - // is guaranteed by `call_with_input_values` which puts `DiceInputValues` - // on the stack and initializes it from the `input_values` argument which - // implements the `InputValues` trait. - // * The fifth argument and the sixth argument are the length of and the pointer to the - // allocated certificate buffer respectively. They are used to return - // the generated certificate. - // * The seventh argument is a pointer to a mutable usize object. It is - // used to return the actual size of the output certificate. - // * All pointers must be valid for the duration of the function call but not beyond. - call_with_input_values(input_values, |input_values| { - let cert = retry_while_adjusting_output_buffer(|cert, actual_size| { - check_result(unsafe { - DiceGenerateCertificate( - self.get_context(), - subject_private_key_seed.as_ptr(), - authority_private_key_seed.as_ptr(), - input_values, - cert.len(), - cert.as_mut_ptr(), - actual_size as *mut _, - ) - }) - })?; - Ok(cert) - }) - } - - /// Safe wrapper around open-dice BccDiceMainFlow, see open dice - /// documentation for details. - /// Returns a tuple of: - /// * The next attestation CDI, - /// * the next seal CDI, and - /// * the next bcc adding the new certificate to the given bcc. - /// `(next_attest_cdi, next_seal_cdi, next_bcc)` - fn bcc_main_flow<T: InputValues + ?Sized>( - &mut self, - current_cdi_attest: &[u8; CDI_SIZE], - current_cdi_seal: &[u8; CDI_SIZE], - bcc: &[u8], - input_values: &T, - ) -> Result<(CdiAttest, CdiSeal, Bcc)> { - let mut next_attest = CdiAttest::new(CDI_SIZE)?; - let mut next_seal = CdiSeal::new(CDI_SIZE)?; - - // SAFETY (BccMainFlow): - // * The first context argument may be NULL and is unused by the wrapped - // implementation. - // * The second argument and the third argument are const arrays of size CDI_SIZE. - // This is fulfilled as per the definition of the arguments `current_cdi_attest` - // and `current_cdi_seal`. - // * The fourth argument and the fifth argument are the pointer to and size of the buffer - // holding the current bcc. - // * The sixth argument is a pointer to `DiceInputValues` it, and its indirect - // references must be valid for the duration of the function call which - // is guaranteed by `call_with_input_values` which puts `DiceInputValues` - // on the stack and initializes it from the `input_values` argument which - // implements the `InputValues` trait. - // * The seventh argument and the eighth argument are the length of and the pointer to the - // allocated certificate buffer respectively. They are used to return the generated - // certificate. - // * The ninth argument is a pointer to a mutable usize object. It is - // used to return the actual size of the output certificate. - // * The tenth argument and the eleventh argument are pointers to mutable buffers of - // size CDI_SIZE. This is fulfilled if the allocation above succeeded. - // * No pointers are expected to be valid beyond the scope of the function - // call. - call_with_input_values(input_values, |input_values| { - let next_bcc = retry_while_adjusting_output_buffer(|next_bcc, actual_size| { - check_result(unsafe { - BccMainFlow( - self.get_context(), - current_cdi_attest.as_ptr(), - current_cdi_seal.as_ptr(), - bcc.as_ptr(), - bcc.len(), - input_values, - next_bcc.len(), - next_bcc.as_mut_ptr(), - actual_size as *mut _, - next_attest.as_mut_ptr(), - next_seal.as_mut_ptr(), - ) - }) - })?; - Ok((next_attest, next_seal, next_bcc)) - }) - } -} - -/// This submodule provides additional support for the Boot Certificate Chain (BCC) -/// specification. -/// See https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl -pub mod bcc { - use super::{check_result, retry_while_adjusting_output_buffer, Result}; - use open_dice_bcc_bindgen::{ - BccConfigValues, BccFormatConfigDescriptor, BCC_INPUT_COMPONENT_NAME, - BCC_INPUT_COMPONENT_VERSION, BCC_INPUT_RESETTABLE, - }; - use std::ffi::CString; - - /// Safe wrapper around BccFormatConfigDescriptor, see open dice documentation for details. - pub fn format_config_descriptor( - component_name: Option<&str>, - component_version: Option<u64>, - resettable: bool, - ) -> Result<Vec<u8>> { - let component_name = match component_name { - Some(n) => Some(CString::new(n)?), - None => None, - }; - let input = BccConfigValues { - inputs: if component_name.is_some() { BCC_INPUT_COMPONENT_NAME } else { 0 } - | if component_version.is_some() { BCC_INPUT_COMPONENT_VERSION } else { 0 } - | if resettable { BCC_INPUT_RESETTABLE } else { 0 }, - // SAFETY: The as_ref() in the line below is vital to keep the component_name object - // alive. Removing as_ref will move the component_name and the pointer will - // become invalid after this statement. - component_name: component_name.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), - component_version: component_version.unwrap_or(0), - }; - - // SAFETY: - // * The first argument is a pointer to the BccConfigValues input assembled above. - // It and its indirections must be valid for the duration of the function call. - // * The second argument and the third argument are the length of and the pointer to the - // allocated output buffer respectively. The buffer must be at least as long - // as indicated by the size argument. - // * The forth argument is a pointer to the actual size returned by the function. - // * All pointers must be valid for the duration of the function call but not beyond. - retry_while_adjusting_output_buffer(|config_descriptor, actual_size| { - check_result(unsafe { - BccFormatConfigDescriptor( - &input as *const BccConfigValues, - config_descriptor.len(), - config_descriptor.as_mut_ptr(), - actual_size as *mut _, - ) - }) - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use diced_sample_inputs::make_sample_bcc_and_cdis; - use std::convert::TryInto; - - static SEED_TEST_VECTOR: &[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, - ]; - - static CDI_ATTEST_TEST_VECTOR: &[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, - ]; - static CDI_PRIVATE_KEY_SEED_TEST_VECTOR: &[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, - ]; - - static PUB_KEY_TEST_VECTOR: &[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, - ]; - static PRIV_KEY_TEST_VECTOR: &[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, - ]; - - static SIGNATURE_TEST_VECTOR: &[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 mut ctx = OpenDiceCborContext::new(); - let seed = ctx.hash("MySeedString".as_bytes()).unwrap(); - assert_eq!(seed, SEED_TEST_VECTOR); - let cdi_attest = &seed[..CDI_SIZE]; - assert_eq!(cdi_attest, CDI_ATTEST_TEST_VECTOR); - let cdi_private_key_seed = - ctx.derive_cdi_private_key_seed(cdi_attest.try_into().unwrap()).unwrap(); - assert_eq!(&cdi_private_key_seed[..], CDI_PRIVATE_KEY_SEED_TEST_VECTOR); - let (pub_key, priv_key) = - ctx.keypair_from_seed(cdi_private_key_seed[..].try_into().unwrap()).unwrap(); - assert_eq!(&pub_key, PUB_KEY_TEST_VECTOR); - assert_eq!(&priv_key[..], PRIV_KEY_TEST_VECTOR); - let mut signature = - ctx.sign("MyMessage".as_bytes(), priv_key[..].try_into().unwrap()).unwrap(); - assert_eq!(&signature, SIGNATURE_TEST_VECTOR); - assert!(ctx - .verify( - "MyMessage".as_bytes(), - signature[..].try_into().unwrap(), - pub_key[..].try_into().unwrap() - ) - .is_ok()); - assert!(ctx - .verify( - "MyMessage_fail".as_bytes(), - signature[..].try_into().unwrap(), - pub_key[..].try_into().unwrap() - ) - .is_err()); - signature[0] += 1; - assert!(ctx - .verify( - "MyMessage".as_bytes(), - signature[..].try_into().unwrap(), - pub_key[..].try_into().unwrap() - ) - .is_err()); - } - - 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, 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 (cdi_attest, cdi_seal, bcc) = make_sample_bcc_and_cdis().unwrap(); - assert_eq!(&cdi_attest[..], SAMPLE_CDI_ATTEST_TEST_VECTOR); - assert_eq!(&cdi_seal[..], SAMPLE_CDI_SEAL_TEST_VECTOR); - assert_eq!(&bcc[..], SAMPLE_BCC_TEST_VECTOR); - } - - static DERIVED_KEY_TEST_VECTOR: &[u8] = &[ - 0x0e, 0xd6, 0x07, 0x0e, 0x1c, 0x38, 0x2c, 0x76, 0x13, 0xc6, 0x76, 0x25, 0x7e, 0x07, 0x6f, - 0xdb, 0x1d, 0xb1, 0x0f, 0x3f, 0xed, 0xc5, 0x2b, 0x95, 0xd1, 0x32, 0xf1, 0x63, 0x2f, 0x2a, - 0x01, 0x5e, - ]; - - #[test] - fn kdf() { - let mut ctx = OpenDiceCborContext::new(); - let derived_key = ctx - .kdf( - PRIVATE_KEY_SEED_SIZE, - "myKey".as_bytes(), - "mySalt".as_bytes(), - "myInfo".as_bytes(), - ) - .unwrap(); - assert_eq!(&derived_key[..], DERIVED_KEY_TEST_VECTOR); - } - - static CERT_ID_TEST_VECTOR: &[u8] = &[ - 0x7a, 0x36, 0x45, 0x2c, 0x02, 0xf6, 0x2b, 0xec, 0xf9, 0x80, 0x06, 0x75, 0x87, 0xa5, 0xc1, - 0x44, 0x0c, 0xd3, 0xc0, 0x6d, - ]; - - #[test] - fn derive_cdi_certificate_id() { - let mut ctx = OpenDiceCborContext::new(); - let cert_id = ctx.derive_cdi_certificate_id("MyPubKey".as_bytes()).unwrap(); - assert_eq!(&cert_id[..], CERT_ID_TEST_VECTOR); - } -} diff --git a/diced/src/diced_client_test.rs b/diced/src/diced_client_test.rs deleted file mode 100644 index 39155088..00000000 --- a/diced/src/diced_client_test.rs +++ /dev/null @@ -1,188 +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. - -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Config::Config as BinderConfig, InputValues::InputValues as BinderInputValues, - Mode::Mode as BinderMode, -}; -use android_security_dice::aidl::android::security::dice::IDiceMaintenance::IDiceMaintenance; -use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode; -use binder::Strong; -use diced_open_dice_cbor as dice; -use nix::libc::uid_t; -use std::convert::TryInto; - -static DICE_NODE_SERVICE_NAME: &str = "android.security.dice.IDiceNode"; -static DICE_MAINTENANCE_SERVICE_NAME: &str = "android.security.dice.IDiceMaintenance"; - -fn get_dice_node() -> Strong<dyn IDiceNode> { - binder::get_interface(DICE_NODE_SERVICE_NAME).unwrap() -} - -fn get_dice_maintenance() -> Strong<dyn IDiceMaintenance> { - binder::get_interface(DICE_MAINTENANCE_SERVICE_NAME).unwrap() -} - -static TEST_MESSAGE: &[u8] = &[ - // "My test message!" - 0x4d, 0x79, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x21, - 0x0a, -]; - -// This test calls derive with an empty argument vector and with a set of three input values. -// It then performs the same three derivation steps on the result of the former and compares -// the result to the result of the latter. -fn equivalence_test() { - let node = get_dice_node(); - let input_values = diced_sample_inputs::get_input_values_vector(); - let former = node.derive(&[]).expect("Trying to call derive."); - let latter = node.derive(&input_values).expect("Trying to call derive with input values."); - let artifacts = - diced_utils::ResidentArtifacts::new(&former.cdiAttest, &former.cdiSeal, &former.bcc.data) - .unwrap(); - - let input_values: Vec<diced_utils::InputValues> = - input_values.iter().map(|v| v.into()).collect(); - - let artifacts = - artifacts.execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)).unwrap(); - let (cdi_attest, cdi_seal, bcc) = artifacts.into_tuple(); - let from_former = diced_utils::make_bcc_handover( - cdi_attest[..].try_into().unwrap(), - cdi_seal[..].try_into().unwrap(), - &bcc, - ) - .unwrap(); - // TODO when we have a parser/verifier, check equivalence rather - // than bit by bit equality. - assert_eq!(latter, from_former); -} - -fn sign_and_verify() { - let node = get_dice_node(); - let _signature = node.sign(&[], TEST_MESSAGE).expect("Trying to call sign."); - - let _bcc = node.getAttestationChain(&[]).expect("Trying to call getAttestationChain."); - // TODO b/204938506 check the signature with the bcc when the verifier is available. -} - -// This test calls derive with an empty argument vector, then demotes the itself using -// a set of three input values, and then calls derive with empty argument vector again. -// It then performs the same three derivation steps on the result of the former and compares -// the result to the result of the latter. -fn demote_test() { - let node = get_dice_node(); - let input_values = diced_sample_inputs::get_input_values_vector(); - let former = node.derive(&[]).expect("Trying to call derive."); - node.demote(&input_values).expect("Trying to call demote with input values."); - - let latter = node.derive(&[]).expect("Trying to call derive after demote."); - - let artifacts = diced_utils::ResidentArtifacts::new( - former.cdiAttest[..].try_into().unwrap(), - former.cdiSeal[..].try_into().unwrap(), - &former.bcc.data, - ) - .unwrap(); - - let input_values: Vec<diced_utils::InputValues> = - input_values.iter().map(|v| v.into()).collect(); - - let artifacts = - artifacts.execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)).unwrap(); - let (cdi_attest, cdi_seal, bcc) = artifacts.into_tuple(); - let from_former = diced_utils::make_bcc_handover( - cdi_attest[..].try_into().unwrap(), - cdi_seal[..].try_into().unwrap(), - &bcc, - ) - .unwrap(); - // TODO b/204938506 when we have a parser/verifier, check equivalence rather - // than bit by bit equality. - assert_eq!(latter, from_former); -} - -fn client_input_values(uid: uid_t) -> BinderInputValues { - BinderInputValues { - codeHash: [0; dice::HASH_SIZE], - config: BinderConfig { - desc: dice::bcc::format_config_descriptor(Some(&format!("{}", uid)), None, true) - .unwrap(), - }, - authorityHash: [0; dice::HASH_SIZE], - authorityDescriptor: None, - mode: BinderMode::NORMAL, - hidden: [0; dice::HIDDEN_SIZE], - } -} - -// This test calls derive with an empty argument vector `former` which look like this: -// <common root> | <caller> -// It then demotes diced using a set of three input values prefixed with the uid based input -// values that diced would add to any call. It then calls derive with empty argument vector -// again which will add another step using the identity of the caller. If diced was demoted -// correctly the chain of `latter` will -// look as follows: -// <common root> | <caller> | <the three sample inputs> | <caller> -// -// It then performs the same three derivation steps followed by a set of caller input values -// on `former` and compares it to `latter`. -fn demote_self_test() { - let maintenance = get_dice_maintenance(); - let node = get_dice_node(); - let input_values = diced_sample_inputs::get_input_values_vector(); - let former = node.derive(&[]).expect("Trying to call derive."); - - let client = client_input_values(nix::unistd::getuid().into()); - - let mut demote_vector = vec![client.clone()]; - demote_vector.append(&mut input_values.clone()); - maintenance.demoteSelf(&demote_vector).expect("Trying to call demote_self with input values."); - - let latter = node.derive(&[]).expect("Trying to call derive after demote."); - - let artifacts = diced_utils::ResidentArtifacts::new( - former.cdiAttest[..].try_into().unwrap(), - former.cdiSeal[..].try_into().unwrap(), - &former.bcc.data, - ) - .unwrap(); - - let client = [client]; - let input_values: Vec<diced_utils::InputValues> = - input_values.iter().chain(client.iter()).map(|v| v.into()).collect(); - - let artifacts = - artifacts.execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)).unwrap(); - let (cdi_attest, cdi_seal, bcc) = artifacts.into_tuple(); - let from_former = diced_utils::make_bcc_handover( - cdi_attest[..].try_into().unwrap(), - cdi_seal[..].try_into().unwrap(), - &bcc, - ) - .unwrap(); - // TODO b/204938506 when we have a parser/verifier, check equivalence rather - // than bit by bit equality. - assert_eq!(latter, from_former); -} - -#[test] -fn run_serialized_test() { - equivalence_test(); - sign_and_verify(); - // The demote self test must run before the demote test or the test fails. - // And since demotion is not reversible the test can only pass once per boot. - demote_self_test(); - demote_test(); -} diff --git a/diced/src/diced_main.rs b/diced/src/diced_main.rs deleted file mode 100644 index c2cf02c8..00000000 --- a/diced/src/diced_main.rs +++ /dev/null @@ -1,76 +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. - -//! Main entry point for diced, the friendly neighborhood DICE service. - -use binder::get_interface; -use diced::{DiceMaintenance, DiceNode, DiceNodeImpl, ProxyNodeHal, ResidentNode}; -use std::convert::TryInto; -use std::panic; -use std::sync::Arc; - -static DICE_NODE_SERVICE_NAME: &str = "android.security.dice.IDiceNode"; -static DICE_MAINTENANCE_SERVICE_NAME: &str = "android.security.dice.IDiceMaintenance"; -static DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default"; - -fn main() { - android_logger::init_once( - android_logger::Config::default().with_tag("diced").with_min_level(log::Level::Debug), - ); - // Redirect panic messages to logcat. - panic::set_hook(Box::new(|panic_info| { - log::error!("{}", panic_info); - })); - - // Saying hi. - log::info!("Diced, your friendly neighborhood DICE service, is starting."); - - let node_impl: Arc<dyn DiceNodeImpl + Send + Sync> = match get_interface(DICE_HAL_SERVICE_NAME) - { - Ok(dice_device) => { - Arc::new(ProxyNodeHal::new(dice_device).expect("Failed to construct a proxy node.")) - } - Err(e) => { - log::warn!("Failed to connect to DICE HAL: {:?}", e); - log::warn!("Using sample dice artifacts."); - let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis() - .expect("Failed to create sample dice artifacts."); - Arc::new( - ResidentNode::new( - cdi_attest[..] - .try_into() - .expect("Failed to convert cdi_attest into array ref."), - cdi_seal[..].try_into().expect("Failed to convert cdi_seal into array ref."), - bcc, - ) - .expect("Failed to construct a resident node."), - ) - } - }; - - let node = DiceNode::new_as_binder(node_impl.clone()) - .expect("Failed to create IDiceNode service instance."); - - let maintenance = DiceMaintenance::new_as_binder(node_impl) - .expect("Failed to create IDiceMaintenance service instance."); - - binder::add_service(DICE_NODE_SERVICE_NAME, node.as_binder()) - .expect("Failed to register IDiceNode Service"); - - binder::add_service(DICE_MAINTENANCE_SERVICE_NAME, maintenance.as_binder()) - .expect("Failed to register IDiceMaintenance Service"); - - log::info!("Joining thread pool now."); - binder::ProcessState::join_thread_pool(); -} diff --git a/diced/src/error.rs b/diced/src/error.rs deleted file mode 100644 index 3e230e4f..00000000 --- a/diced/src/error.rs +++ /dev/null @@ -1,123 +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. - -use android_security_dice::aidl::android::security::dice::ResponseCode::ResponseCode; -use anyhow::Result; -use binder::{ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode}; -use keystore2_selinux as selinux; -use std::ffi::CString; - -/// This is the main Diced error type. It wraps the Diced `ResponseCode` generated -/// from AIDL in the `Rc` variant and Binder and BinderTransaction errors in the respective -/// variants. -#[allow(dead_code)] // Binder error forwarding will be needed when proxy nodes are implemented. -#[derive(Debug, thiserror::Error, Eq, PartialEq, Clone)] -pub enum Error { - /// Wraps a dice `ResponseCode` as defined by the android.security.dice AIDL interface - /// specification. - #[error("Error::Rc({0:?})")] - Rc(ResponseCode), - /// Wraps a Binder exception code other than a service specific exception. - #[error("Binder exception code {0:?}, {1:?}")] - Binder(ExceptionCode, i32), - /// Wraps a Binder status code. - #[error("Binder transaction error {0:?}")] - BinderTransaction(StatusCode), -} - -/// This function should be used by dice service calls to translate error conditions -/// into service specific exceptions. -/// -/// All error conditions get logged by this function. -/// -/// All `Error::Rc(x)` variants get mapped onto a service specific error code of x. -/// `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). -/// -/// # Examples -/// -/// ``` -/// fn do_something() -> anyhow::Result<Vec<u8>> { -/// Err(anyhow!(Error::Rc(ResponseCode::NOT_IMPLEMENTED))) -/// } -/// -/// map_or_log_err(do_something(), Ok) -/// ``` -pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> -where - F: FnOnce(U) -> BinderResult<T>, -{ - map_err_with( - result, - |e| { - log::error!("{:?}", e); - e - }, - handle_ok, - ) -} - -/// This function behaves similar to map_or_log_error, but it does not log the errors, instead -/// it calls map_err on the error before mapping it to a binder result allowing callers to -/// log or transform the error before mapping it. -fn map_err_with<T, U, F1, F2>(result: Result<U>, map_err: F1, handle_ok: F2) -> BinderResult<T> -where - F1: FnOnce(anyhow::Error) -> anyhow::Error, - F2: FnOnce(U) -> BinderResult<T>, -{ - result.map_or_else( - |e| { - let e = map_err(e); - let msg = match CString::new(format!("{:?}", e)) { - Ok(msg) => Some(msg), - Err(_) => { - log::warn!( - "Cannot convert error message to CStr. It contained a nul byte. - Omitting message from service specific error." - ); - None - } - }; - let rc = get_error_code(&e); - Err(BinderStatus::new_service_specific_error(rc, msg.as_deref())) - }, - handle_ok, - ) -} - -/// Extracts the error code from an `anyhow::Error` mapping any error that does not have a -/// root cause of `Error::Rc` onto `ResponseCode::SYSTEM_ERROR` and to `e` with `Error::Rc(e)` -/// otherwise. -fn get_error_code(e: &anyhow::Error) -> i32 { - let root_cause = e.root_cause(); - match root_cause.downcast_ref::<Error>() { - Some(Error::Rc(rcode)) => rcode.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 - } - None => match root_cause.downcast_ref::<selinux::Error>() { - Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0, - _ => ResponseCode::SYSTEM_ERROR.0, - }, - } -} diff --git a/diced/src/error_vendor.rs b/diced/src/error_vendor.rs deleted file mode 100644 index e8657e0a..00000000 --- a/diced/src/error_vendor.rs +++ /dev/null @@ -1,119 +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. - -use android_hardware_security_dice::aidl::android::hardware::security::dice::ResponseCode::ResponseCode; -use anyhow::Result; -use binder::{ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode}; -use std::ffi::CString; - -/// This is the error type for DICE HAL implementations. It wraps -/// `android::hardware::security::dice::ResponseCode` generated -/// from AIDL in the `Rc` variant and Binder and BinderTransaction errors in the respective -/// variants. -#[allow(dead_code)] // Binder error forwarding will be needed when proxy nodes are implemented. -#[derive(Debug, thiserror::Error, Eq, PartialEq, Clone)] -pub enum Error { - /// Wraps a dice `ResponseCode` as defined by the Keystore AIDL interface specification. - #[error("Error::Rc({0:?})")] - Rc(ResponseCode), - /// Wraps a Binder exception code other than a service specific exception. - #[error("Binder exception code {0:?}, {1:?}")] - Binder(ExceptionCode, i32), - /// Wraps a Binder status code. - #[error("Binder transaction error {0:?}")] - BinderTransaction(StatusCode), -} - -/// This function should be used by dice service calls to translate error conditions -/// into service specific exceptions. -/// -/// All error conditions get logged by this function. -/// -/// All `Error::Rc(x)` variants get mapped onto a service specific error code of x. -/// `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). -/// -/// # Examples -/// -/// ``` -/// fn do_something() -> anyhow::Result<Vec<u8>> { -/// Err(anyhow!(Error::Rc(ResponseCode::NOT_IMPLEMENTED))) -/// } -/// -/// map_or_log_err(do_something(), Ok) -/// ``` -pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> -where - F: FnOnce(U) -> BinderResult<T>, -{ - map_err_with( - result, - |e| { - log::error!("{:?}", e); - e - }, - handle_ok, - ) -} - -/// This function behaves similar to map_or_log_error, but it does not log the errors, instead -/// it calls map_err on the error before mapping it to a binder result allowing callers to -/// log or transform the error before mapping it. -fn map_err_with<T, U, F1, F2>(result: Result<U>, map_err: F1, handle_ok: F2) -> BinderResult<T> -where - F1: FnOnce(anyhow::Error) -> anyhow::Error, - F2: FnOnce(U) -> BinderResult<T>, -{ - result.map_or_else( - |e| { - let e = map_err(e); - let msg = match CString::new(format!("{:?}", e)) { - Ok(msg) => Some(msg), - Err(_) => { - log::warn!( - "Cannot convert error message to CStr. It contained a nul byte. - Omitting message from service specific error." - ); - None - } - }; - let rc = get_error_code(&e); - Err(BinderStatus::new_service_specific_error(rc, msg.as_deref())) - }, - handle_ok, - ) -} - -/// Extracts the error code from an `anyhow::Error` mapping any error that does not have a -/// root cause of `Error::Rc` onto `ResponseCode::SYSTEM_ERROR` and to `e` with `Error::Rc(e)` -/// otherwise. -fn get_error_code(e: &anyhow::Error) -> i32 { - let root_cause = e.root_cause(); - match root_cause.downcast_ref::<Error>() { - Some(Error::Rc(rcode)) => rcode.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 - } - None => ResponseCode::SYSTEM_ERROR.0, - } -} diff --git a/diced/src/hal_node.rs b/diced/src/hal_node.rs deleted file mode 100644 index 01a75777..00000000 --- a/diced/src/hal_node.rs +++ /dev/null @@ -1,725 +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 `ResidentHal`, an implementation of a IDiceDevice HAL Interface. -//! While the name implies that the DICE secrets are memory resident, the residency -//! is augmented by the implementation of the traits `DiceArtifacts` and -//! `UpdatableDiceArtifacts`. The implementation outsources all operations that -//! involve the DICE secrets to a short lived child process. By implementing -//! `UpdatableDiceArtifacts` accordingly, integrators can limit the exposure of -//! the resident DICE secrets to user space memory. E.g., an implementation might only -//! hold a path to a securefs file allowing the child to read and update the kernel state -//! through this path directly. -//! -//! ## Important Safety Note. -//! The module is not safe to use in multi threaded processes. It uses fork and runs -//! code that is not async signal safe in the child. Implementing a HAL service without -//! starting a thread pool is safe, but no secondary thread must be created. - -use crate::error_vendor::map_or_log_err; -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, IDiceDevice::BnDiceDevice, IDiceDevice::IDiceDevice, - InputValues::InputValues as BinderInputValues, Signature::Signature, -}; -use anyhow::{Context, Result}; -use binder::{BinderFeatures, Result as BinderResult, Strong}; -use dice::{ContextImpl, OpenDiceCborContext}; -use diced_open_dice_cbor as dice; -use diced_utils as utils; -use nix::sys::wait::{waitpid, WaitStatus}; -use nix::unistd::{ - close, fork, pipe as nix_pipe, read as nix_read, write as nix_write, ForkResult, -}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::convert::TryInto; -use std::io::{Read, Write}; -use std::os::unix::io::RawFd; -use std::sync::{Arc, RwLock}; -use utils::ResidentArtifacts; -pub use utils::{DiceArtifacts, UpdatableDiceArtifacts}; - -/// 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); - -impl Read for PipeReader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - let bytes = nix_read(self.0, 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 Write for PipeWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { - let written = nix_write(self.0, buf)?; - Ok(written) - } - - fn flush(&mut self) -> std::io::Result<()> { - // Flush is a NO-OP. - Ok(()) - } -} - -impl Drop for PipeWriter { - fn drop(&mut self) { - close(self.0).expect("Failed to close writer pipe fd."); - } -} - -fn pipe() -> Result<(PipeReader, PipeWriter), nix::Error> { - let (read_fd, write_fd) = nix_pipe()?; - Ok((PipeReader(read_fd), PipeWriter(write_fd))) -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, thiserror::Error)] -enum RunForkedError { - #[error("RunForkedError::String({0:?})")] - String(String), -} - -/// Run the given closure in a new process. -/// Safety: The function runs code that is not async-signal-safe in the child after forking. -/// This means, that this function must not be called by a multi threaded process. -fn run_forked<F, R>(f: F) -> Result<R> -where - R: Serialize + DeserializeOwned, - F: FnOnce() -> Result<R>, -{ - let (reader, writer) = pipe().expect("Failed to create pipe."); - - match unsafe { fork() } { - Ok(ForkResult::Parent { child, .. }) => { - drop(writer); - let status = waitpid(child, None).expect("Failed while waiting for child."); - if let WaitStatus::Exited(_, 0) = status { - // Child exited successfully. - // Read the result from the pipe. - // Deserialize the result and return it. - let result: Result<R, RunForkedError> = - serde_cbor::from_reader(reader).expect("Failed to deserialize result."); - - result.context("In run_forked:") - } else { - panic!("Child did not exit as expected {:?}", status); - } - } - Ok(ForkResult::Child) => { - // Run the closure. - let result = f() - .map_err(|err| RunForkedError::String(format! {"Nested anyhow error {:?}", err})); - - // Serialize the result of the closure. - serde_cbor::to_writer(writer, &result).expect("Result serialization failed"); - - // Set exit status to `0`. - std::process::exit(0); - } - Err(errno) => { - panic!("Failed to fork: {:?}", errno); - } - } -} - -/// A DiceHal backend implementation. -/// All functions, except `demote`, derive effective dice artifacts starting from -/// this node and iterating through `input_values` in ascending order. -pub trait DiceHalImpl { - /// Signs the message using the effective dice artifacts and Ed25519Pure. - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> Result<Signature>; - /// Returns the effective attestation chain. - fn get_attestation_chain(&self, input_values: &[BinderInputValues]) -> Result<Bcc>; - /// Returns the effective dice artifacts. - fn derive(&self, input_values: &[BinderInputValues]) -> Result<BccHandover>; - /// This demotes the implementation itself. I.e. a resident node would replace its resident - /// artifacts with the effective artifacts derived using `input_values`. A proxy node would - /// simply call `demote` on its parent node. This is not reversible and changes - /// the effective dice artifacts of all clients. - fn demote(&self, input_values: &[BinderInputValues]) -> Result<()>; -} - -/// The ResidentHal implements a IDiceDevice backend with memory resident DICE secrets. -pub struct ResidentHal<T: UpdatableDiceArtifacts + Serialize + DeserializeOwned + Clone + Send> { - artifacts: RwLock<T>, -} - -impl<T: UpdatableDiceArtifacts + Serialize + DeserializeOwned + Clone + Send> ResidentHal<T> { - /// Creates a new Resident node with the given dice secrets and certificate chain. - /// ## Safety - /// It is not safe to use implementations of ResidentHal in multi threaded environments. - /// If using this library to implement a HAL service make sure not to start a thread pool. - pub unsafe fn new(artifacts: T) -> Result<Self> { - Ok(ResidentHal { artifacts: RwLock::new(artifacts) }) - } - - fn with_effective_artifacts<R, F>(&self, input_values: &[BinderInputValues], f: F) -> Result<R> - where - R: Serialize + DeserializeOwned, - F: FnOnce(ResidentArtifacts) -> Result<R>, - { - let artifacts = self.artifacts.read().unwrap().clone(); - - // Safety: run_forked must not be be called by a multi threaded process. - // This requirement is propagated to the public interface of this module through - // `ResidentHal::new` - run_forked(move || { - let artifacts = artifacts.with_artifacts(|a| ResidentArtifacts::new_from(a))?; - let input_values: Vec<utils::InputValues> = - input_values.iter().map(|v| v.into()).collect(); - let artifacts = artifacts - .execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)) - .context("In ResidentHal::get_effective_artifacts:")?; - f(artifacts) - }) - } -} - -impl<T: UpdatableDiceArtifacts + Serialize + DeserializeOwned + Clone + Send> DiceHalImpl - for ResidentHal<T> -{ - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> Result<Signature> { - let signature: Vec<u8> = self - .with_effective_artifacts(input_values, |artifacts| { - let (cdi_attest, _, _) = artifacts.into_tuple(); - let mut dice = OpenDiceCborContext::new(); - let seed = dice - .derive_cdi_private_key_seed(cdi_attest[..].try_into().with_context(|| { - format!( - "In ResidentHal::sign: Failed to convert cdi_attest (length: {}).", - cdi_attest.len() - ) - })?) - .context("In ResidentHal::sign: Failed to derive seed from cdi_attest.")?; - let (_public_key, private_key) = dice - .keypair_from_seed(seed[..].try_into().with_context(|| { - format!( - "In ResidentHal::sign: Failed to convert seed (length: {}).", - seed.len() - ) - })?) - .context("In ResidentHal::sign: Failed to derive keypair from seed.")?; - dice.sign( - message, - private_key[..].try_into().with_context(|| { - format!( - "In ResidentHal::sign: Failed to convert private_key (length: {}).", - private_key.len() - ) - })?, - ) - .context("In ResidentHal::sign: Failed to sign.") - }) - .context("In ResidentHal::sign:")?; - Ok(Signature { data: signature }) - } - - fn get_attestation_chain(&self, input_values: &[BinderInputValues]) -> Result<Bcc> { - let bcc = self - .with_effective_artifacts(input_values, |artifacts| { - let (_, _, bcc) = artifacts.into_tuple(); - Ok(bcc) - }) - .context("In ResidentHal::get_attestation_chain: Failed to get effective_artifacts.")?; - - Ok(Bcc { data: bcc }) - } - - fn derive(&self, input_values: &[BinderInputValues]) -> Result<BccHandover> { - let (cdi_attest, cdi_seal, bcc): (Vec<u8>, Vec<u8>, Vec<u8>) = self - .with_effective_artifacts(input_values, |artifacts| { - let (cdi_attest, cdi_seal, bcc) = artifacts.into_tuple(); - Ok((cdi_attest[..].to_vec(), cdi_seal[..].to_vec(), bcc)) - })?; - - utils::make_bcc_handover( - &cdi_attest - .as_slice() - .try_into() - .context("In ResidentHal::derive: Trying to convert cdi_attest to sized array.")?, - &cdi_seal - .as_slice() - .try_into() - .context("In ResidentHal::derive: Trying to convert cdi_seal to sized array.")?, - &bcc, - ) - .context("In ResidentHal::derive: Trying to construct BccHandover.") - } - - fn demote(&self, input_values: &[BinderInputValues]) -> Result<()> { - let mut artifacts = self.artifacts.write().unwrap(); - - let artifacts_clone = (*artifacts).clone(); - - // Safety: run_forked may not be called from a multi threaded process. - // This requirement is propagated to the public interface of this module through - // `ResidentHal::new` - *artifacts = run_forked(|| { - let new_artifacts = - artifacts_clone.with_artifacts(|a| ResidentArtifacts::new_from(a))?; - let input_values: Vec<utils::InputValues> = - input_values.iter().map(|v| v.into()).collect(); - - let new_artifacts = new_artifacts - .execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)) - .context("In ResidentHal::get_effective_artifacts:")?; - artifacts_clone.update(&new_artifacts) - })?; - - Ok(()) - } -} - -/// Implements android.hardware.security.dice.IDiceDevice. Forwards public API calls -/// to the given DiceHalImpl backend. -pub struct DiceDevice { - hal_impl: Arc<dyn DiceHalImpl + Sync + Send>, -} - -impl DiceDevice { - /// Constructs an instance of DiceDevice, wraps it with a BnDiceDevice object and - /// returns a strong pointer to the binder. The result can be used to register - /// the service with service manager. - pub fn new_as_binder( - hal_impl: Arc<dyn DiceHalImpl + Sync + Send>, - ) -> Result<Strong<dyn IDiceDevice>> { - let result = BnDiceDevice::new_binder(DiceDevice { hal_impl }, BinderFeatures::default()); - Ok(result) - } -} - -impl binder::Interface for DiceDevice {} - -impl IDiceDevice for DiceDevice { - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> BinderResult<Signature> { - map_or_log_err(self.hal_impl.sign(input_values, message), Ok) - } - fn getAttestationChain(&self, input_values: &[BinderInputValues]) -> BinderResult<Bcc> { - map_or_log_err(self.hal_impl.get_attestation_chain(input_values), Ok) - } - fn derive(&self, input_values: &[BinderInputValues]) -> BinderResult<BccHandover> { - map_or_log_err(self.hal_impl.derive(input_values), Ok) - } - fn demote(&self, input_values: &[BinderInputValues]) -> BinderResult<()> { - map_or_log_err(self.hal_impl.demote(input_values), Ok) - } -} - -#[cfg(test)] -mod test { - use super::*; - use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - BccHandover::BccHandover, Config::Config as BinderConfig, - InputValues::InputValues as BinderInputValues, Mode::Mode as BinderMode, - }; - use anyhow::{Context, Result}; - use diced_open_dice_cbor as dice; - use diced_sample_inputs; - use diced_utils as utils; - - #[derive(Debug, Serialize, Deserialize, Clone)] - struct InsecureSerializableArtifacts { - cdi_attest: [u8; dice::CDI_SIZE], - cdi_seal: [u8; dice::CDI_SIZE], - bcc: Vec<u8>, - } - - impl DiceArtifacts for InsecureSerializableArtifacts { - fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] { - &self.cdi_attest - } - fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] { - &self.cdi_seal - } - fn bcc(&self) -> Vec<u8> { - self.bcc.clone() - } - } - - impl UpdatableDiceArtifacts for InsecureSerializableArtifacts { - fn with_artifacts<F, T>(&self, f: F) -> Result<T> - where - F: FnOnce(&dyn DiceArtifacts) -> Result<T>, - { - f(self) - } - fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> { - Ok(Self { - cdi_attest: *new_artifacts.cdi_attest(), - cdi_seal: *new_artifacts.cdi_seal(), - bcc: new_artifacts.bcc(), - }) - } - } - - fn make_input_values( - code: &str, - config_name: &str, - authority: &str, - ) -> Result<BinderInputValues> { - let mut dice_ctx = dice::OpenDiceCborContext::new(); - Ok(BinderInputValues { - codeHash: dice_ctx - .hash(code.as_bytes()) - .context("In make_input_values: code hash failed.")? - .as_slice() - .try_into()?, - config: BinderConfig { - desc: dice::bcc::format_config_descriptor(Some(config_name), None, true) - .context("In make_input_values: Failed to format config descriptor.")?, - }, - authorityHash: dice_ctx - .hash(authority.as_bytes()) - .context("In make_input_values: authority hash failed.")? - .as_slice() - .try_into()?, - authorityDescriptor: None, - mode: BinderMode::NORMAL, - hidden: [0; dice::HIDDEN_SIZE], - }) - } - - /// Test the resident artifact batched derivation in process. - #[test] - fn derive_with_resident_artifacts() -> Result<()> { - let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()?; - - let artifacts = - ResidentArtifacts::new(cdi_attest[..].try_into()?, cdi_seal[..].try_into()?, &bcc)?; - - let input_values = &[ - make_input_values("component 1 code", "component 1", "component 1 authority")?, - make_input_values("component 2 code", "component 2", "component 2 authority")?, - make_input_values("component 3 code", "component 3", "component 3 authority")?, - ]; - - let input_values: Vec<utils::InputValues> = input_values.iter().map(|v| v.into()).collect(); - - let new_artifacts = - artifacts.execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues))?; - - let result = utils::make_bcc_handover( - new_artifacts.cdi_attest(), - new_artifacts.cdi_seal(), - &new_artifacts.bcc(), - )?; - - assert_eq!(result, make_derive_test_vector()); - Ok(()) - } - - /// Test the ResidentHal hal implementation which performs the derivation in a separate - /// process and returns the result through a pipe. This test compares the result against - /// the same test vector as the in process test above. - #[test] - fn derive_with_insecure_artifacts() -> Result<()> { - let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()?; - - // Safety: ResidentHal can only be used in single threaded environments. - // On-device Rust tests run each test in a separate process. - let hal_impl = unsafe { - ResidentHal::new(InsecureSerializableArtifacts { - cdi_attest: cdi_attest[..].try_into()?, - cdi_seal: cdi_seal[..].try_into()?, - bcc, - }) - } - .expect("Failed to create ResidentHal."); - - let bcc_handover = hal_impl - .derive(&[ - make_input_values("component 1 code", "component 1", "component 1 authority")?, - make_input_values("component 2 code", "component 2", "component 2 authority")?, - make_input_values("component 3 code", "component 3", "component 3 authority")?, - ]) - .expect("Failed to derive artifacts."); - - assert_eq!(bcc_handover, make_derive_test_vector()); - Ok(()) - } - - /// Demoting the implementation two steps and then performing one step of child derivation - /// must yield the same outcome as three derivations with the same input values. - #[test] - fn demote() -> Result<()> { - let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()?; - - // Safety: ResidentHal can only be used in single threaded environments. - // On-device Rust tests run each test in a separate process. - let hal_impl = unsafe { - ResidentHal::new(InsecureSerializableArtifacts { - cdi_attest: cdi_attest[..].try_into()?, - cdi_seal: cdi_seal[..].try_into()?, - bcc, - }) - } - .expect("Failed to create ResidentHal."); - - hal_impl - .demote(&[ - make_input_values("component 1 code", "component 1", "component 1 authority")?, - make_input_values("component 2 code", "component 2", "component 2 authority")?, - ]) - .expect("Failed to demote implementation."); - - let bcc_handover = hal_impl - .derive(&[make_input_values( - "component 3 code", - "component 3", - "component 3 authority", - )?]) - .expect("Failed to derive artifacts."); - - assert_eq!(bcc_handover, make_derive_test_vector()); - Ok(()) - } - - fn make_derive_test_vector() -> BccHandover { - utils::make_bcc_handover( - &[ - // cdi_attest - 0x8f, 0xdf, 0x93, 0x67, 0xd7, 0x0e, 0xf8, 0xb8, 0xd2, 0x9c, 0x30, 0xeb, 0x4e, 0x9b, - 0x71, 0x5f, 0x9a, 0x5b, 0x67, 0xa6, 0x29, 0xe0, 0x00, 0x9b, 0x4d, 0xe6, 0x95, 0xcf, - 0xf9, 0xed, 0x5e, 0x9b, - ], - &[ - // cdi_seal - 0x15, 0x3e, 0xd6, 0x30, 0x5a, 0x8d, 0x4b, 0x6f, 0x07, 0x3f, 0x5d, 0x89, 0xc5, 0x6e, - 0x30, 0xba, 0x05, 0x56, 0xfc, 0x66, 0xf4, 0xae, 0xce, 0x7f, 0x81, 0xb9, 0xc5, 0x21, - 0x9b, 0x49, 0x3d, 0xe1, - ], - &[ - // bcc - 0x87, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 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, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8d, 0xa9, 0x01, 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, 0x02, 0x78, - 0x28, 0x36, 0x39, 0x62, 0x31, 0x37, 0x36, 0x37, 0x35, 0x38, 0x61, 0x36, 0x66, 0x34, - 0x34, 0x62, 0x35, 0x65, 0x38, 0x39, 0x39, 0x63, 0x64, 0x65, 0x33, 0x63, 0x66, 0x34, - 0x35, 0x31, 0x39, 0x61, 0x39, 0x33, 0x35, 0x62, 0x63, 0x39, 0x66, 0x65, 0x34, 0x3a, - 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x31, 0x0d, 0x31, 0xfa, 0x78, 0x58, 0x33, 0xf2, - 0xf8, 0x58, 0x6b, 0xe9, 0x68, 0x32, 0x44, 0xd0, 0xfc, 0x2d, 0xe1, 0xfc, 0xe1, 0xc2, - 0x4e, 0x2b, 0xa8, 0x2c, 0xa1, 0xc1, 0x48, 0xc6, 0xaa, 0x91, 0x89, 0x4f, 0xb7, 0x9c, - 0x40, 0x74, 0x21, 0x36, 0x31, 0x45, 0x09, 0xdf, 0x0c, 0xb4, 0xf9, 0x9a, 0x59, 0xae, - 0x4f, 0x21, 0x10, 0xc1, 0x38, 0xa8, 0xa2, 0xbe, 0xc6, 0x36, 0xf0, 0x56, 0x58, 0xdb, - 0x3a, 0x00, 0x47, 0x44, 0x53, 0x58, 0x18, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x6b, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x20, 0x31, 0x3a, 0x00, 0x01, - 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0xce, 0x8a, 0x30, 0x4e, - 0x31, 0x53, 0xea, 0xdd, 0x2f, 0xbd, 0x15, 0xbc, 0x6b, 0x0f, 0xe7, 0x43, 0x50, 0xef, - 0x65, 0xec, 0x4e, 0x21, 0x64, 0x6e, 0x41, 0x22, 0xac, 0x87, 0xda, 0xf1, 0xf2, 0x80, - 0xc6, 0x8a, 0xd8, 0x7b, 0xe8, 0xe2, 0x9b, 0x87, 0x21, 0x5e, 0x26, 0x23, 0x11, 0x89, - 0x86, 0x57, 0x2d, 0x47, 0x73, 0x3f, 0x47, 0x87, 0xfa, 0x58, 0x5c, 0x78, 0x7b, 0xa3, - 0xfc, 0x2b, 0x6c, 0xed, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xd8, 0x40, 0xa0, - 0x60, 0x45, 0x28, 0x5d, 0xd4, 0xc1, 0x08, 0x3c, 0xbc, 0x91, 0xf4, 0xa6, 0xa4, 0xde, - 0xd3, 0x3d, 0xbb, 0x24, 0x46, 0xa3, 0x58, 0x49, 0x57, 0x4d, 0x2e, 0x6d, 0x7a, 0x78, - 0x4b, 0x9d, 0x28, 0x9a, 0x4e, 0xf1, 0x23, 0x06, 0x35, 0xff, 0x8e, 0x1e, 0xb3, 0x02, - 0x63, 0x62, 0x9a, 0x50, 0x6d, 0x18, 0x70, 0x8e, 0xe3, 0x2e, 0x29, 0xb4, 0x22, 0x71, - 0x31, 0x39, 0x65, 0xd5, 0xb5, 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, 0x51, 0x3c, 0x4b, 0x56, 0x0b, 0x49, 0x0b, 0xee, 0xc5, 0x71, - 0xd4, 0xe7, 0xbc, 0x44, 0x27, 0x4f, 0x4e, 0x67, 0xfc, 0x3a, 0xb9, 0x47, 0x8c, 0x6f, - 0x24, 0x29, 0xf8, 0xb8, 0x2f, 0xa7, 0xb3, 0x4d, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, - 0x20, 0x58, 0x40, 0x4e, 0x6d, 0x0e, 0x2b, 0x1d, 0x44, 0x99, 0xb6, 0x63, 0x07, 0x86, - 0x1a, 0xce, 0x4b, 0xdc, 0xd1, 0x3a, 0xdc, 0xbf, 0xaa, 0xb3, 0x06, 0xd9, 0xb5, 0x5c, - 0x75, 0xf0, 0x14, 0x63, 0xa9, 0x1e, 0x7c, 0x56, 0x62, 0x2c, 0xa5, 0xda, 0xc9, 0x81, - 0xcb, 0x3d, 0x63, 0x32, 0x6b, 0x76, 0x81, 0xd2, 0x93, 0xeb, 0xac, 0xfe, 0x0c, 0x87, - 0x66, 0x9e, 0x87, 0x82, 0xb4, 0x81, 0x6e, 0x33, 0xf1, 0x08, 0x01, 0x84, 0x43, 0xa1, - 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8d, 0xa9, 0x01, 0x78, 0x28, 0x36, 0x39, 0x62, 0x31, - 0x37, 0x36, 0x37, 0x35, 0x38, 0x61, 0x36, 0x66, 0x34, 0x34, 0x62, 0x35, 0x65, 0x38, - 0x39, 0x39, 0x63, 0x64, 0x65, 0x33, 0x63, 0x66, 0x34, 0x35, 0x31, 0x39, 0x61, 0x39, - 0x33, 0x35, 0x62, 0x63, 0x39, 0x66, 0x65, 0x34, 0x02, 0x78, 0x28, 0x32, 0x39, 0x65, - 0x34, 0x62, 0x61, 0x63, 0x33, 0x30, 0x31, 0x65, 0x66, 0x36, 0x35, 0x61, 0x38, 0x31, - 0x31, 0x62, 0x39, 0x39, 0x62, 0x30, 0x33, 0x64, 0x65, 0x39, 0x35, 0x34, 0x65, 0x61, - 0x37, 0x36, 0x61, 0x38, 0x39, 0x31, 0x37, 0x38, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x50, - 0x58, 0x40, 0xa4, 0x03, 0xe3, 0xde, 0x44, 0x96, 0xed, 0x31, 0x41, 0xa0, 0xba, 0x59, - 0xee, 0x2b, 0x03, 0x65, 0xcb, 0x63, 0x14, 0x78, 0xbe, 0xad, 0x24, 0x33, 0xb8, 0x6b, - 0x52, 0xd8, 0xab, 0xd5, 0x79, 0x84, 0x98, 0x6c, 0xc2, 0x66, 0xeb, 0x6c, 0x24, 0xa6, - 0xfa, 0x32, 0xa8, 0x16, 0xb8, 0x64, 0x37, 0x2b, 0xd4, 0xc0, 0xc4, 0xc2, 0x63, 0x25, - 0x10, 0xce, 0x47, 0xe3, 0x49, 0xad, 0x41, 0xf5, 0xc8, 0xf6, 0x3a, 0x00, 0x47, 0x44, - 0x53, 0x58, 0x18, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x6b, 0x63, 0x6f, 0x6d, 0x70, - 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x20, 0x32, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, - 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0xc7, 0x50, 0x09, 0xd0, 0xe0, 0xdd, 0x80, 0x77, - 0xae, 0xa7, 0xc8, 0x88, 0x1e, 0x88, 0xd0, 0xc7, 0x0d, 0x7c, 0x49, 0xc5, 0xb5, 0x64, - 0x32, 0x28, 0x2c, 0x48, 0x94, 0xc0, 0xd6, 0x7d, 0x9c, 0x86, 0xda, 0xf7, 0x98, 0xc7, - 0xae, 0xa4, 0x0e, 0x61, 0xc8, 0xb0, 0x8b, 0x8a, 0xe4, 0xad, 0xcf, 0xcf, 0x6d, 0x60, - 0x60, 0x31, 0xdd, 0xa7, 0x24, 0x9b, 0x27, 0x16, 0x31, 0x90, 0x80, 0x70, 0xc3, 0xba, - 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xf8, 0x86, 0xc6, 0x94, 0xf9, 0x3f, 0x66, - 0x3c, 0x43, 0x01, 0x29, 0x27, 0x8d, 0x3c, 0xb2, 0x11, 0xf2, 0x04, 0xb6, 0x67, 0x4f, - 0x5f, 0x90, 0xcb, 0xc6, 0x73, 0xe6, 0x25, 0x14, 0x63, 0xa7, 0x95, 0x11, 0x0e, 0xa0, - 0x1d, 0x3f, 0x6a, 0x58, 0x0a, 0x53, 0xaa, 0x68, 0x3b, 0x92, 0x64, 0x2b, 0x2e, 0x79, - 0x80, 0x70, 0x0e, 0x41, 0xf5, 0xe9, 0x2a, 0x36, 0x0a, 0xa4, 0xe8, 0xb4, 0xe5, 0xdd, - 0xa6, 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, - 0x9e, 0x04, 0x11, 0x24, 0x34, 0xba, 0x40, 0xed, 0x86, 0xe9, 0x48, 0x70, 0x3b, 0xe7, - 0x76, 0xfa, 0xc5, 0xf6, 0x6d, 0xab, 0x86, 0x12, 0x00, 0xbe, 0xc7, 0x00, 0x69, 0x0e, - 0x97, 0x97, 0xa6, 0x12, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xb7, - 0x31, 0xd5, 0x4c, 0x7d, 0xf5, 0xd7, 0xb8, 0xb4, 0x4f, 0x93, 0x47, 0x2c, 0x3d, 0x50, - 0xcc, 0xad, 0x28, 0x23, 0x68, 0xcf, 0xc2, 0x90, 0xd7, 0x02, 0x00, 0xd8, 0xf1, 0x00, - 0x14, 0x03, 0x90, 0x9e, 0x0b, 0x91, 0xa7, 0x22, 0x28, 0xfe, 0x55, 0x42, 0x30, 0x93, - 0x05, 0x66, 0xcd, 0xce, 0xb8, 0x48, 0x07, 0x56, 0x54, 0x67, 0xa5, 0xd7, 0xe3, 0x16, - 0xd6, 0x75, 0x7c, 0x94, 0x98, 0x1b, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, - 0x01, 0x8d, 0xa9, 0x01, 0x78, 0x28, 0x32, 0x39, 0x65, 0x34, 0x62, 0x61, 0x63, 0x33, - 0x30, 0x31, 0x65, 0x66, 0x36, 0x35, 0x61, 0x38, 0x31, 0x31, 0x62, 0x39, 0x39, 0x62, - 0x30, 0x33, 0x64, 0x65, 0x39, 0x35, 0x34, 0x65, 0x61, 0x37, 0x36, 0x61, 0x38, 0x39, - 0x31, 0x37, 0x38, 0x35, 0x02, 0x78, 0x28, 0x31, 0x38, 0x37, 0x36, 0x63, 0x61, 0x63, - 0x34, 0x32, 0x33, 0x39, 0x35, 0x37, 0x66, 0x33, 0x62, 0x66, 0x62, 0x32, 0x62, 0x32, - 0x63, 0x39, 0x33, 0x37, 0x64, 0x31, 0x34, 0x62, 0x62, 0x38, 0x30, 0x64, 0x30, 0x36, - 0x37, 0x33, 0x65, 0x66, 0x66, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0xf4, 0x7d, - 0x11, 0x21, 0xc1, 0x19, 0x57, 0x23, 0x08, 0x6e, 0x5f, 0xe4, 0x55, 0xc5, 0x08, 0x16, - 0x40, 0x5f, 0x2a, 0x6f, 0x04, 0x1e, 0x6f, 0x22, 0xde, 0x53, 0xbd, 0x37, 0xe2, 0xfb, - 0xb4, 0x0b, 0x65, 0xf4, 0xdc, 0xc9, 0xf4, 0xce, 0x2d, 0x82, 0x2a, 0xbc, 0xaf, 0x37, - 0x80, 0x0b, 0x7f, 0xff, 0x3a, 0x98, 0x9c, 0xa7, 0x70, 0x4f, 0xbc, 0x59, 0x4f, 0x4e, - 0xb1, 0x6d, 0xdf, 0x60, 0x39, 0x11, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x58, 0x18, 0xa2, - 0x3a, 0x00, 0x01, 0x11, 0x71, 0x6b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, - 0x74, 0x20, 0x33, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, - 0x58, 0x40, 0xa4, 0xd5, 0x6f, 0xc8, 0xd6, 0xc7, 0xe4, 0x22, 0xb4, 0x7a, 0x26, 0x49, - 0xd5, 0xb4, 0xc1, 0xc6, 0x1b, 0xfa, 0x14, 0x8c, 0x49, 0x72, 0x2f, 0xfe, 0xbc, 0xc1, - 0xc8, 0xc6, 0x65, 0x62, 0x86, 0xf7, 0xf2, 0x74, 0x45, 0x9b, 0x1a, 0xa0, 0x2b, 0xc4, - 0x27, 0x13, 0xc5, 0xc3, 0xe5, 0x28, 0xc2, 0x16, 0xcd, 0x90, 0x6d, 0xa0, 0xf7, 0x27, - 0x04, 0xa8, 0xa2, 0x62, 0xaa, 0x2c, 0x0c, 0x75, 0xd5, 0x9d, 0x3a, 0x00, 0x47, 0x44, - 0x54, 0x58, 0x40, 0x1d, 0x92, 0x34, 0xfb, 0xfe, 0x74, 0xb7, 0xce, 0x3a, 0x95, 0x45, - 0xe5, 0x3e, 0x1f, 0x5f, 0x18, 0x53, 0x5f, 0xe1, 0x85, 0xb0, 0x1d, 0xe3, 0x8d, 0x53, - 0x77, 0xdc, 0x86, 0x32, 0x3d, 0x9b, 0xf9, 0xa5, 0x51, 0x17, 0x51, 0x9a, 0xd8, 0xa6, - 0x7d, 0x45, 0x98, 0x47, 0xa2, 0x73, 0x54, 0x66, 0x28, 0x66, 0x92, 0x1d, 0x28, 0x8a, - 0xe7, 0x5d, 0xb8, 0x96, 0x4b, 0x6a, 0x9d, 0xee, 0xc2, 0xe9, 0x20, 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, 0x4d, 0xf5, 0x61, 0x1e, - 0xa6, 0x64, 0x74, 0x0b, 0x6c, 0x99, 0x8b, 0x6d, 0x34, 0x42, 0x21, 0xdd, 0x82, 0x26, - 0x13, 0xb4, 0xf0, 0xbc, 0x9a, 0x0b, 0xf6, 0x56, 0xbd, 0x5d, 0xea, 0xd5, 0x07, 0x7a, - 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0x40, 0x4d, 0x09, 0x0d, 0x80, - 0xba, 0x12, 0x94, 0x05, 0xfb, 0x1a, 0x23, 0xa3, 0xcb, 0x28, 0x6f, 0xd7, 0x29, 0x95, - 0xda, 0x83, 0x07, 0x3c, 0xbe, 0x7c, 0x37, 0xeb, 0x9c, 0xb2, 0x77, 0x10, 0x3f, 0x6a, - 0x41, 0x80, 0xce, 0x56, 0xb7, 0x55, 0x22, 0x81, 0x77, 0x2d, 0x3c, 0xf8, 0x16, 0x38, - 0x49, 0xcc, 0x9a, 0xe8, 0x3a, 0x03, 0x33, 0x4c, 0xe6, 0x87, 0x72, 0xf6, 0x5a, 0x4a, - 0x3f, 0x4e, 0x0a, - ], - ) - .unwrap() - } -} diff --git a/diced/src/lib.rs b/diced/src/lib.rs deleted file mode 100644 index 50e0e969..00000000 --- a/diced/src/lib.rs +++ /dev/null @@ -1,203 +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. - -//! Implement the android.security.dice.IDiceNode service. - -mod error; -mod permission; -mod proxy_node_hal; -mod resident_node; - -pub use crate::proxy_node_hal::ProxyNodeHal; -pub use crate::resident_node::ResidentNode; -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, Config::Config as BinderConfig, - InputValues::InputValues as BinderInputValues, Mode::Mode, Signature::Signature, -}; -use android_security_dice::aidl::android::security::dice::{ - IDiceMaintenance::BnDiceMaintenance, IDiceMaintenance::IDiceMaintenance, IDiceNode::BnDiceNode, - IDiceNode::IDiceNode, ResponseCode::ResponseCode, -}; -use anyhow::{Context, Result}; -use binder::{BinderFeatures, Result as BinderResult, Strong, ThreadState}; -pub use diced_open_dice_cbor as dice; -use error::{map_or_log_err, Error}; -use keystore2_selinux as selinux; -use libc::uid_t; -use permission::Permission; -use std::sync::Arc; - -/// A DiceNode backend implementation. -/// All functions except demote_self derive effective dice artifacts staring from -/// this node and iterating through `{ [client | demotion path], input_values }` -/// in ascending order. -pub trait DiceNodeImpl { - /// Signs the message using the effective dice artifacts and Ed25519Pure. - fn sign( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - message: &[u8], - ) -> Result<Signature>; - /// Returns the effective attestation chain. - fn get_attestation_chain( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<Bcc>; - /// Returns the effective dice artifacts. - fn derive( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<BccHandover>; - /// Adds [ `client` | `input_values` ] to the demotion path of the given client. - /// This changes the effective dice artifacts for all subsequent API calls of the - /// given client. - fn demote(&self, client: BinderInputValues, input_values: &[BinderInputValues]) -> Result<()>; - /// This demotes the implementation itself. I.e. a resident node would replace its resident - /// with the effective artifacts derived using `input_values`. A proxy node would - /// simply call `demote` on its parent node. This is not reversible and changes - /// the effective dice artifacts of all clients. - fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()>; -} - -/// Wraps a DiceNodeImpl and implements the actual IDiceNode AIDL API. -pub struct DiceNode { - node_impl: Arc<dyn DiceNodeImpl + Sync + Send>, -} - -/// 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. -pub fn check_caller_permission<T: selinux::ClassPermission>(perm: T) -> Result<()> { - ThreadState::with_calling_sid(|calling_sid| { - let target_context = - selinux::getcon().context("In check_caller_permission: getcon failed.")?; - - selinux::check_permission( - calling_sid.ok_or(Error::Rc(ResponseCode::SYSTEM_ERROR)).context( - "In check_keystore_permission: Cannot check permission without calling_sid.", - )?, - &target_context, - perm, - ) - }) -} - -fn client_input_values(uid: uid_t) -> Result<BinderInputValues> { - Ok(BinderInputValues { - codeHash: [0; dice::HASH_SIZE], - config: BinderConfig { - desc: dice::bcc::format_config_descriptor(Some(&format!("{}", uid)), None, false) - .context("In client_input_values: failed to format config descriptor")?, - }, - authorityHash: [0; dice::HASH_SIZE], - authorityDescriptor: None, - hidden: [0; dice::HIDDEN_SIZE], - mode: Mode::NORMAL, - }) -} - -impl DiceNode { - /// Constructs an instance of DiceNode, wraps it with a BnDiceNode object and - /// returns a strong pointer to the binder. The result can be used to register - /// the service with service manager. - pub fn new_as_binder( - node_impl: Arc<dyn DiceNodeImpl + Sync + Send>, - ) -> Result<Strong<dyn IDiceNode>> { - let result = BnDiceNode::new_binder( - DiceNode { node_impl }, - BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, - ); - Ok(result) - } - - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> Result<Signature> { - check_caller_permission(Permission::UseSign).context("In DiceNode::sign:")?; - let client = - client_input_values(ThreadState::get_calling_uid()).context("In DiceNode::sign:")?; - self.node_impl.sign(client, input_values, message) - } - fn get_attestation_chain(&self, input_values: &[BinderInputValues]) -> Result<Bcc> { - check_caller_permission(Permission::GetAttestationChain) - .context("In DiceNode::get_attestation_chain:")?; - let client = client_input_values(ThreadState::get_calling_uid()) - .context("In DiceNode::get_attestation_chain:")?; - self.node_impl.get_attestation_chain(client, input_values) - } - fn derive(&self, input_values: &[BinderInputValues]) -> Result<BccHandover> { - check_caller_permission(Permission::Derive).context("In DiceNode::derive:")?; - let client = - client_input_values(ThreadState::get_calling_uid()).context("In DiceNode::extend:")?; - self.node_impl.derive(client, input_values) - } - fn demote(&self, input_values: &[BinderInputValues]) -> Result<()> { - check_caller_permission(Permission::Demote).context("In DiceNode::demote:")?; - let client = - client_input_values(ThreadState::get_calling_uid()).context("In DiceNode::demote:")?; - self.node_impl.demote(client, input_values) - } -} - -impl binder::Interface for DiceNode {} - -impl IDiceNode for DiceNode { - fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> BinderResult<Signature> { - map_or_log_err(self.sign(input_values, message), Ok) - } - fn getAttestationChain(&self, input_values: &[BinderInputValues]) -> BinderResult<Bcc> { - map_or_log_err(self.get_attestation_chain(input_values), Ok) - } - fn derive(&self, input_values: &[BinderInputValues]) -> BinderResult<BccHandover> { - map_or_log_err(self.derive(input_values), Ok) - } - fn demote(&self, input_values: &[BinderInputValues]) -> BinderResult<()> { - map_or_log_err(self.demote(input_values), Ok) - } -} - -/// Wraps a DiceNodeImpl and implements the IDiceMaintenance AIDL API. -pub struct DiceMaintenance { - node_impl: Arc<dyn DiceNodeImpl + Sync + Send>, -} - -impl DiceMaintenance { - /// Constructs an instance of DiceMaintenance, wraps it with a BnDiceMaintenance object and - /// returns a strong pointer to the binder. The result can be used to register the service - /// with service manager. - pub fn new_as_binder( - node_impl: Arc<dyn DiceNodeImpl + Sync + Send>, - ) -> Result<Strong<dyn IDiceMaintenance>> { - let result = BnDiceMaintenance::new_binder( - DiceMaintenance { node_impl }, - BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, - ); - Ok(result) - } - - fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()> { - check_caller_permission(Permission::DemoteSelf) - .context("In DiceMaintenance::demote_self:")?; - self.node_impl.demote_self(input_values) - } -} - -impl binder::Interface for DiceMaintenance {} - -impl IDiceMaintenance for DiceMaintenance { - fn demoteSelf(&self, input_values: &[BinderInputValues]) -> BinderResult<()> { - map_or_log_err(self.demote_self(input_values), Ok) - } -} diff --git a/diced/src/lib_vendor.rs b/diced/src/lib_vendor.rs deleted file mode 100644 index 01c804b7..00000000 --- a/diced/src/lib_vendor.rs +++ /dev/null @@ -1,20 +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 crate implements the android.hardware.security.dice.IDiceDevice interface -//! and provides support for implementing a DICE HAL service. - -mod error_vendor; -pub mod hal_node; -pub use diced_open_dice_cbor as dice; diff --git a/diced/src/permission.rs b/diced/src/permission.rs deleted file mode 100644 index 116df1b9..00000000 --- a/diced/src/permission.rs +++ /dev/null @@ -1,46 +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 crate provides convenience wrappers for the SELinux permission -//! defined in the diced SELinux access class. - -use keystore2_selinux as selinux; -use selinux::{implement_class, ClassPermission}; - -implement_class!( - /// Permission provides a convenient abstraction from the SELinux class `diced`. - #[selinux(class_name = diced)] - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum Permission { - /// Checked when a client attempts to call seal or unseal. - #[selinux(name = use_seal)] - UseSeal, - /// Checked when a client attempts to call IDiceNode::sign. - #[selinux(name = use_sign)] - UseSign, - /// Checked when a client attempts to call IDiceNode::getAttestationChain. - #[selinux(name = get_attestation_chain)] - GetAttestationChain, - /// Checked when a client attempts to call IDiceNode::derive. - #[selinux(name = derive)] - Derive, - /// Checked when a client wants to demote itself by calling IDiceNode::demote. - #[selinux(name = demote)] - Demote, - /// Checked when a client calls IDiceMaintenance::demote in an attempt to - /// demote this dice node. - #[selinux(name = demote_self)] - DemoteSelf, - } -); diff --git a/diced/src/proxy_node_hal.rs b/diced/src/proxy_node_hal.rs deleted file mode 100644 index 8d883d27..00000000 --- a/diced/src/proxy_node_hal.rs +++ /dev/null @@ -1,119 +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. - -//! A proxy dice node delegates all accesses to CDI_attest and CDI_seal to a parent -//! node, here an implementation of android.hardware.security.dice.IDiceDevice. - -#![allow(dead_code)] - -use crate::DiceNodeImpl; -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, IDiceDevice::IDiceDevice, - InputValues::InputValues as BinderInputValues, Signature::Signature, -}; -use anyhow::{Context, Result}; -use binder::Strong; -use std::collections::HashMap; -use std::sync::RwLock; - -/// The ProxyNodeHal implements a IDiceNode backend delegating crypto operations -/// to the corresponding HAL. -pub struct ProxyNodeHal { - parent: Strong<dyn IDiceDevice>, - demotion_db: RwLock<HashMap<BinderInputValues, Vec<BinderInputValues>>>, -} - -impl ProxyNodeHal { - /// Creates a new proxy node with a reference to the parent service. - pub fn new(parent: Strong<dyn IDiceDevice>) -> Result<Self> { - Ok(ProxyNodeHal { parent, demotion_db: Default::default() }) - } - - fn get_effective_input_values( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Vec<BinderInputValues> { - let demotion_db = self.demotion_db.read().unwrap(); - - let client_arr = [client]; - - demotion_db - .get(&client_arr[0]) - .map(|v| v.iter()) - .unwrap_or_else(|| client_arr.iter()) - .chain(input_values.iter()) - .cloned() - .collect() - } -} - -impl DiceNodeImpl for ProxyNodeHal { - fn sign( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - message: &[u8], - ) -> Result<Signature> { - self.parent - .sign(&self.get_effective_input_values(client, input_values), message) - .context("In ProxyNodeHal::sign:") - } - - fn get_attestation_chain( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<Bcc> { - self.parent - .getAttestationChain(&self.get_effective_input_values(client, input_values)) - .context("In ProxyNodeHal::get_attestation_chain:") - } - - fn derive( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<BccHandover> { - self.parent - .derive(&self.get_effective_input_values(client, input_values)) - .context("In ProxyNodeHal::derive:") - } - - fn demote(&self, client: BinderInputValues, input_values: &[BinderInputValues]) -> Result<()> { - let mut demotion_db = self.demotion_db.write().unwrap(); - - let client_arr = [client]; - - // The following statement consults demotion database which yields an optional demotion - // path. It then constructs an iterator over the following elements, then clones and - // collects them into a new vector: - // [ demotion path | client ], input_values - let new_path: Vec<BinderInputValues> = demotion_db - .get(&client_arr[0]) - .map(|v| v.iter()) - .unwrap_or_else(|| client_arr.iter()) - .chain(input_values) - .cloned() - .collect(); - - let [client] = client_arr; - demotion_db.insert(client, new_path); - Ok(()) - } - - fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()> { - self.parent.demote(input_values).context("In ProxyNodeHal::demote_self:") - } -} diff --git a/diced/src/resident_node.rs b/diced/src/resident_node.rs deleted file mode 100644 index 99a6dc9d..00000000 --- a/diced/src/resident_node.rs +++ /dev/null @@ -1,191 +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. - -//! A resident dice node keeps CDI_attest and CDI_seal memory resident and can serve -//! its clients directly by performing all crypto operations including derivations and -//! certificate generation itself. - -use crate::DiceNodeImpl; -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, InputValues::InputValues as BinderInputValues, - Signature::Signature, -}; -use anyhow::{Context, Result}; -use dice::{ContextImpl, OpenDiceCborContext}; -use diced_open_dice_cbor as dice; -use diced_utils::{self as utils, InputValues, ResidentArtifacts}; -use std::collections::HashMap; -use std::convert::TryInto; -use std::sync::RwLock; - -/// The ResidentNode implements a IDiceNode backend with memory resident DICE secrets. -pub struct ResidentNode { - artifacts: RwLock<ResidentArtifacts>, - demotion_db: RwLock<HashMap<BinderInputValues, Vec<BinderInputValues>>>, -} - -impl ResidentNode { - /// Creates a new Resident node with the given dice secrets and certificate chain. - pub fn new( - cdi_attest: &[u8; dice::CDI_SIZE], - cdi_seal: &[u8; dice::CDI_SIZE], - bcc: Vec<u8>, - ) -> Result<Self> { - Ok(ResidentNode { - artifacts: RwLock::new( - ResidentArtifacts::new(cdi_attest, cdi_seal, &bcc) - .context("In ResidentNode::new: Trying to initialize ResidentArtifacts")?, - ), - demotion_db: Default::default(), - }) - } - - fn get_effective_artifacts( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<ResidentArtifacts> { - let artifacts = self.artifacts.read().unwrap().try_clone()?; - let demotion_db = self.demotion_db.read().unwrap(); - - let client_arr = [client]; - - let input_values: Vec<utils::InputValues> = demotion_db - .get(&client_arr[0]) - .map(|v| v.iter()) - .unwrap_or_else(|| client_arr.iter()) - .chain(input_values.iter()) - .map(|v| v.into()) - .collect(); - - artifacts - .execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)) - .context("In get_effective_artifacts:") - } -} - -impl DiceNodeImpl for ResidentNode { - fn sign( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - message: &[u8], - ) -> Result<Signature> { - let (cdi_attest, _, _) = self - .get_effective_artifacts(client, input_values) - .context("In ResidentNode::sign: Failed to get effective_artifacts.")? - .into_tuple(); - let mut dice = OpenDiceCborContext::new(); - let seed = dice - .derive_cdi_private_key_seed(cdi_attest[..].try_into().with_context(|| { - format!( - "In ResidentNode::sign: Failed to convert cdi_attest (length: {}).", - cdi_attest.len() - ) - })?) - .context("In ResidentNode::sign: Failed to derive seed from cdi_attest.")?; - let (_public_key, private_key) = dice - .keypair_from_seed(seed[..].try_into().with_context(|| { - format!("In ResidentNode::sign: Failed to convert seed (length: {}).", seed.len()) - })?) - .context("In ResidentNode::sign: Failed to derive keypair from seed.")?; - Ok(Signature { - data: dice - .sign( - message, - private_key[..].try_into().with_context(|| { - format!( - "In ResidentNode::sign: Failed to convert private_key (length: {}).", - private_key.len() - ) - })?, - ) - .context("In ResidentNode::sign: Failed to sign.")?, - }) - } - - fn get_attestation_chain( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<Bcc> { - let (_, _, bcc) = self - .get_effective_artifacts(client, input_values) - .context("In ResidentNode::get_attestation_chain: Failed to get effective_artifacts.")? - .into_tuple(); - - Ok(Bcc { data: bcc }) - } - - fn derive( - &self, - client: BinderInputValues, - input_values: &[BinderInputValues], - ) -> Result<BccHandover> { - let (cdi_attest, cdi_seal, bcc) = - self.get_effective_artifacts(client, input_values)?.into_tuple(); - - utils::make_bcc_handover( - &cdi_attest[..] - .try_into() - .context("In ResidentNode::derive: Trying to convert cdi_attest to sized array.")?, - &cdi_seal[..] - .try_into() - .context("In ResidentNode::derive: Trying to convert cdi_attest to sized array.")?, - &bcc, - ) - .context("In ResidentNode::derive: Trying to format bcc handover.") - } - - fn demote(&self, client: BinderInputValues, input_values: &[BinderInputValues]) -> Result<()> { - let mut demotion_db = self.demotion_db.write().unwrap(); - - let client_arr = [client]; - - // The following statement consults demotion database which yields an optional demotion - // path. It then constructs an iterator over the following elements, then clones and - // collects them into a new vector: - // [ demotion path | client ], input_values - let new_path: Vec<BinderInputValues> = demotion_db - .get(&client_arr[0]) - .map(|v| v.iter()) - .unwrap_or_else(|| client_arr.iter()) - .chain(input_values) - .cloned() - .collect(); - - let [client] = client_arr; - demotion_db.insert(client, new_path); - Ok(()) - } - - fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()> { - let mut artifacts = self.artifacts.write().unwrap(); - - let input_values = input_values - .iter() - .map(|v| { - v.try_into().with_context(|| format!("Failed to convert input values: {:#?}", v)) - }) - .collect::<Result<Vec<InputValues>>>() - .context("In ResidentNode::demote_self:")?; - - *artifacts = artifacts - .try_clone() - .context("In ResidentNode::demote_self: Failed to clone resident artifacts")? - .execute_steps(input_values.iter().map(|v| v as &dyn dice::InputValues)) - .context("In ResidentNode::demote_self:")?; - Ok(()) - } -} diff --git a/diced/src/sample_inputs.rs b/diced/src/sample_inputs.rs deleted file mode 100644 index 93897a6f..00000000 --- a/diced/src/sample_inputs.rs +++ /dev/null @@ -1,255 +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 android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Config::Config as BinderConfig, InputValues::InputValues as BinderInputValues, Mode::Mode, -}; -use anyhow::{Context, Result}; -use dice::ContextImpl; -use diced_open_dice_cbor as dice; -use diced_utils::cbor; -use diced_utils::InputValues; -use keystore2_crypto::ZVec; -use std::convert::{TryFrom, TryInto}; -use std::io::Write; - -/// Sample UDS used to perform the root dice flow by `make_sample_bcc_and_cdis`. -pub static UDS: &[u8; dice::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, -]; - -fn encode_pub_key_ed25519(pub_key: &[u8], stream: &mut dyn Write) -> Result<()> { - cbor::encode_header(5 /* CBOR MAP */, 5, stream) - .context("In encode_pub_key_ed25519: Trying to encode map header.")?; - cbor::encode_number(1, stream) - .context("In encode_pub_key_ed25519: Trying to encode Key type tag.")?; - cbor::encode_number(1, stream) - .context("In encode_pub_key_ed25519: Trying to encode Key type.")?; - cbor::encode_number(3, stream) - .context("In encode_pub_key_ed25519: Trying to encode algorithm tag.")?; - // Encoding a -8 for AlgorithmEdDSA. The encoded number is -1 - <header argument>, - // the an argument of 7 below. - cbor::encode_header(1 /* CBOR NEGATIVE INT */, 7 /* -1 -7 = -8*/, stream) - .context("In encode_pub_key_ed25519: Trying to encode algorithm.")?; - cbor::encode_number(4, stream) - .context("In encode_pub_key_ed25519: Trying to encode ops tag.")?; - // Ops 2 for verify. - cbor::encode_number(2, stream).context("In encode_pub_key_ed25519: Trying to encode ops.")?; - cbor::encode_header(1 /* CBOR NEGATIVE INT */, 0 /* -1 -0 = -1*/, stream) - .context("In encode_pub_key_ed25519: Trying to encode curve tag.")?; - // Curve 6 for Ed25519 - cbor::encode_number(6, stream).context("In encode_pub_key_ed25519: Trying to encode curve.")?; - cbor::encode_header(1 /* CBOR NEGATIVE INT */, 1 /* -1 -1 = -2*/, stream) - .context("In encode_pub_key_ed25519: Trying to encode X coordinate tag.")?; - cbor::encode_bstr(pub_key, stream) - .context("In encode_pub_key_ed25519: Trying to encode X coordinate.")?; - Ok(()) -} - -/// Derives a tuple of (CDI_ATTEST, CDI_SEAL, BCC) derived of the vector of input values returned -/// by `get_input_values_vector`. -pub fn make_sample_bcc_and_cdis() -> Result<(ZVec, ZVec, Vec<u8>)> { - let mut dice_ctx = dice::OpenDiceCborContext::new(); - let private_key_seed = dice_ctx - .derive_cdi_private_key_seed(UDS) - .context("In make_sample_bcc_and_cdis: Trying to derive private key seed.")?; - - let (public_key, _) = - dice_ctx - .keypair_from_seed(&private_key_seed[..].try_into().context( - "In make_sample_bcc_and_cids: Failed to convert seed to array reference.", - )?) - .context("In make_sample_bcc_and_cids: Failed to generate key pair.")?; - - let input_values_vector = get_input_values_vector(); - - let (cdi_attest, cdi_seal, mut cert) = dice_ctx - .main_flow( - UDS, - UDS, - &InputValues::try_from(&input_values_vector[0]) - .context("In make_sample_bcc_and_cdis: Trying to convert input values. (0)")?, - ) - .context("In make_sample_bcc_and_cdis: Trying to run first main flow.")?; - - let mut bcc: Vec<u8> = vec![]; - - cbor::encode_header(4 /* CBOR ARRAY */, 2, &mut bcc) - .context("In make_sample_bcc_and_cdis: Trying to encode array header.")?; - encode_pub_key_ed25519(&public_key, &mut bcc) - .context("In make_sample_bcc_and_cdis: Trying encode pub_key.")?; - - bcc.append(&mut cert); - - let (cdi_attest, cdi_seal, bcc) = dice_ctx - .bcc_main_flow( - &cdi_attest[..].try_into().context( - "In make_sample_bcc_and_cdis: Failed to convert cdi_attest to array reference. (1)", - )?, - &cdi_seal[..].try_into().context( - "In make_sample_bcc_and_cdis: Failed to convert cdi_seal to array reference. (1)", - )?, - &bcc, - &InputValues::try_from(&input_values_vector[1]) - .context("In make_sample_bcc_and_cdis: Trying to convert input values. (1)")?, - ) - .context("In make_sample_bcc_and_cdis: Trying to run first bcc main flow.")?; - dice_ctx - .bcc_main_flow( - &cdi_attest[..].try_into().context( - "In make_sample_bcc_and_cdis: Failed to convert cdi_attest to array reference. (2)", - )?, - &cdi_seal[..].try_into().context( - "In make_sample_bcc_and_cdis: Failed to convert cdi_seal to array reference. (2)", - )?, - &bcc, - &InputValues::try_from(&input_values_vector[2]) - .context("In make_sample_bcc_and_cdis: Trying to convert input values. (2)")?, - ) - .context("In make_sample_bcc_and_cdis: Trying to run second bcc main flow.") -} - -fn make_input_values( - code_hash: &[u8; dice::HASH_SIZE], - authority_hash: &[u8; dice::HASH_SIZE], - config_name: &str, - config_version: u64, - config_resettable: bool, - mode: Mode, - hidden: &[u8; dice::HIDDEN_SIZE], -) -> Result<BinderInputValues> { - Ok(BinderInputValues { - codeHash: *code_hash, - config: BinderConfig { - desc: dice::bcc::format_config_descriptor( - Some(config_name), - Some(config_version), - config_resettable, - ) - .context("In make_input_values: Failed to format config descriptor.")?, - }, - authorityHash: *authority_hash, - authorityDescriptor: None, - hidden: *hidden, - mode, - }) -} - -/// Returns a set of sample input for a dice chain comprising the android boot loader ABL, -/// the verified boot information AVB, and Android S. -pub fn get_input_values_vector() -> Vec<BinderInputValues> { - vec![ - make_input_values( - &[ - // code hash - 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, - ], - &[ - // authority hash - 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, - ], - "ABL", // config name - 1, // config version - true, // resettable - Mode::NORMAL, - &[ - // hidden - 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, - ], - ) - .unwrap(), - make_input_values( - &[ - // code hash - 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, - ], - &[ - // authority hash - 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, - ], - "AVB", // config name - 1, // config version - true, // resettable - Mode::NORMAL, - &[ - // hidden - 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, - ], - ) - .unwrap(), - make_input_values( - &[ - // code hash - 0; dice::HASH_SIZE - ], - &[ - // authority hash - 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, - ], - "Android", // config name - 12, // config version - true, // resettable - Mode::NORMAL, - &[ - // hidden - 0; dice::HIDDEN_SIZE - ], - ) - .unwrap(), - ] -} - -#[cfg(test)] -mod test { - use super::*; - - // This simple test checks if the invocation succeeds, essentially it tests - // if the initial bcc is accepted by `DiceContext::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 03e8969b..00000000 --- a/diced/src/utils.rs +++ /dev/null @@ -1,381 +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. - -use android_hardware_security_dice::aidl::android::hardware::security::dice::{ - Bcc::Bcc, BccHandover::BccHandover, InputValues::InputValues as BinderInputValues, - Mode::Mode as BinderMode, -}; -use anyhow::{Context, Result}; -use dice::ContextImpl; -use diced_open_dice_cbor as dice; -use keystore2_crypto::ZVec; -use std::convert::TryInto; - -/// This new type wraps a reference to BinderInputValues and implements the open dice -/// InputValues trait. -#[derive(Debug)] -pub struct InputValues<'a>(&'a BinderInputValues); - -impl<'a> From<&'a BinderInputValues> for InputValues<'a> { - fn from(input_values: &'a BinderInputValues) -> InputValues<'a> { - Self(input_values) - } -} - -impl From<&InputValues<'_>> for BinderInputValues { - fn from(input_values: &InputValues) -> BinderInputValues { - input_values.0.clone() - } -} -impl From<InputValues<'_>> for BinderInputValues { - fn from(input_values: InputValues) -> BinderInputValues { - input_values.0.clone() - } -} - -impl dice::InputValues for InputValues<'_> { - fn code_hash(&self) -> &[u8; dice::HASH_SIZE] { - &self.0.codeHash - } - - fn config(&self) -> dice::Config { - dice::Config::Descriptor(self.0.config.desc.as_slice()) - } - - fn authority_hash(&self) -> &[u8; dice::HASH_SIZE] { - &self.0.authorityHash - } - - fn authority_descriptor(&self) -> Option<&[u8]> { - self.0.authorityDescriptor.as_deref() - } - - fn mode(&self) -> dice::Mode { - match self.0.mode { - BinderMode::NOT_INITIALIZED => dice::Mode::NotConfigured, - BinderMode::NORMAL => dice::Mode::Normal, - BinderMode::DEBUG => dice::Mode::Debug, - BinderMode::RECOVERY => dice::Mode::Recovery, - _ => dice::Mode::NotConfigured, - } - } - - fn hidden(&self) -> &[u8; dice::HIDDEN_SIZE] { - // If `self` was created using try_from the length was checked and this cannot panic. - &self.0.hidden - } -} - -/// Initializes an aidl defined BccHandover object with the arguments `cdi_attest`, `cdi_seal`, -/// and `bcc`. -pub fn make_bcc_handover( - cdi_attest: &[u8; dice::CDI_SIZE], - cdi_seal: &[u8; dice::CDI_SIZE], - bcc: &[u8], -) -> Result<BccHandover> { - Ok(BccHandover { cdiAttest: *cdi_attest, cdiSeal: *cdi_seal, bcc: Bcc { data: bcc.to_vec() } }) -} - -/// ResidentArtifacts stores a set of dice artifacts comprising CDI_ATTEST, CDI_SEAL, -/// and the BCC formatted attestation certificate chain. The sensitive secrets are -/// stored in zeroing vectors, and it implements functionality to perform DICE -/// derivation steps using libopen-dice-cbor. -pub struct ResidentArtifacts { - cdi_attest: ZVec, - cdi_seal: ZVec, - bcc: Vec<u8>, -} - -impl ResidentArtifacts { - /// Create a ResidentArtifacts object. The parameters ensure that the stored secrets - /// can only have the appropriate size, so that subsequent casts to array references - /// cannot fail. - pub fn new( - cdi_attest: &[u8; dice::CDI_SIZE], - cdi_seal: &[u8; dice::CDI_SIZE], - bcc: &[u8], - ) -> Result<Self> { - Ok(ResidentArtifacts { - cdi_attest: cdi_attest[..] - .try_into() - .context("In ResidentArtifacts::new: Trying to convert cdi_attest to ZVec.")?, - cdi_seal: cdi_seal[..] - .try_into() - .context("In ResidentArtifacts::new: Trying to convert cdi_seal to ZVec.")?, - bcc: bcc.to_vec(), - }) - } - - /// Creates a ResidentArtifacts object from another one implementing the DiceArtifacts - /// trait. Like `new` this function can only create artifacts of appropriate size - /// because DiceArtifacts returns array references of appropriate size. - pub fn new_from<T: DiceArtifacts + ?Sized>(artifacts: &T) -> Result<Self> { - Ok(ResidentArtifacts { - cdi_attest: artifacts.cdi_attest()[..].try_into()?, - cdi_seal: artifacts.cdi_seal()[..].try_into()?, - bcc: artifacts.bcc(), - }) - } - - /// Attempts to clone the artifacts. This operation is fallible due to the fallible - /// nature of ZVec. - pub fn try_clone(&self) -> Result<Self> { - Ok(ResidentArtifacts { - cdi_attest: self - .cdi_attest - .try_clone() - .context("In ResidentArtifacts::new: Trying to clone cdi_attest.")?, - cdi_seal: self - .cdi_seal - .try_clone() - .context("In ResidentArtifacts::new: Trying to clone cdi_seal.")?, - bcc: self.bcc.clone(), - }) - } - - /// Deconstruct the Artifacts into a tuple. - /// (CDI_ATTEST, CDI_SEAL, BCC) - pub fn into_tuple(self) -> (ZVec, ZVec, Vec<u8>) { - let ResidentArtifacts { cdi_attest, cdi_seal, bcc } = self; - (cdi_attest, cdi_seal, bcc) - } - - fn execute_step(self, input_values: &dyn dice::InputValues) -> Result<Self> { - let ResidentArtifacts { cdi_attest, cdi_seal, bcc } = self; - - let (cdi_attest, cdi_seal, bcc) = dice::OpenDiceCborContext::new() - .bcc_main_flow( - cdi_attest[..].try_into().with_context(|| { - format!("Trying to convert cdi_attest. (length: {})", cdi_attest.len()) - })?, - cdi_seal[..].try_into().with_context(|| { - format!("Trying to convert cdi_seal. (length: {})", cdi_seal.len()) - })?, - &bcc, - input_values, - ) - .context("In ResidentArtifacts::execute_step:")?; - Ok(ResidentArtifacts { cdi_attest, cdi_seal, bcc }) - } - - /// Iterate through the iterator of dice input values performing one - /// BCC main flow step on each element. - pub fn execute_steps<'a, Iter>(self, input_values: Iter) -> Result<Self> - where - Iter: IntoIterator<Item = &'a dyn dice::InputValues>, - { - input_values - .into_iter() - .try_fold(self, |acc, input_values| acc.execute_step(input_values)) - .context("In ResidentArtifacts::execute_step:") - } -} - -/// An object that implements this trait provides the typical DICE artifacts. -/// CDI_ATTEST, CDI_SEAL, and a certificate chain up to the public key that -/// can be derived from CDI_ATTEST. Implementations should check the length of -/// the stored CDI_* secrets on creation so that any valid instance returns the -/// correct secrets in an infallible way. -pub trait DiceArtifacts { - /// Returns CDI_ATTEST. - fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE]; - /// Returns CDI_SEAL. - fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE]; - /// Returns the attestation certificate chain in BCC format. - fn bcc(&self) -> Vec<u8>; -} - -/// Implement this trait to provide read and write access to a secure artifact -/// storage that can be used by the ResidentHal implementation. -pub trait UpdatableDiceArtifacts { - /// With artifacts provides access to the stored artifacts for the duration - /// of the function call by means of calling the callback. - fn with_artifacts<F, T>(&self, f: F) -> Result<T> - where - F: FnOnce(&dyn DiceArtifacts) -> Result<T>; - - /// Consumes the object and returns a an updated version of itself. - fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> - where - Self: Sized; -} - -impl DiceArtifacts for ResidentArtifacts { - fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] { - self.cdi_attest[..].try_into().unwrap() - } - fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] { - self.cdi_seal[..].try_into().unwrap() - } - fn bcc(&self) -> Vec<u8> { - self.bcc.clone() - } -} - -/// 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 as u8) << 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 as u8) << 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 as u8) << 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 as u8) << 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 as u8) << 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 as u64)).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/Android.bp b/fsverity/Android.bp deleted file mode 100644 index 2fc3c01c..00000000 --- a/fsverity/Android.bp +++ /dev/null @@ -1,66 +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 { - // 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"], -} - -python_library_host { - name: "fsverity_digests_proto_python", - srcs: [ - "fsverity_digests.proto", - ], - required: [ - "fsverity", - ], - proto: { - canonical_path_from_root: false, - }, - version: { - py2: { - enabled: true, - }, - py3: { - enabled: true, - }, - }, -} - -rust_protobuf { - name: "libfsverity_digests_proto_rust", - crate_name: "fsverity_digests_proto", - source_stem: "fsverity_digests_proto", - protos: [ - "fsverity_digests.proto", - ], - apex_available: [ - "com.android.compos", - ], -} - -cc_library_static { - name: "libfsverity_digests_proto_cc", - proto: { - type: "lite", - static: true, - canonical_path_from_root: false, - export_proto_headers: true, - }, - srcs: ["fsverity_digests.proto"], -} diff --git a/fsverity/AndroidManifest.xml b/fsverity/AndroidManifest.xml deleted file mode 100644 index 434955c4..00000000 --- a/fsverity/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.security.fsverity_metadata" /> diff --git a/fsverity/OWNERS b/fsverity/OWNERS deleted file mode 100644 index f9e7b25e..00000000 --- a/fsverity/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -alanstokes@google.com -ebiggers@google.com -jeffv@google.com -jiyong@google.com -victorhsieh@google.com diff --git a/fsverity_init/Android.bp b/fsverity_init/Android.bp index 83c59457..39d4e6b6 100644 --- a/fsverity_init/Android.bp +++ b/fsverity_init/Android.bp @@ -10,34 +10,17 @@ package { cc_binary { name: "fsverity_init", srcs: [ - "main.cpp", + "fsverity_init.cpp", ], static_libs: [ "libc++fs", - "libfsverity_init", "libmini_keyctl_static", ], shared_libs: [ "libbase", "libkeyutils", "liblog", + "liblogwrap", ], 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, -} diff --git a/fsverity_init/OWNERS b/fsverity_init/OWNERS deleted file mode 100644 index f9e7b25e..00000000 --- a/fsverity_init/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -alanstokes@google.com -ebiggers@google.com -jeffv@google.com -jiyong@google.com -victorhsieh@google.com diff --git a/fsverity_init/fsverity_init.cpp b/fsverity_init/fsverity_init.cpp index 61f84ddf..7ab4097e 100644 --- a/fsverity_init/fsverity_init.cpp +++ b/fsverity_init/fsverity_init.cpp @@ -37,17 +37,15 @@ bool LoadKeyToKeyring(key_serial_t keyring_id, const char* desc, const char* dat return true; } -bool LoadKeyFromStdin(key_serial_t keyring_id, const char* keyname) { +void 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; + return; } 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) { @@ -81,3 +79,45 @@ 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 (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; + } + LoadKeyFromStdin(keyring_id, argv[2]); + } else if (command == "--lock") { + // Requires files backed by fs-verity to be verified with a key in .fs-verity + // keyring. + if (!android::base::WriteStringToFile("1", "/proc/sys/fs/verity/require_signatures")) { + PLOG(ERROR) << "Failed to enforce fs-verity signature"; + } + + 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/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 3f75dca9..00000000 --- a/fsverity_init/main.cpp +++ /dev/null @@ -1,68 +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") { - // Requires files backed by fs-verity to be verified with a key in .fs-verity - // keyring. - if (!android::base::WriteStringToFile("1", "/proc/sys/fs/verity/require_signatures")) { - PLOG(ERROR) << "Failed to enforce fs-verity signature"; - } - - 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 b3b704fb..8267a6b1 100644 --- a/identity/Android.bp +++ b/identity/Android.bp @@ -24,11 +24,7 @@ cc_defaults { cc_binary { name: "credstore", - defaults: [ - "identity_defaults", - "keymint_use_latest_hal_aidl_ndk_shared", - "keymint_use_latest_hal_aidl_cpp_static", - ], + defaults: ["identity_defaults"], srcs: [ "main.cpp", @@ -37,7 +33,6 @@ cc_binary { "WritableCredential.cpp", "Credential.cpp", "CredentialData.cpp", - "Session.cpp", "Util.cpp", ], init_rc: ["credstore.rc"], @@ -53,15 +48,14 @@ cc_binary { "android.hardware.identity-support-lib", "libkeymaster4support", "libkeystore-attestation-application-id", - "android.security.authorization-ndk", - "android.security.remoteprovisioning-cpp", - "libutilscallstack", + "android.hardware.security.keymint-V1-ndk_platform", + "android.security.authorization-ndk_platform", ], static_libs: [ - "android.hardware.identity-V4-cpp", + "android.hardware.identity-V3-cpp", "android.hardware.keymaster-V3-cpp", "libcppbor_external", - ], + ] } filegroup { @@ -81,7 +75,6 @@ filegroup { "binder/android/security/identity/AuthKeyParcel.aidl", "binder/android/security/identity/SecurityHardwareInfoParcel.aidl", "binder/android/security/identity/ICredentialStoreFactory.aidl", - "binder/android/security/identity/ISession.aidl", ], path: "binder", } diff --git a/identity/Credential.cpp b/identity/Credential.cpp index c67fe4a3..7c75d8a2 100644 --- a/identity/Credential.cpp +++ b/identity/Credential.cpp @@ -70,10 +70,10 @@ using ::aidl::android::security::authorization::IKeystoreAuthorization; Credential::Credential(CipherSuite cipherSuite, const std::string& dataPath, const std::string& credentialName, uid_t callingUid, HardwareInformation hwInfo, sp<IIdentityCredentialStore> halStoreBinder, - sp<IPresentationSession> halSessionBinder, int halApiVersion) + int halApiVersion) : cipherSuite_(cipherSuite), dataPath_(dataPath), credentialName_(credentialName), callingUid_(callingUid), hwInfo_(std::move(hwInfo)), halStoreBinder_(halStoreBinder), - halSessionBinder_(halSessionBinder), halApiVersion_(halApiVersion) {} + halApiVersion_(halApiVersion) {} Credential::~Credential() {} @@ -85,40 +85,25 @@ Status Credential::ensureOrReplaceHalBinder() { "Error loading data for credential"); } - // If we're in a session we explicitly don't get the binder to IIdentityCredential until - // it's used in getEntries() which is the only method call allowed for sessions. - // - // Why? This is because we want to throw the IIdentityCredential object away as soon as it's - // used because the HAL only guarantees a single IIdentityCredential object alive at a time - // and in a session there may be multiple credentials in play and we want to do multiple - // getEntries() calls on all of them. - // - - if (!halSessionBinder_) { - sp<IIdentityCredential> halBinder; - Status status = - halStoreBinder_->getCredential(cipherSuite_, data->getCredentialData(), &halBinder); - if (!status.isOk() && status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) { - int code = status.serviceSpecificErrorCode(); - if (code == IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED) { - return halStatusToError(status, ICredentialStore::ERROR_CIPHER_SUITE_NOT_SUPPORTED); - } - } - if (!status.isOk()) { - LOG(ERROR) << "Error getting HAL binder"; - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC); + sp<IIdentityCredential> halBinder; + Status status = + halStoreBinder_->getCredential(cipherSuite_, data->getCredentialData(), &halBinder); + if (!status.isOk() && status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) { + int code = status.serviceSpecificErrorCode(); + if (code == IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED) { + return halStatusToError(status, ICredentialStore::ERROR_CIPHER_SUITE_NOT_SUPPORTED); } - halBinder_ = halBinder; } + if (!status.isOk()) { + LOG(ERROR) << "Error getting HAL binder"; + return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC); + } + halBinder_ = halBinder; return Status::ok(); } Status Credential::getCredentialKeyCertificateChain(std::vector<uint8_t>* _aidl_return) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); if (!data->loadFromDisk()) { LOG(ERROR) << "Error loading data for credential"; @@ -131,11 +116,7 @@ Status Credential::getCredentialKeyCertificateChain(std::vector<uint8_t>* _aidl_ // Returns operation handle Status Credential::selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys, - bool incrementUsageCount, int64_t* _aidl_return) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } + int64_t* _aidl_return) { sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); if (!data->loadFromDisk()) { LOG(ERROR) << "Error loading data for credential"; @@ -146,7 +127,7 @@ Status Credential::selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingEx // We just check if a key is available, we actually don't store it since we // don't keep CredentialData around between binder calls. const AuthKeyData* authKey = - data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys, incrementUsageCount); + data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys); if (authKey == nullptr) { return Status::fromServiceSpecificError( ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE, @@ -167,19 +148,10 @@ bool Credential::ensureChallenge() { } int64_t challenge; - // If we're in a session, the challenge is selected by the session - if (halSessionBinder_) { - Status status = halSessionBinder_->getAuthChallenge(&challenge); - if (!status.isOk()) { - LOG(ERROR) << "Error getting challenge from session: " << status.exceptionMessage(); - return false; - } - } else { - Status status = halBinder_->createAuthChallenge(&challenge); - if (!status.isOk()) { - LOG(ERROR) << "Error getting challenge: " << status.exceptionMessage(); - return false; - } + Status status = halBinder_->createAuthChallenge(&challenge); + if (!status.isOk()) { + LOG(ERROR) << "Error getting challenge: " << status.exceptionMessage(); + return false; } if (challenge == 0) { LOG(ERROR) << "Returned challenge is 0 (bug in HAL or TA)"; @@ -246,8 +218,7 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, const vector<RequestNamespaceParcel>& requestNamespaces, const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerSignature, bool allowUsingExhaustedKeys, - bool allowUsingExpiredKeys, bool incrementUsageCount, - GetEntriesResultParcel* _aidl_return) { + bool allowUsingExpiredKeys, GetEntriesResultParcel* _aidl_return) { GetEntriesResultParcel ret; sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); @@ -257,28 +228,6 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, "Error loading data for credential"); } - // If used in a session, get the binder on demand... - // - sp<IIdentityCredential> halBinder = halBinder_; - if (halSessionBinder_) { - if (halBinder) { - LOG(ERROR) << "Unexpected HAL binder for session"; - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Unexpected HAL binder for session"); - } - Status status = halSessionBinder_->getCredential(data->getCredentialData(), &halBinder); - if (!status.isOk() && status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) { - int code = status.serviceSpecificErrorCode(); - if (code == IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED) { - return halStatusToError(status, ICredentialStore::ERROR_CIPHER_SUITE_NOT_SUPPORTED); - } - } - if (!status.isOk()) { - LOG(ERROR) << "Error getting HAL binder"; - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC); - } - } - // Calculate requestCounts ahead of time and be careful not to include // elements that don't exist. // @@ -405,40 +354,33 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, } } - // Reuse the same AuthKey over multiple getEntries() calls. + // Note that the selectAuthKey() method is only called if a CryptoObject is involved at + // the Java layer. So we could end up with no previously selected auth key and we may + // need one. // - bool updateUseCountOnDisk = false; - if (!selectedAuthKey_) { - // Note that the selectAuthKey() method is only called if a CryptoObject is involved at - // the Java layer. So we could end up with no previously selected auth key and we may - // need one. + const AuthKeyData* authKey = + data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys); + if (authKey == nullptr) { + // If no authKey is available, consider it an error only when a + // SessionTranscript was provided. // - const AuthKeyData* authKey = data->selectAuthKey( - allowUsingExhaustedKeys, allowUsingExpiredKeys, incrementUsageCount); - if (authKey == nullptr) { - // If no authKey is available, consider it an error only when a - // SessionTranscript was provided. - // - // We allow no SessionTranscript to be provided because it makes - // the API simpler to deal with insofar it can be used without having - // to generate any authentication keys. - // - // In this "no SessionTranscript is provided" mode we don't return - // DeviceNameSpaces nor a MAC over DeviceAuthentication so we don't - // need a device key. - // - if (sessionTranscript.size() > 0) { - return Status::fromServiceSpecificError( - ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE, - "No suitable authentication key available and one is needed"); - } - } else { - // We did find an authKey. Store its contents for future getEntries() calls. - updateUseCountOnDisk = true; - selectedAuthKeySigningKeyBlob_ = authKey->keyBlob; - selectedAuthKeyStaticAuthData_ = authKey->staticAuthenticationData; + // We allow no SessionTranscript to be provided because it makes + // the API simpler to deal with insofar it can be used without having + // to generate any authentication keys. + // + // In this "no SessionTranscript is provided" mode we don't return + // DeviceNameSpaces nor a MAC over DeviceAuthentication so we don't + // need a device key. + // + if (sessionTranscript.size() > 0) { + return Status::fromServiceSpecificError( + ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE, + "No suitable authentication key available and one is needed"); } - selectedAuthKey_ = true; + } + vector<uint8_t> signingKeyBlob; + if (authKey != nullptr) { + signingKeyBlob = authKey->keyBlob; } // Pass the HAL enough information to allow calculating the size of @@ -463,22 +405,22 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, } // This is not catastrophic, we might be dealing with a version 1 implementation which // doesn't have this method. - Status status = halBinder->setRequestedNamespaces(halRequestNamespaces); + Status status = halBinder_->setRequestedNamespaces(halRequestNamespaces); if (!status.isOk()) { LOG(INFO) << "Failed setting expected requested namespaces, assuming V1 HAL " << "and continuing"; } // Pass the verification token. Failure is OK, this method isn't in the V1 HAL. - status = halBinder->setVerificationToken(aidlVerificationToken); + status = halBinder_->setVerificationToken(aidlVerificationToken); if (!status.isOk()) { LOG(INFO) << "Failed setting verification token, assuming V1 HAL " << "and continuing"; } - status = halBinder->startRetrieval(selectedProfiles, aidlAuthToken, requestMessage, - selectedAuthKeySigningKeyBlob_, sessionTranscript, - readerSignature, requestCounts); + status = + halBinder_->startRetrieval(selectedProfiles, aidlAuthToken, requestMessage, signingKeyBlob, + sessionTranscript, readerSignature, requestCounts); if (!status.isOk() && status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) { int code = status.serviceSpecificErrorCode(); if (code == IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND) { @@ -511,8 +453,8 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, } status = - halBinder->startRetrieveEntryValue(rns.namespaceName, rep.name, eData.value().size, - eData.value().accessControlProfileIds); + halBinder_->startRetrieveEntryValue(rns.namespaceName, rep.name, eData.value().size, + eData.value().accessControlProfileIds); if (!status.isOk() && status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) { int code = status.serviceSpecificErrorCode(); if (code == IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED) { @@ -540,7 +482,7 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, vector<uint8_t> value; for (const auto& encryptedChunk : eData.value().encryptedChunks) { vector<uint8_t> chunk; - status = halBinder->retrieveEntryValue(encryptedChunk, &chunk); + status = halBinder_->retrieveEntryValue(encryptedChunk, &chunk); if (!status.isOk()) { return halStatusToGenericError(status); } @@ -554,14 +496,16 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, ret.resultNamespaces.push_back(resultNamespaceParcel); } - status = halBinder->finishRetrieval(&ret.mac, &ret.deviceNameSpaces); + status = halBinder_->finishRetrieval(&ret.mac, &ret.deviceNameSpaces); if (!status.isOk()) { return halStatusToGenericError(status); } - ret.staticAuthenticationData = selectedAuthKeyStaticAuthData_; + if (authKey != nullptr) { + ret.staticAuthenticationData = authKey->staticAuthenticationData; + } // Ensure useCount is updated on disk. - if (updateUseCountOnDisk) { + if (authKey != nullptr) { if (!data->saveToDisk()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error saving data"); @@ -573,11 +517,6 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, } Status Credential::deleteCredential(vector<uint8_t>* _aidl_return) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - vector<uint8_t> proofOfDeletionSignature; sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); @@ -605,12 +544,6 @@ Status Credential::deleteWithChallenge(const vector<uint8_t>& challenge, return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED, "Not implemented by HAL"); } - - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - vector<uint8_t> proofOfDeletionSignature; sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); @@ -637,12 +570,6 @@ Status Credential::proveOwnership(const vector<uint8_t>& challenge, vector<uint8 return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED, "Not implemented by HAL"); } - - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - vector<uint8_t> proofOfOwnershipSignature; Status status = halBinder_->proveOwnership(challenge, &proofOfOwnershipSignature); if (!status.isOk()) { @@ -653,26 +580,19 @@ Status Credential::proveOwnership(const vector<uint8_t>& challenge, vector<uint8 } Status Credential::createEphemeralKeyPair(vector<uint8_t>* _aidl_return) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - vector<uint8_t> keyPair; Status status = halBinder_->createEphemeralKeyPair(&keyPair); if (!status.isOk()) { return halStatusToGenericError(status); } - time_t nowSeconds = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - time_t validityNotBefore = nowSeconds; - time_t validityNotAfter = nowSeconds + 24 * 60 * 60; optional<vector<uint8_t>> pkcs12Bytes = ecKeyPairGetPkcs12(keyPair, "ephemeralKey", // Alias for key "0", // Serial, as a decimal number "Credstore", // Issuer "Ephemeral Key", // Subject - validityNotBefore, validityNotAfter); + 0, // Validity Not Before + 24 * 60 * 60); // Validity Not After if (!pkcs12Bytes) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error creating PKCS#12 structure for key pair"); @@ -682,11 +602,6 @@ Status Credential::createEphemeralKeyPair(vector<uint8_t>* _aidl_return) { } Status Credential::setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - Status status = halBinder_->setReaderEphemeralPublicKey(publicKey); if (!status.isOk()) { return halStatusToGenericError(status); @@ -695,11 +610,6 @@ Status Credential::setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) } Status Credential::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); if (!data->loadFromDisk()) { LOG(ERROR) << "Error loading data for credential"; @@ -715,11 +625,6 @@ Status Credential::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxU } Status Credential::getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_return) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); if (!data->loadFromDisk()) { LOG(ERROR) << "Error loading data for credential"; @@ -748,11 +653,6 @@ Status Credential::getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_ Status Credential::storeStaticAuthenticationData(const AuthKeyParcel& authenticationKey, const vector<uint8_t>& staticAuthData) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); if (!data->loadFromDisk()) { LOG(ERROR) << "Error loading data for credential"; @@ -781,12 +681,6 @@ Credential::storeStaticAuthenticationDataWithExpiration(const AuthKeyParcel& aut return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED, "Not implemented by HAL"); } - - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); if (!data->loadFromDisk()) { LOG(ERROR) << "Error loading data for credential"; @@ -808,11 +702,6 @@ Credential::storeStaticAuthenticationDataWithExpiration(const AuthKeyParcel& aut } Status Credential::getAuthenticationDataUsageCount(vector<int32_t>* _aidl_return) { - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); if (!data->loadFromDisk()) { LOG(ERROR) << "Error loading data for credential"; @@ -852,12 +741,6 @@ Status Credential::update(sp<IWritableCredential>* _aidl_return) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED, "Not implemented by HAL"); } - - if (halSessionBinder_) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Cannot be used with session"); - } - sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_); if (!data->loadFromDisk()) { LOG(ERROR) << "Error loading data for credential"; diff --git a/identity/Credential.h b/identity/Credential.h index 0906fea2..a76f3cc0 100644 --- a/identity/Credential.h +++ b/identity/Credential.h @@ -39,7 +39,6 @@ using ::android::hardware::identity::CipherSuite; using ::android::hardware::identity::HardwareInformation; using ::android::hardware::identity::IIdentityCredential; using ::android::hardware::identity::IIdentityCredentialStore; -using ::android::hardware::identity::IPresentationSession; using ::android::hardware::identity::RequestDataItem; using ::android::hardware::identity::RequestNamespace; @@ -47,8 +46,7 @@ class Credential : public BnCredential { public: Credential(CipherSuite cipherSuite, const string& dataPath, const string& credentialName, uid_t callingUid, HardwareInformation hwInfo, - sp<IIdentityCredentialStore> halStoreBinder, - sp<IPresentationSession> halSessionBinder, int halApiVersion); + sp<IIdentityCredentialStore> halStoreBinder, int halApiVersion); ~Credential(); Status ensureOrReplaceHalBinder(); @@ -69,14 +67,13 @@ class Credential : public BnCredential { Status getCredentialKeyCertificateChain(vector<uint8_t>* _aidl_return) override; Status selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys, - bool incrementUsageCount, int64_t* _aidl_return) override; + int64_t* _aidl_return) override; Status getEntries(const vector<uint8_t>& requestMessage, const vector<RequestNamespaceParcel>& requestNamespaces, const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerSignature, bool allowUsingExhaustedKeys, - bool allowUsingExpiredKeys, bool incrementUsageCount, - GetEntriesResultParcel* _aidl_return) override; + bool allowUsingExpiredKeys, GetEntriesResultParcel* _aidl_return) override; Status setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) override; Status getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_return) override; @@ -97,20 +94,12 @@ class Credential : public BnCredential { uid_t callingUid_; HardwareInformation hwInfo_; sp<IIdentityCredentialStore> halStoreBinder_; - sp<IPresentationSession> halSessionBinder_; uint64_t selectedChallenge_ = 0; sp<IIdentityCredential> halBinder_; int halApiVersion_; - // This is used to cache the selected AuthKey to ensure the same AuthKey is used across - // multiple getEntries() calls. - // - bool selectedAuthKey_ = false; - vector<uint8_t> selectedAuthKeySigningKeyBlob_; - vector<uint8_t> selectedAuthKeyStaticAuthData_; - bool ensureChallenge(); ssize_t diff --git a/identity/CredentialData.cpp b/identity/CredentialData.cpp index 2189f909..74b995d8 100644 --- a/identity/CredentialData.cpp +++ b/identity/CredentialData.cpp @@ -538,8 +538,7 @@ AuthKeyData* CredentialData::findAuthKey_(bool allowUsingExhaustedKeys, } const AuthKeyData* CredentialData::selectAuthKey(bool allowUsingExhaustedKeys, - bool allowUsingExpiredKeys, - bool incrementUsageCount) { + bool allowUsingExpiredKeys) { AuthKeyData* candidate; // First try to find a un-expired key.. @@ -557,9 +556,7 @@ const AuthKeyData* CredentialData::selectAuthKey(bool allowUsingExhaustedKeys, } } - if (incrementUsageCount) { - candidate->useCount += 1; - } + candidate->useCount += 1; return candidate; } diff --git a/identity/CredentialData.h b/identity/CredentialData.h index e240e473..24b55d3d 100644 --- a/identity/CredentialData.h +++ b/identity/CredentialData.h @@ -111,8 +111,7 @@ class CredentialData : public RefBase { // Returns |nullptr| if a suitable key cannot be found. Otherwise returns // the authentication and increases its use-count. - const AuthKeyData* selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys, - bool incrementUsageCount); + const AuthKeyData* selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys); optional<vector<vector<uint8_t>>> getAuthKeysNeedingCertification(const sp<IIdentityCredential>& halBinder); diff --git a/identity/CredentialStore.cpp b/identity/CredentialStore.cpp index c5c429b7..071cf247 100644 --- a/identity/CredentialStore.cpp +++ b/identity/CredentialStore.cpp @@ -17,66 +17,20 @@ #define LOG_TAG "credstore" #include <algorithm> -#include <optional> #include <android-base/logging.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 "Credential.h" #include "CredentialData.h" #include "CredentialStore.h" -#include "Session.h" #include "Util.h" #include "WritableCredential.h" namespace android { namespace security { namespace identity { -namespace { - -using ::android::hardware::security::keymint::IRemotelyProvisionedComponent; -using ::android::hardware::security::keymint::RpcHardwareInfo; -using ::android::security::remoteprovisioning::IRemotelyProvisionedKeyPool; -using ::android::security::remoteprovisioning::RemotelyProvisionedKey; - -std::optional<std::string> -getRemotelyProvisionedComponentId(const sp<IIdentityCredentialStore>& hal) { - auto init = [](const sp<IIdentityCredentialStore>& hal) -> std::optional<std::string> { - sp<IRemotelyProvisionedComponent> remotelyProvisionedComponent; - Status status = hal->getRemotelyProvisionedComponent(&remotelyProvisionedComponent); - if (!status.isOk()) { - LOG(ERROR) << "Error getting remotely provisioned component: " << status; - return std::nullopt; - } - - RpcHardwareInfo rpcHwInfo; - status = remotelyProvisionedComponent->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; - } - - // This id is required to later fetch remotely provisioned attestation keys. - return *rpcHwInfo.uniqueId; - }; - - static std::optional<std::string> id = init(hal); - return id; -} - -} // namespace CredentialStore::CredentialStore(const std::string& dataPath, sp<IIdentityCredentialStore> hal) : dataPath_(dataPath), hal_(hal) {} @@ -89,16 +43,6 @@ bool CredentialStore::init() { } halApiVersion_ = hal_->getInterfaceVersion(); - if (hwInfo_.isRemoteKeyProvisioningSupported) { - keyPool_ = android::waitForService<IRemotelyProvisionedKeyPool>( - IRemotelyProvisionedKeyPool::descriptor); - if (keyPool_.get() == nullptr) { - LOG(ERROR) << "Error getting IRemotelyProvisionedKeyPool HAL with service name '" - << IRemotelyProvisionedKeyPool::descriptor << "'"; - return false; - } - } - LOG(INFO) << "Connected to Identity Credential HAL with API version " << halApiVersion_ << " and name '" << hwInfo_.credentialStoreName << "' authored by '" << hwInfo_.credentialStoreAuthorName << "' with chunk size " << hwInfo_.dataChunkSize @@ -145,21 +89,13 @@ Status CredentialStore::createCredential(const std::string& credentialName, return halStatusToGenericError(status); } - if (hwInfo_.isRemoteKeyProvisioningSupported) { - status = setRemotelyProvisionedAttestationKey(halWritableCredential.get()); - if (!status.isOk()) { - return halStatusToGenericError(status); - } - } - sp<IWritableCredential> writableCredential = new WritableCredential( dataPath_, credentialName, docType, false, hwInfo_, halWritableCredential); *_aidl_return = writableCredential; return Status::ok(); } -Status CredentialStore::getCredentialCommon(const std::string& credentialName, int32_t cipherSuite, - sp<IPresentationSession> halSessionBinder, +Status CredentialStore::getCredentialByName(const std::string& credentialName, int32_t cipherSuite, sp<ICredential>* _aidl_return) { *_aidl_return = nullptr; @@ -177,9 +113,8 @@ Status CredentialStore::getCredentialCommon(const std::string& credentialName, i // Note: IdentityCredentialStore.java's CipherSuite enumeration and CipherSuite from the // HAL is manually kept in sync. So this cast is safe. - sp<Credential> credential = - new Credential(CipherSuite(cipherSuite), dataPath_, credentialName, callingUid, hwInfo_, - hal_, halSessionBinder, halApiVersion_); + sp<Credential> credential = new Credential(CipherSuite(cipherSuite), dataPath_, credentialName, + callingUid, hwInfo_, hal_, halApiVersion_); Status loadStatus = credential->ensureOrReplaceHalBinder(); if (!loadStatus.isOk()) { @@ -190,50 +125,6 @@ Status CredentialStore::getCredentialCommon(const std::string& credentialName, i return loadStatus; } -Status CredentialStore::getCredentialByName(const std::string& credentialName, int32_t cipherSuite, - sp<ICredential>* _aidl_return) { - return getCredentialCommon(credentialName, cipherSuite, nullptr, _aidl_return); -} - -Status CredentialStore::createPresentationSession(int32_t cipherSuite, sp<ISession>* _aidl_return) { - sp<IPresentationSession> halPresentationSession; - Status status = - hal_->createPresentationSession(CipherSuite(cipherSuite), &halPresentationSession); - if (!status.isOk()) { - return halStatusToGenericError(status); - } - - *_aidl_return = new Session(cipherSuite, halPresentationSession, this); - return Status::ok(); -} - -Status CredentialStore::setRemotelyProvisionedAttestationKey( - IWritableIdentityCredential* halWritableCredential) { - std::optional<std::string> rpcId = getRemotelyProvisionedComponentId(hal_); - if (!rpcId) { - return Status::fromServiceSpecificError(ERROR_GENERIC, - "Error getting remotely provisioned component id"); - } - - uid_t callingUid = android::IPCThreadState::self()->getCallingUid(); - RemotelyProvisionedKey key; - Status status = keyPool_->getAttestationKey(callingUid, *rpcId, &key); - if (!status.isOk()) { - LOG(WARNING) << "Unable to fetch remotely provisioned attestation key, falling back " - << "to the factory-provisioned attestation key."; - return Status::ok(); - } - - status = halWritableCredential->setRemotelyProvisionedAttestationKey(key.keyBlob, - key.encodedCertChain); - if (!status.isOk()) { - LOG(ERROR) << "Error setting remotely provisioned attestation key on credential"; - return status; - } - - return Status::ok(); -} - } // namespace identity } // namespace security } // namespace android diff --git a/identity/CredentialStore.h b/identity/CredentialStore.h index df7928e5..15da4eba 100644 --- a/identity/CredentialStore.h +++ b/identity/CredentialStore.h @@ -21,8 +21,8 @@ #include <vector> #include <android/hardware/identity/IIdentityCredentialStore.h> + #include <android/security/identity/BnCredentialStore.h> -#include <android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.h> namespace android { namespace security { @@ -30,16 +30,12 @@ namespace identity { using ::android::sp; using ::android::binder::Status; -using ::std::optional; using ::std::string; using ::std::unique_ptr; using ::std::vector; using ::android::hardware::identity::HardwareInformation; using ::android::hardware::identity::IIdentityCredentialStore; -using ::android::hardware::identity::IPresentationSession; -using ::android::hardware::identity::IWritableIdentityCredential; -using ::android::security::remoteprovisioning::IRemotelyProvisionedKeyPool; class CredentialStore : public BnCredentialStore { public: @@ -48,12 +44,6 @@ class CredentialStore : public BnCredentialStore { bool init(); - // Used by both getCredentialByName() and Session::getCredential() - // - Status getCredentialCommon(const string& credentialName, int32_t cipherSuite, - sp<IPresentationSession> halSessionBinder, - sp<ICredential>* _aidl_return); - // ICredentialStore overrides Status getSecurityHardwareInfo(SecurityHardwareInfoParcel* _aidl_return) override; @@ -63,18 +53,12 @@ class CredentialStore : public BnCredentialStore { Status getCredentialByName(const string& credentialName, int32_t cipherSuite, sp<ICredential>* _aidl_return) override; - Status createPresentationSession(int32_t cipherSuite, sp<ISession>* _aidl_return) override; - private: - Status setRemotelyProvisionedAttestationKey(IWritableIdentityCredential* halWritableCredential); - string dataPath_; sp<IIdentityCredentialStore> hal_; int halApiVersion_; - sp<IRemotelyProvisionedKeyPool> keyPool_; - HardwareInformation hwInfo_; }; diff --git a/identity/Session.cpp b/identity/Session.cpp deleted file mode 100644 index 98ba3d3a..00000000 --- a/identity/Session.cpp +++ /dev/null @@ -1,101 +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. - */ - -#define LOG_TAG "credstore" - -#include <android-base/logging.h> -#include <android/binder_manager.h> -#include <android/hardware/identity/support/IdentityCredentialSupport.h> - -#include <android/security/identity/ICredentialStore.h> -#include <android/security/identity/ISession.h> - -#include "Session.h" -#include "Util.h" - -namespace android { -namespace security { -namespace identity { - -using std::optional; - -using ::android::hardware::identity::IPresentationSession; -using ::android::hardware::identity::IWritableIdentityCredential; - -using ::android::hardware::identity::support::ecKeyPairGetPkcs12; -using ::android::hardware::identity::support::ecKeyPairGetPrivateKey; -using ::android::hardware::identity::support::ecKeyPairGetPublicKey; -using ::android::hardware::identity::support::hexdump; -using ::android::hardware::identity::support::sha256; - -Status Session::getEphemeralKeyPair(vector<uint8_t>* _aidl_return) { - vector<uint8_t> keyPair; - Status status = halBinder_->getEphemeralKeyPair(&keyPair); - if (!status.isOk()) { - return halStatusToGenericError(status); - } - time_t nowSeconds = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - time_t validityNotBefore = nowSeconds; - time_t validityNotAfter = nowSeconds + 24 * 60 * 60; - optional<vector<uint8_t>> pkcs12Bytes = ecKeyPairGetPkcs12(keyPair, - "ephemeralKey", // Alias for key - "0", // Serial, as a decimal number - "Credstore", // Issuer - "Ephemeral Key", // Subject - validityNotBefore, validityNotAfter); - if (!pkcs12Bytes) { - return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, - "Error creating PKCS#12 structure for key pair"); - } - *_aidl_return = pkcs12Bytes.value(); - return Status::ok(); -} - -Status Session::setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) { - Status status = halBinder_->setReaderEphemeralPublicKey(publicKey); - if (!status.isOk()) { - return halStatusToGenericError(status); - } - return Status::ok(); -} - -Status Session::setSessionTranscript(const vector<uint8_t>& sessionTranscript) { - Status status = halBinder_->setSessionTranscript(sessionTranscript); - if (!status.isOk()) { - return halStatusToGenericError(status); - } - return Status::ok(); -} - -Status Session::getCredentialForPresentation(const string& credentialName, - sp<ICredential>* _aidl_return) { - return store_->getCredentialCommon(credentialName, cipherSuite_, halBinder_, _aidl_return); -} - -Status Session::getAuthChallenge(int64_t* _aidl_return) { - *_aidl_return = 0; - int64_t authChallenge; - Status status = halBinder_->getAuthChallenge(&authChallenge); - if (!status.isOk()) { - return halStatusToGenericError(status); - } - *_aidl_return = authChallenge; - return Status::ok(); -} - -} // namespace identity -} // namespace security -} // namespace android diff --git a/identity/Session.h b/identity/Session.h deleted file mode 100644 index 116c2fd1..00000000 --- a/identity/Session.h +++ /dev/null @@ -1,77 +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. - */ - -#ifndef SYSTEM_SECURITY_PRESENTATION_H_ -#define SYSTEM_SECURITY_PRESENTATION_H_ - -#include <string> -#include <vector> - -#include <android/security/identity/BnSession.h> - -#include <android/hardware/identity/IPresentationSession.h> - -#include <android/hardware/identity/IIdentityCredentialStore.h> - -#include "CredentialStore.h" - -namespace android { -namespace security { -namespace identity { - -using ::android::sp; -using ::android::binder::Status; -using ::std::string; -using ::std::vector; - -using ::android::hardware::identity::CipherSuite; -using ::android::hardware::identity::HardwareInformation; -using ::android::hardware::identity::IIdentityCredential; -using ::android::hardware::identity::IIdentityCredentialStore; -using ::android::hardware::identity::IPresentationSession; -using ::android::hardware::identity::RequestDataItem; -using ::android::hardware::identity::RequestNamespace; - -class Session : public BnSession { - public: - Session(int32_t cipherSuite, sp<IPresentationSession> halBinder, sp<CredentialStore> store) - : cipherSuite_(cipherSuite), halBinder_(halBinder), store_(store) {} - - bool initialize(); - - // ISession overrides - Status getEphemeralKeyPair(vector<uint8_t>* _aidl_return) override; - - Status setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override; - - Status setSessionTranscript(const vector<uint8_t>& sessionTranscript) override; - - Status getAuthChallenge(int64_t* _aidl_return) override; - - Status getCredentialForPresentation(const string& credentialName, - sp<ICredential>* _aidl_return) override; - - private: - int32_t cipherSuite_; - sp<IPresentationSession> halBinder_; - sp<CredentialStore> store_; -}; - -} // namespace identity -} // namespace security -} // namespace android - -#endif // SYSTEM_SECURITY_SESSION_H_ diff --git a/identity/TEST_MAPPING b/identity/TEST_MAPPING index 6444c56a..87707a84 100644 --- a/identity/TEST_MAPPING +++ b/identity/TEST_MAPPING @@ -2,9 +2,6 @@ "presubmit": [ { "name": "CtsIdentityTestCases" - }, - { - "name": "identity-credential-util-tests" } ] } diff --git a/identity/binder/android/security/identity/ICredential.aidl b/identity/binder/android/security/identity/ICredential.aidl index e6a9fae0..2165810f 100644 --- a/identity/binder/android/security/identity/ICredential.aidl +++ b/identity/binder/android/security/identity/ICredential.aidl @@ -49,16 +49,14 @@ interface ICredential { byte[] getCredentialKeyCertificateChain(); long selectAuthKey(in boolean allowUsingExhaustedKeys, - in boolean allowUsingExpiredKeys, - in boolean incrementUsageCount); + in boolean allowUsingExpiredKeys); GetEntriesResultParcel getEntries(in byte[] requestMessage, in RequestNamespaceParcel[] requestNamespaces, in byte[] sessionTranscript, in byte[] readerSignature, in boolean allowUsingExhaustedKeys, - in boolean allowUsingExpiredKeys, - in boolean incrementUsageCount); + in boolean allowUsingExpiredKeys); void setAvailableAuthenticationKeys(in int keyCount, in int maxUsesPerKey); diff --git a/identity/binder/android/security/identity/ICredentialStore.aidl b/identity/binder/android/security/identity/ICredentialStore.aidl index 39b5e5f6..8357f47b 100644 --- a/identity/binder/android/security/identity/ICredentialStore.aidl +++ b/identity/binder/android/security/identity/ICredentialStore.aidl @@ -19,7 +19,6 @@ package android.security.identity; import android.security.identity.IWritableCredential; import android.security.identity.ICredential; import android.security.identity.SecurityHardwareInfoParcel; -import android.security.identity.ISession; /** * @hide @@ -46,9 +45,6 @@ interface ICredentialStore { IWritableCredential createCredential(in @utf8InCpp String credentialName, in @utf8InCpp String docType); - ICredential getCredentialByName(in @utf8InCpp String credentialName, in int cipherSuite); - - ISession createPresentationSession(in int cipherSuite); } diff --git a/identity/binder/android/security/identity/ISession.aidl b/identity/binder/android/security/identity/ISession.aidl deleted file mode 100644 index 2139ec1c..00000000 --- a/identity/binder/android/security/identity/ISession.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.identity; - -import android.security.identity.ICredential; - -/** - * @hide - */ -interface ISession { - byte[] getEphemeralKeyPair(); - - long getAuthChallenge(); - - void setReaderEphemeralPublicKey(in byte[] publicKey); - - void setSessionTranscript(in byte[] sessionTranscript); - - ICredential getCredentialForPresentation(in @utf8InCpp String credentialName); -} diff --git a/identity/util/Android.bp b/identity/util/Android.bp deleted file mode 100644 index 71d77187..00000000 --- a/identity/util/Android.bp +++ /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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -java_library { - name: "identity-credential-util", - srcs: [ - "src/java/**/*.java", - ], - static_libs: [ - "androidx.annotation_annotation", - "bouncycastle-unbundled", - "cbor-java", - ], -} - -android_test { - name: "identity-credential-util-tests", - test_suites: ["general-tests"], - srcs: [ - "test/java/**/*.java", - ], - static_libs: [ - "androidx.test.rules", - "identity-credential-util", - "junit", - ], -} diff --git a/identity/util/AndroidManifest.xml b/identity/util/AndroidManifest.xml deleted file mode 100644 index eece4dc0..00000000 --- a/identity/util/AndroidManifest.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * 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. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.security.identity.internal"> - - <application> - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.security.identity.internal" - android:label="Unit tests for com.android.security.identity.internal"/> - -</manifest> - diff --git a/identity/util/AndroidTest.xml b/identity/util/AndroidTest.xml deleted file mode 100644 index 345460f0..00000000 --- a/identity/util/AndroidTest.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<configuration description="Config for identity cred support library tests"> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="identity-credential-util-tests.apk" /> - </target_preparer> - <test class="com.android.tradefed.testtype.InstrumentationTest" > - <option name="package" value="com.android.security.identity.internal" /> - </test> -</configuration> diff --git a/identity/util/src/java/com/android/security/identity/internal/Iso18013.java b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java deleted file mode 100644 index 6da90e52..00000000 --- a/identity/util/src/java/com/android/security/identity/internal/Iso18013.java +++ /dev/null @@ -1,296 +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 com.android.security.identity.internal; - -import static com.android.security.identity.internal.Util.CBOR_SEMANTIC_TAG_ENCODED_CBOR; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.InvalidParameterException; -import java.security.KeyPair; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECPoint; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import javax.crypto.KeyAgreement; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.CborDecoder; -import co.nstant.in.cbor.CborEncoder; -import co.nstant.in.cbor.CborException; -import co.nstant.in.cbor.builder.MapBuilder; -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; - -/** - * Various utilities for working with the ISO mobile driving license (mDL) - * application specification (ISO 18013-5). - */ -public class Iso18013 { - /** - * Each version of the spec is namespaced, and all namespace-specific constants - * are thus collected into a namespace-specific nested class. - */ - public static class V1 { - public static final String NAMESPACE = "org.iso.18013.5.1"; - public static final String DOC_TYPE = "org.iso.18013.5.1.mdl"; - - public static final String FAMILY_NAME = "family_name"; - public static final String GIVEN_NAME = "given_name"; - public static final String BIRTH_DATE = "birth_date"; - public static final String ISSUE_DATE = "issue_date"; - public static final String EXPIRY = "expiry_date"; - public static final String ISSUING_COUNTRY = "issuing_country"; - public static final String ISSUING_AUTHORITY = "issuing_authority"; - public static final String DOCUMENT_NUMBER = "document_number"; - public static final String PORTRAIT = "portrait"; - public static final String DRIVING_PRIVILEGES = "driving_privileges"; - public static final String UN_DISTINGUISHING_SIGN = "un_distinguishing_sign"; - public static final String HEIGHT = "height"; - public static final String BIO_FACE = "biometric_template_face"; - - public static String ageOver(int age) { - if (age < 0 || age > 99) { - throw new InvalidParameterException("age must be between 0 and 99, inclusive"); - } - return String.format("age_over_%02d", age); - } - } - - public static byte[] buildDeviceAuthenticationCbor(String docType, - byte[] encodedSessionTranscript, - byte[] deviceNameSpacesBytes) { - ByteArrayOutputStream daBaos = new ByteArrayOutputStream(); - try { - ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript); - List<DataItem> dataItems = null; - dataItems = new CborDecoder(bais).decode(); - DataItem sessionTranscript = dataItems.get(0); - ByteString deviceNameSpacesBytesItem = new ByteString(deviceNameSpacesBytes); - deviceNameSpacesBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - new CborEncoder(daBaos).encode(new CborBuilder() - .addArray() - .add("DeviceAuthentication") - .add(sessionTranscript) - .add(docType) - .add(deviceNameSpacesBytesItem) - .end() - .build()); - } catch (CborException e) { - throw new RuntimeException("Error encoding DeviceAuthentication", e); - } - return daBaos.toByteArray(); - } - - public static byte[] buildReaderAuthenticationBytesCbor( - byte[] encodedSessionTranscript, - byte[] requestMessageBytes) { - - ByteArrayOutputStream daBaos = new ByteArrayOutputStream(); - try { - ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript); - List<DataItem> dataItems = null; - dataItems = new CborDecoder(bais).decode(); - DataItem sessionTranscript = dataItems.get(0); - ByteString requestMessageBytesItem = new ByteString(requestMessageBytes); - requestMessageBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - new CborEncoder(daBaos).encode(new CborBuilder() - .addArray() - .add("ReaderAuthentication") - .add(sessionTranscript) - .add(requestMessageBytesItem) - .end() - .build()); - } catch (CborException e) { - throw new RuntimeException("Error encoding ReaderAuthentication", e); - } - byte[] readerAuthentication = daBaos.toByteArray(); - return Util.prependSemanticTagForEncodedCbor(readerAuthentication); - } - - // This returns a SessionTranscript which satisfy the requirement - // that the uncompressed X and Y coordinates of the public key for the - // mDL's ephemeral key-pair appear somewhere in the encoded - // DeviceEngagement. - public static byte[] buildSessionTranscript(KeyPair ephemeralKeyPair) { - // Make the coordinates appear in an already encoded bstr - this - // mimics how the mDL COSE_Key appear as encoded data inside the - // encoded DeviceEngagement - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW(); - // X and Y are always positive so for interop we remove any leading zeroes - // inserted by the BigInteger encoder. - byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); - byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); - baos.write(new byte[]{41}); - baos.write(x); - baos.write(y); - baos.write(new byte[]{42, 44}); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - byte[] blobWithCoords = baos.toByteArray(); - - baos = new ByteArrayOutputStream(); - try { - new CborEncoder(baos).encode(new CborBuilder() - .addArray() - .add(blobWithCoords) - .end() - .build()); - } catch (CborException e) { - e.printStackTrace(); - return null; - } - ByteString encodedDeviceEngagementItem = new ByteString(baos.toByteArray()); - ByteString encodedEReaderKeyItem = new ByteString(Util.cborEncodeString("doesn't matter")); - encodedDeviceEngagementItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - encodedEReaderKeyItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - - baos = new ByteArrayOutputStream(); - try { - new CborEncoder(baos).encode(new CborBuilder() - .addArray() - .add(encodedDeviceEngagementItem) - .add(encodedEReaderKeyItem) - .end() - .build()); - } catch (CborException e) { - e.printStackTrace(); - return null; - } - return baos.toByteArray(); - } - - /* - * Helper function to create a CBOR data for requesting data items. The IntentToRetain - * value will be set to false for all elements. - * - * <p>The returned CBOR data conforms to the following CDDL schema:</p> - * - * <pre> - * ItemsRequest = { - * ? "docType" : DocType, - * "nameSpaces" : NameSpaces, - * ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide - * } - * - * NameSpaces = { - * + NameSpace => DataElements ; Requested data elements for each NameSpace - * } - * - * DataElements = { - * + DataElement => IntentToRetain - * } - * - * DocType = tstr - * - * DataElement = tstr - * IntentToRetain = bool - * NameSpace = tstr - * </pre> - * - * @param entriesToRequest The entries to request, organized as a map of namespace - * names with each value being a collection of data elements - * in the given namespace. - * @param docType The document type or {@code null} if there is no document - * type. - * @return CBOR data conforming to the CDDL mentioned above. - */ - public static @NonNull - byte[] createItemsRequest( - @NonNull Map<String, Collection<String>> entriesToRequest, - @Nullable String docType) { - CborBuilder builder = new CborBuilder(); - MapBuilder<CborBuilder> mapBuilder = builder.addMap(); - if (docType != null) { - mapBuilder.put("docType", docType); - } - - MapBuilder<MapBuilder<CborBuilder>> nsMapBuilder = mapBuilder.putMap("nameSpaces"); - for (String namespaceName : entriesToRequest.keySet()) { - Collection<String> entryNames = entriesToRequest.get(namespaceName); - MapBuilder<MapBuilder<MapBuilder<CborBuilder>>> entryNameMapBuilder = - nsMapBuilder.putMap(namespaceName); - for (String entryName : entryNames) { - entryNameMapBuilder.put(entryName, false); - } - } - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CborEncoder encoder = new CborEncoder(baos); - try { - encoder.encode(builder.build()); - } catch (CborException e) { - throw new RuntimeException("Error encoding CBOR", e); - } - return baos.toByteArray(); - } - - public static SecretKey calcEMacKeyForReader(PublicKey authenticationPublicKey, - PrivateKey ephemeralReaderPrivateKey, - byte[] encodedSessionTranscript) { - try { - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); - ka.init(ephemeralReaderPrivateKey); - ka.doPhase(authenticationPublicKey, true); - byte[] sharedSecret = ka.generateSecret(); - - byte[] sessionTranscriptBytes = - Util.cborEncode(Util.buildCborTaggedByteString(encodedSessionTranscript)); - - byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes); - byte[] info = new byte[]{'E', 'M', 'a', 'c', 'K', 'e', 'y'}; - byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); - - SecretKey secretKey = new SecretKeySpec(derivedKey, ""); - return secretKey; - } catch (InvalidKeyException - | NoSuchAlgorithmException e) { - throw new IllegalStateException("Error performing key agreement", e); - } - } - - private static byte[] stripLeadingZeroes(byte[] value) { - int n = 0; - while (n < value.length && value[n] == 0) { - n++; - } - int newLen = value.length - n; - byte[] ret = new byte[newLen]; - int m = 0; - while (n < value.length) { - ret[m++] = value[n++]; - } - return ret; - } -} diff --git a/identity/util/src/java/com/android/security/identity/internal/Util.java b/identity/util/src/java/com/android/security/identity/internal/Util.java deleted file mode 100644 index 4ec54a72..00000000 --- a/identity/util/src/java/com/android/security/identity/internal/Util.java +++ /dev/null @@ -1,1316 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.security.identity.internal; - -import android.security.identity.ResultData; -import android.security.identity.IdentityCredentialStore; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.FeatureInfo; -import android.os.SystemProperties; -import android.security.keystore.KeyProperties; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyStore; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.PrivateKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.spec.ECGenParameterSpec; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Formatter; -import java.util.Map; - -import javax.crypto.KeyAgreement; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECPoint; - -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1OctetString; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.CborDecoder; -import co.nstant.in.cbor.CborEncoder; -import co.nstant.in.cbor.CborException; -import co.nstant.in.cbor.builder.ArrayBuilder; -import co.nstant.in.cbor.builder.MapBuilder; -import co.nstant.in.cbor.model.AbstractFloat; -import co.nstant.in.cbor.model.Array; -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.DoublePrecisionFloat; -import co.nstant.in.cbor.model.MajorType; -import co.nstant.in.cbor.model.NegativeInteger; -import co.nstant.in.cbor.model.SimpleValue; -import co.nstant.in.cbor.model.SimpleValueType; -import co.nstant.in.cbor.model.SpecialType; -import co.nstant.in.cbor.model.UnicodeString; -import co.nstant.in.cbor.model.UnsignedInteger; - -public class Util { - private static final String TAG = "Util"; - - public static byte[] canonicalizeCbor(byte[] encodedCbor) throws CborException { - ByteArrayInputStream bais = new ByteArrayInputStream(encodedCbor); - List<DataItem> dataItems = new CborDecoder(bais).decode(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for(DataItem dataItem : dataItems) { - CborEncoder encoder = new CborEncoder(baos); - encoder.encode(dataItem); - } - return baos.toByteArray(); - } - - - public static String cborPrettyPrint(byte[] encodedBytes) throws CborException { - StringBuilder sb = new StringBuilder(); - - ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes); - List<DataItem> dataItems = new CborDecoder(bais).decode(); - int count = 0; - for (DataItem dataItem : dataItems) { - if (count > 0) { - sb.append(",\n"); - } - cborPrettyPrintDataItem(sb, 0, dataItem); - count++; - } - - return sb.toString(); - } - - // Returns true iff all elements in |items| are not compound (e.g. an array or a map). - static boolean cborAreAllDataItemsNonCompound(List<DataItem> items) { - for (DataItem item : items) { - switch (item.getMajorType()) { - case ARRAY: - case MAP: - return false; - default: - // continue inspecting other data items - } - } - return true; - } - - public static void cborPrettyPrintDataItem(StringBuilder sb, int indent, DataItem dataItem) { - StringBuilder indentBuilder = new StringBuilder(); - for (int n = 0; n < indent; n++) { - indentBuilder.append(' '); - } - String indentString = indentBuilder.toString(); - - if (dataItem.hasTag()) { - sb.append(String.format("tag %d ", dataItem.getTag().getValue())); - } - - switch (dataItem.getMajorType()) { - case INVALID: - // TODO: throw - sb.append("<invalid>"); - break; - case UNSIGNED_INTEGER: { - // Major type 0: an unsigned integer. - BigInteger value = ((UnsignedInteger) dataItem).getValue(); - sb.append(value); - } - break; - case NEGATIVE_INTEGER: { - // Major type 1: a negative integer. - BigInteger value = ((NegativeInteger) dataItem).getValue(); - sb.append(value); - } - break; - case BYTE_STRING: { - // Major type 2: a byte string. - byte[] value = ((ByteString) dataItem).getBytes(); - sb.append("["); - int count = 0; - for (byte b : value) { - if (count > 0) { - sb.append(", "); - } - sb.append(String.format("0x%02x", b)); - count++; - } - sb.append("]"); - } - break; - case UNICODE_STRING: { - // Major type 3: string of Unicode characters that is encoded as UTF-8 [RFC3629]. - String value = ((UnicodeString) dataItem).getString(); - // TODO: escape ' in |value| - sb.append("'" + value + "'"); - } - break; - case ARRAY: { - // Major type 4: an array of data items. - List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems(); - if (items.size() == 0) { - sb.append("[]"); - } else if (cborAreAllDataItemsNonCompound(items)) { - // The case where everything fits on one line. - sb.append("["); - int count = 0; - for (DataItem item : items) { - cborPrettyPrintDataItem(sb, indent, item); - if (++count < items.size()) { - sb.append(", "); - } - } - sb.append("]"); - } else { - sb.append("[\n" + indentString); - int count = 0; - for (DataItem item : items) { - sb.append(" "); - cborPrettyPrintDataItem(sb, indent + 2, item); - if (++count < items.size()) { - sb.append(","); - } - sb.append("\n" + indentString); - } - sb.append("]"); - } - } - break; - case MAP: { - // Major type 5: a map of pairs of data items. - Collection<DataItem> keys = ((co.nstant.in.cbor.model.Map) dataItem).getKeys(); - if (keys.size() == 0) { - sb.append("{}"); - } else { - sb.append("{\n" + indentString); - int count = 0; - for (DataItem key : keys) { - sb.append(" "); - DataItem value = ((co.nstant.in.cbor.model.Map) dataItem).get(key); - cborPrettyPrintDataItem(sb, indent + 2, key); - sb.append(" : "); - cborPrettyPrintDataItem(sb, indent + 2, value); - if (++count < keys.size()) { - sb.append(","); - } - sb.append("\n" + indentString); - } - sb.append("}"); - } - } - break; - case TAG: - // Major type 6: optional semantic tagging of other major types - // - // We never encounter this one since it's automatically handled via the - // DataItem that is tagged. - throw new RuntimeException("Semantic tag data item not expected"); - - case SPECIAL: - // Major type 7: floating point numbers and simple data types that need no - // content, as well as the "break" stop code. - if (dataItem instanceof SimpleValue) { - switch (((SimpleValue) dataItem).getSimpleValueType()) { - case FALSE: - sb.append("false"); - break; - case TRUE: - sb.append("true"); - break; - case NULL: - sb.append("null"); - break; - case UNDEFINED: - sb.append("undefined"); - break; - case RESERVED: - sb.append("reserved"); - break; - case UNALLOCATED: - sb.append("unallocated"); - break; - } - } else if (dataItem instanceof DoublePrecisionFloat) { - DecimalFormat df = new DecimalFormat("0", - DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - df.setMaximumFractionDigits(340); - sb.append(df.format(((DoublePrecisionFloat) dataItem).getValue())); - } else if (dataItem instanceof AbstractFloat) { - DecimalFormat df = new DecimalFormat("0", - DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - df.setMaximumFractionDigits(340); - sb.append(df.format(((AbstractFloat) dataItem).getValue())); - } else { - sb.append("break"); - } - break; - } - } - - public static byte[] encodeCbor(List<DataItem> dataItems) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CborEncoder encoder = new CborEncoder(baos); - try { - encoder.encode(dataItems); - } catch (CborException e) { - throw new RuntimeException("Error encoding data", e); - } - return baos.toByteArray(); - } - - public static byte[] coseBuildToBeSigned(byte[] encodedProtectedHeaders, - byte[] payload, - byte[] detachedContent) { - CborBuilder sigStructure = new CborBuilder(); - ArrayBuilder<CborBuilder> array = sigStructure.addArray(); - - array.add("Signature1"); - array.add(encodedProtectedHeaders); - - // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) - // so external_aad is the empty bstr - byte emptyExternalAad[] = new byte[0]; - array.add(emptyExternalAad); - - // Next field is the payload, independently of how it's transported (RFC - // 8152 section 4.4). Since our API specifies only one of |data| and - // |detachedContent| can be non-empty, it's simply just the non-empty one. - if (payload != null && payload.length > 0) { - array.add(payload); - } else { - array.add(detachedContent); - } - array.end(); - return encodeCbor(sigStructure.build()); - } - - private static final int COSE_LABEL_ALG = 1; - private static final int COSE_LABEL_X5CHAIN = 33; // temporary identifier - - // From "COSE Algorithms" registry - private static final int COSE_ALG_ECDSA_256 = -7; - private static final int COSE_ALG_HMAC_256_256 = 5; - - private static byte[] signatureDerToCose(byte[] signature) { - if (signature.length > 128) { - throw new RuntimeException("Unexpected length " + signature.length - + ", expected less than 128"); - } - if (signature[0] != 0x30) { - throw new RuntimeException("Unexpected first byte " + signature[0] - + ", expected 0x30"); - } - if ((signature[1] & 0x80) != 0x00) { - throw new RuntimeException("Unexpected second byte " + signature[1] - + ", bit 7 shouldn't be set"); - } - int rOffset = 2; - int rSize = signature[rOffset + 1]; - byte[] rBytes = stripLeadingZeroes( - Arrays.copyOfRange(signature,rOffset + 2, rOffset + rSize + 2)); - - int sOffset = rOffset + 2 + rSize; - int sSize = signature[sOffset + 1]; - byte[] sBytes = stripLeadingZeroes( - Arrays.copyOfRange(signature, sOffset + 2, sOffset + sSize + 2)); - - if (rBytes.length > 32) { - throw new RuntimeException("rBytes.length is " + rBytes.length + " which is > 32"); - } - if (sBytes.length > 32) { - throw new RuntimeException("sBytes.length is " + sBytes.length + " which is > 32"); - } - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - for (int n = 0; n < 32 - rBytes.length; n++) { - baos.write(0x00); - } - baos.write(rBytes); - for (int n = 0; n < 32 - sBytes.length; n++) { - baos.write(0x00); - } - baos.write(sBytes); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - return baos.toByteArray(); - } - - // Adds leading 0x00 if the first encoded byte MSB is set. - private static byte[] encodePositiveBigInteger(BigInteger i) { - byte[] bytes = i.toByteArray(); - if ((bytes[0] & 0x80) != 0) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - baos.write(0x00); - baos.write(bytes); - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException("Failed writing data", e); - } - bytes = baos.toByteArray(); - } - return bytes; - } - - private static byte[] signatureCoseToDer(byte[] signature) { - if (signature.length != 64) { - throw new RuntimeException("signature.length is " + signature.length + ", expected 64"); - } - // r and s are always positive and may use all 256 bits so use the constructor which - // parses them as unsigned. - BigInteger r = new BigInteger(1, Arrays.copyOfRange(signature, 0, 32)); - BigInteger s = new BigInteger(1, Arrays.copyOfRange(signature, 32, 64)); - byte[] rBytes = encodePositiveBigInteger(r); - byte[] sBytes = encodePositiveBigInteger(s); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - baos.write(0x30); - baos.write(2 + rBytes.length + 2 + sBytes.length); - baos.write(0x02); - baos.write(rBytes.length); - baos.write(rBytes); - baos.write(0x02); - baos.write(sBytes.length); - baos.write(sBytes); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - return baos.toByteArray(); - } - - public static byte[] coseSign1Sign(PrivateKey key, - @Nullable byte[] data, - byte[] detachedContent, - @Nullable Collection<X509Certificate> certificateChain) - throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException { - - int dataLen = (data != null ? data.length : 0); - int detachedContentLen = (detachedContent != null ? detachedContent.length : 0); - if (dataLen > 0 && detachedContentLen > 0) { - throw new RuntimeException("data and detachedContent cannot both be non-empty"); - } - - CborBuilder protectedHeaders = new CborBuilder(); - MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap(); - protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_ECDSA_256); - byte[] protectedHeadersBytes = encodeCbor(protectedHeaders.build()); - - byte[] toBeSigned = coseBuildToBeSigned(protectedHeadersBytes, data, detachedContent); - - byte[] coseSignature = null; - try { - Signature s = Signature.getInstance("SHA256withECDSA"); - s.initSign(key); - s.update(toBeSigned); - byte[] derSignature = s.sign(); - coseSignature = signatureDerToCose(derSignature); - } catch (SignatureException e) { - throw new RuntimeException("Error signing data"); - } - - CborBuilder builder = new CborBuilder(); - ArrayBuilder<CborBuilder> array = builder.addArray(); - array.add(protectedHeadersBytes); - MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap(); - if (certificateChain != null && certificateChain.size() > 0) { - if (certificateChain.size() == 1) { - X509Certificate cert = certificateChain.iterator().next(); - unprotectedHeaders.put(COSE_LABEL_X5CHAIN, cert.getEncoded()); - } else { - ArrayBuilder<MapBuilder<ArrayBuilder<CborBuilder>>> x5chainsArray = - unprotectedHeaders.putArray(COSE_LABEL_X5CHAIN); - for (X509Certificate cert : certificateChain) { - x5chainsArray.add(cert.getEncoded()); - } - } - } - if (data == null || data.length == 0) { - array.add(new SimpleValue(SimpleValueType.NULL)); - } else { - array.add(data); - } - array.add(coseSignature); - - return encodeCbor(builder.build()); - } - - public static boolean coseSign1CheckSignature(byte[] signatureCose1, - byte[] detachedContent, - PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException { - ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1); - List<DataItem> dataItems = null; - try { - dataItems = new CborDecoder(bais).decode(); - } catch (CborException e) { - throw new RuntimeException("Given signature is not valid CBOR", e); - } - if (dataItems.size() != 1) { - throw new RuntimeException("Expected just one data item"); - } - DataItem dataItem = dataItems.get(0); - if (dataItem.getMajorType() != MajorType.ARRAY) { - throw new RuntimeException("Data item is not an array"); - } - List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems(); - if (items.size() < 4) { - throw new RuntimeException("Expected at least four items in COSE_Sign1 array"); - } - if (items.get(0).getMajorType() != MajorType.BYTE_STRING) { - throw new RuntimeException("Item 0 (protected headers) is not a byte-string"); - } - byte[] encodedProtectedHeaders = - ((co.nstant.in.cbor.model.ByteString) items.get(0)).getBytes(); - byte[] payload = new byte[0]; - if (items.get(2).getMajorType() == MajorType.SPECIAL) { - if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType() - != SpecialType.SIMPLE_VALUE) { - throw new RuntimeException("Item 2 (payload) is a special but not a simple value"); - } - SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2); - if (simple.getSimpleValueType() != SimpleValueType.NULL) { - throw new RuntimeException("Item 2 (payload) is a simple but not the value null"); - } - } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) { - payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes(); - } else { - throw new RuntimeException("Item 2 (payload) is not nil or byte-string"); - } - if (items.get(3).getMajorType() != MajorType.BYTE_STRING) { - throw new RuntimeException("Item 3 (signature) is not a byte-string"); - } - byte[] coseSignature = ((co.nstant.in.cbor.model.ByteString) items.get(3)).getBytes(); - - byte[] derSignature = signatureCoseToDer(coseSignature); - - int dataLen = payload.length; - int detachedContentLen = (detachedContent != null ? detachedContent.length : 0); - if (dataLen > 0 && detachedContentLen > 0) { - throw new RuntimeException("data and detachedContent cannot both be non-empty"); - } - - byte[] toBeSigned = Util.coseBuildToBeSigned(encodedProtectedHeaders, - payload, detachedContent); - - try { - Signature verifier = Signature.getInstance("SHA256withECDSA"); - verifier.initVerify(publicKey); - verifier.update(toBeSigned); - return verifier.verify(derSignature); - } catch (SignatureException e) { - throw new RuntimeException("Error verifying signature"); - } - } - - // Returns the empty byte-array if no data is included in the structure. - // - // Throws RuntimeException if the given bytes aren't valid COSE_Sign1. - // - public static byte[] coseSign1GetData(byte[] signatureCose1) { - ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1); - List<DataItem> dataItems = null; - try { - dataItems = new CborDecoder(bais).decode(); - } catch (CborException e) { - throw new RuntimeException("Given signature is not valid CBOR", e); - } - if (dataItems.size() != 1) { - throw new RuntimeException("Expected just one data item"); - } - DataItem dataItem = dataItems.get(0); - if (dataItem.getMajorType() != MajorType.ARRAY) { - throw new RuntimeException("Data item is not an array"); - } - List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems(); - if (items.size() < 4) { - throw new RuntimeException("Expected at least four items in COSE_Sign1 array"); - } - byte[] payload = new byte[0]; - if (items.get(2).getMajorType() == MajorType.SPECIAL) { - if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType() - != SpecialType.SIMPLE_VALUE) { - throw new RuntimeException("Item 2 (payload) is a special but not a simple value"); - } - SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2); - if (simple.getSimpleValueType() != SimpleValueType.NULL) { - throw new RuntimeException("Item 2 (payload) is a simple but not the value null"); - } - } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) { - payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes(); - } else { - throw new RuntimeException("Item 2 (payload) is not nil or byte-string"); - } - return payload; - } - - // Returns the empty collection if no x5chain is included in the structure. - // - // Throws RuntimeException if the given bytes aren't valid COSE_Sign1. - // - public static Collection<X509Certificate> coseSign1GetX5Chain(byte[] signatureCose1) - throws CertificateException { - ArrayList<X509Certificate> ret = new ArrayList<>(); - ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1); - List<DataItem> dataItems = null; - try { - dataItems = new CborDecoder(bais).decode(); - } catch (CborException e) { - throw new RuntimeException("Given signature is not valid CBOR", e); - } - if (dataItems.size() != 1) { - throw new RuntimeException("Expected just one data item"); - } - DataItem dataItem = dataItems.get(0); - if (dataItem.getMajorType() != MajorType.ARRAY) { - throw new RuntimeException("Data item is not an array"); - } - List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems(); - if (items.size() < 4) { - throw new RuntimeException("Expected at least four items in COSE_Sign1 array"); - } - if (items.get(1).getMajorType() != MajorType.MAP) { - throw new RuntimeException("Item 1 (unprocted headers) is not a map"); - } - co.nstant.in.cbor.model.Map map = (co.nstant.in.cbor.model.Map) items.get(1); - DataItem x5chainItem = map.get(new UnsignedInteger(COSE_LABEL_X5CHAIN)); - if (x5chainItem != null) { - CertificateFactory factory = CertificateFactory.getInstance("X.509"); - if (x5chainItem instanceof ByteString) { - ByteArrayInputStream certBais = - new ByteArrayInputStream(((ByteString) x5chainItem).getBytes()); - ret.add((X509Certificate) factory.generateCertificate(certBais)); - } else if (x5chainItem instanceof Array) { - for (DataItem certItem : ((Array) x5chainItem).getDataItems()) { - if (!(certItem instanceof ByteString)) { - throw new RuntimeException( - "Unexpected type for array item in x5chain value"); - } - ByteArrayInputStream certBais = - new ByteArrayInputStream(((ByteString) certItem).getBytes()); - ret.add((X509Certificate) factory.generateCertificate(certBais)); - } - } else { - throw new RuntimeException("Unexpected type for x5chain value"); - } - } - return ret; - } - - public static byte[] coseBuildToBeMACed(byte[] encodedProtectedHeaders, - byte[] payload, - byte[] detachedContent) { - CborBuilder macStructure = new CborBuilder(); - ArrayBuilder<CborBuilder> array = macStructure.addArray(); - - array.add("MAC0"); - array.add(encodedProtectedHeaders); - - // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) - // so external_aad is the empty bstr - byte emptyExternalAad[] = new byte[0]; - array.add(emptyExternalAad); - - // Next field is the payload, independently of how it's transported (RFC - // 8152 section 4.4). Since our API specifies only one of |data| and - // |detachedContent| can be non-empty, it's simply just the non-empty one. - if (payload != null && payload.length > 0) { - array.add(payload); - } else { - array.add(detachedContent); - } - - return encodeCbor(macStructure.build()); - } - - public static byte[] coseMac0(SecretKey key, - @Nullable byte[] data, - byte[] detachedContent) - throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException { - - int dataLen = (data != null ? data.length : 0); - int detachedContentLen = (detachedContent != null ? detachedContent.length : 0); - if (dataLen > 0 && detachedContentLen > 0) { - throw new RuntimeException("data and detachedContent cannot both be non-empty"); - } - - CborBuilder protectedHeaders = new CborBuilder(); - MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap(); - protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256); - byte[] protectedHeadersBytes = encodeCbor(protectedHeaders.build()); - - byte[] toBeMACed = coseBuildToBeMACed(protectedHeadersBytes, data, detachedContent); - - byte[] mac = null; - Mac m = Mac.getInstance("HmacSHA256"); - m.init(key); - m.update(toBeMACed); - mac = m.doFinal(); - - CborBuilder builder = new CborBuilder(); - ArrayBuilder<CborBuilder> array = builder.addArray(); - array.add(protectedHeadersBytes); - MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap(); - if (data == null || data.length == 0) { - array.add(new SimpleValue(SimpleValueType.NULL)); - } else { - array.add(data); - } - array.add(mac); - - return encodeCbor(builder.build()); - } - - public static String replaceLine(String text, int lineNumber, String replacementLine) { - String[] lines = text.split("\n"); - int numLines = lines.length; - if (lineNumber < 0) { - lineNumber = numLines - (-lineNumber); - } - StringBuilder sb = new StringBuilder(); - for (int n = 0; n < numLines; n++) { - if (n == lineNumber) { - sb.append(replacementLine); - } else { - sb.append(lines[n]); - } - // Only add terminating newline if passed-in string ends in a newline. - if (n == numLines - 1) { - if (text.endsWith(("\n"))) { - sb.append('\n'); - } - } else { - sb.append('\n'); - } - } - return sb.toString(); - } - - public static byte[] cborEncode(DataItem dataItem) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - new CborEncoder(baos).encode(dataItem); - } catch (CborException e) { - // This should never happen and we don't want cborEncode() to throw since that - // would complicate all callers. Log it instead. - e.printStackTrace(); - Log.e(TAG, "Error encoding DataItem"); - } - return baos.toByteArray(); - } - - public static byte[] cborEncodeBoolean(boolean value) { - return cborEncode(new CborBuilder().add(value).build().get(0)); - } - - public static byte[] cborEncodeString(@NonNull String value) { - return cborEncode(new CborBuilder().add(value).build().get(0)); - } - - public static byte[] cborEncodeBytestring(@NonNull byte[] value) { - return cborEncode(new CborBuilder().add(value).build().get(0)); - } - - public static byte[] cborEncodeInt(long value) { - return cborEncode(new CborBuilder().add(value).build().get(0)); - } - - static final int CBOR_SEMANTIC_TAG_ENCODED_CBOR = 24; - - public static DataItem cborToDataItem(byte[] data) { - ByteArrayInputStream bais = new ByteArrayInputStream(data); - try { - List<DataItem> dataItems = new CborDecoder(bais).decode(); - if (dataItems.size() != 1) { - throw new RuntimeException("Expected 1 item, found " + dataItems.size()); - } - return dataItems.get(0); - } catch (CborException e) { - throw new RuntimeException("Error decoding data", e); - } - } - - public static boolean cborDecodeBoolean(@NonNull byte[] data) { - return cborToDataItem(data) == SimpleValue.TRUE; - } - - public static String cborDecodeString(@NonNull byte[] data) { - return ((co.nstant.in.cbor.model.UnicodeString) cborToDataItem(data)).getString(); - } - - public static long cborDecodeInt(@NonNull byte[] data) { - return ((co.nstant.in.cbor.model.Number) cborToDataItem(data)).getValue().longValue(); - } - - public static byte[] cborDecodeBytestring(@NonNull byte[] data) { - return ((co.nstant.in.cbor.model.ByteString) cborToDataItem(data)).getBytes(); - } - - public static String getStringEntry(ResultData data, String namespaceName, String name) { - return Util.cborDecodeString(data.getEntry(namespaceName, name)); - } - - public static boolean getBooleanEntry(ResultData data, String namespaceName, String name) { - return Util.cborDecodeBoolean(data.getEntry(namespaceName, name)); - } - - public static long getIntegerEntry(ResultData data, String namespaceName, String name) { - return Util.cborDecodeInt(data.getEntry(namespaceName, name)); - } - - public static byte[] getBytestringEntry(ResultData data, String namespaceName, String name) { - return Util.cborDecodeBytestring(data.getEntry(namespaceName, name)); - } - - /* -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: ecdsa-with-SHA256 - Issuer: CN=fake - Validity - Not Before: Jan 1 00:00:00 1970 GMT - Not After : Jan 1 00:00:00 2048 GMT - Subject: CN=fake - Subject Public Key Info: - Public Key Algorithm: id-ecPublicKey - Public-Key: (256 bit) - 00000000 04 9b 60 70 8a 99 b6 bf e3 b8 17 02 9e 93 eb 48 |..`p...........H| - 00000010 23 b9 39 89 d1 00 bf a0 0f d0 2f bd 6b 11 bc d1 |#.9......./.k...| - 00000020 19 53 54 28 31 00 f5 49 db 31 fb 9f 7d 99 bf 23 |.ST(1..I.1..}..#| - 00000030 fb 92 04 6b 23 63 55 98 ad 24 d2 68 c4 83 bf 99 |...k#cU..$.h....| - 00000040 62 |b| - Signature Algorithm: ecdsa-with-SHA256 - 30:45:02:20:67:ad:d1:34:ed:a5:68:3f:5b:33:ee:b3:18:a2: - eb:03:61:74:0f:21:64:4a:a3:2e:82:b3:92:5c:21:0f:88:3f: - 02:21:00:b7:38:5c:9b:f2:9c:b1:27:86:37:44:df:eb:4a:b2: - 6c:11:9a:c1:ff:b2:80:95:ce:fc:5f:26:b4:20:6e:9b:0d - */ - - - public static @NonNull X509Certificate signPublicKeyWithPrivateKey(String keyToSignAlias, - String keyToSignWithAlias) { - - KeyStore ks = null; - try { - ks = KeyStore.getInstance("AndroidKeyStore"); - ks.load(null); - - /* First note that KeyStore.getCertificate() returns a self-signed X.509 certificate - * for the key in question. As per RFC 5280, section 4.1 an X.509 certificate has the - * following structure: - * - * Certificate ::= SEQUENCE { - * tbsCertificate TBSCertificate, - * signatureAlgorithm AlgorithmIdentifier, - * signatureValue BIT STRING } - * - * Conveniently, the X509Certificate class has a getTBSCertificate() method which - * returns the tbsCertificate blob. So all we need to do is just sign that and build - * signatureAlgorithm and signatureValue and combine it with tbsCertificate. We don't - * need a full-blown ASN.1/DER encoder to do this. - */ - X509Certificate selfSignedCert = (X509Certificate) ks.getCertificate(keyToSignAlias); - byte[] tbsCertificate = selfSignedCert.getTBSCertificate(); - - KeyStore.Entry keyToSignWithEntry = ks.getEntry(keyToSignWithAlias, null); - Signature s = Signature.getInstance("SHA256withECDSA"); - s.initSign(((KeyStore.PrivateKeyEntry) keyToSignWithEntry).getPrivateKey()); - s.update(tbsCertificate); - byte[] signatureValue = s.sign(); - - /* The DER encoding for a SEQUENCE of length 128-65536 - the length is updated below. - * - * We assume - and test for below - that the final length is always going to be in - * this range. This is a sound assumption given we're using 256-bit EC keys. - */ - byte[] sequence = new byte[]{ - 0x30, (byte) 0x82, 0x00, 0x00 - }; - - /* The DER encoding for the ECDSA with SHA-256 signature algorithm: - * - * SEQUENCE (1 elem) - * OBJECT IDENTIFIER 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA - * algorithm with SHA256) - */ - byte[] signatureAlgorithm = new byte[]{ - 0x30, 0x0a, 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x04, 0x03, - 0x02 - }; - - /* The DER encoding for a BIT STRING with one element - the length is updated below. - * - * We assume the length of signatureValue is always going to be less than 128. This - * assumption works since we know ecdsaWithSHA256 signatures are always 69, 70, or - * 71 bytes long when DER encoded. - */ - byte[] bitStringForSignature = new byte[]{0x03, 0x00, 0x00}; - - // Calculate sequence length and set it in |sequence|. - int sequenceLength = tbsCertificate.length - + signatureAlgorithm.length - + bitStringForSignature.length - + signatureValue.length; - if (sequenceLength < 128 || sequenceLength > 65535) { - throw new Exception("Unexpected sequenceLength " + sequenceLength); - } - sequence[2] = (byte) (sequenceLength >> 8); - sequence[3] = (byte) (sequenceLength & 0xff); - - // Calculate signatureValue length and set it in |bitStringForSignature|. - int signatureValueLength = signatureValue.length + 1; - if (signatureValueLength >= 128) { - throw new Exception("Unexpected signatureValueLength " + signatureValueLength); - } - bitStringForSignature[1] = (byte) signatureValueLength; - - // Finally concatenate everything together. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write(sequence); - baos.write(tbsCertificate); - baos.write(signatureAlgorithm); - baos.write(bitStringForSignature); - baos.write(signatureValue); - byte[] resultingCertBytes = baos.toByteArray(); - - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - ByteArrayInputStream bais = new ByteArrayInputStream(resultingCertBytes); - X509Certificate result = (X509Certificate) cf.generateCertificate(bais); - return result; - } catch (Exception e) { - throw new RuntimeException("Error signing public key with private key", e); - } - } - - public static byte[] buildDeviceAuthenticationCbor(String docType, - byte[] encodedSessionTranscript, - byte[] deviceNameSpacesBytes) { - ByteArrayOutputStream daBaos = new ByteArrayOutputStream(); - try { - ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript); - List<DataItem> dataItems = null; - dataItems = new CborDecoder(bais).decode(); - DataItem sessionTranscript = dataItems.get(0); - ByteString deviceNameSpacesBytesItem = new ByteString(deviceNameSpacesBytes); - deviceNameSpacesBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - new CborEncoder(daBaos).encode(new CborBuilder() - .addArray() - .add("DeviceAuthentication") - .add(sessionTranscript) - .add(docType) - .add(deviceNameSpacesBytesItem) - .end() - .build()); - } catch (CborException e) { - throw new RuntimeException("Error encoding DeviceAuthentication", e); - } - return daBaos.toByteArray(); - } - - public static byte[] buildReaderAuthenticationBytesCbor( - byte[] encodedSessionTranscript, - byte[] requestMessageBytes) { - - ByteArrayOutputStream daBaos = new ByteArrayOutputStream(); - try { - ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript); - List<DataItem> dataItems = null; - dataItems = new CborDecoder(bais).decode(); - DataItem sessionTranscript = dataItems.get(0); - ByteString requestMessageBytesItem = new ByteString(requestMessageBytes); - requestMessageBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - new CborEncoder(daBaos).encode(new CborBuilder() - .addArray() - .add("ReaderAuthentication") - .add(sessionTranscript) - .add(requestMessageBytesItem) - .end() - .build()); - } catch (CborException e) { - throw new RuntimeException("Error encoding ReaderAuthentication", e); - } - byte[] readerAuthentication = daBaos.toByteArray(); - return Util.prependSemanticTagForEncodedCbor(readerAuthentication); - } - - // Returns #6.24(bstr) of the given already encoded CBOR - // - public static @NonNull DataItem buildCborTaggedByteString(@NonNull byte[] encodedCbor) { - DataItem item = new ByteString(encodedCbor); - item.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - return item; - } - - public static byte[] prependSemanticTagForEncodedCbor(byte[] encodedCbor) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - new CborEncoder(baos).encode(buildCborTaggedByteString(encodedCbor)); - } catch (CborException e) { - throw new RuntimeException("Error encoding with semantic tag for CBOR encoding", e); - } - return baos.toByteArray(); - } - - public static byte[] concatArrays(byte[] a, byte[] b) { - byte[] ret = new byte[a.length + b.length]; - System.arraycopy(a, 0, ret, 0, a.length); - System.arraycopy(b, 0, ret, a.length, b.length); - return ret; - } - - public static SecretKey calcEMacKeyForReader(PublicKey authenticationPublicKey, - PrivateKey ephemeralReaderPrivateKey, - byte[] encodedSessionTranscript) { - try { - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); - ka.init(ephemeralReaderPrivateKey); - ka.doPhase(authenticationPublicKey, true); - byte[] sharedSecret = ka.generateSecret(); - - byte[] sessionTranscriptBytes = - Util.prependSemanticTagForEncodedCbor(encodedSessionTranscript); - - byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes); - byte[] info = new byte[] {'E', 'M', 'a', 'c', 'K', 'e', 'y'}; - byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); - SecretKey secretKey = new SecretKeySpec(derivedKey, ""); - return secretKey; - } catch (InvalidKeyException - | NoSuchAlgorithmException e) { - throw new RuntimeException("Error performing key agreement", e); - } - } - - /** - * Computes an HKDF. - * - * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google - * /crypto/tink/subtle/Hkdf.java - * which is also Copyright (c) Google and also licensed under the Apache 2 license. - * - * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or - * "HMACSHA256". - * @param ikm the input keying material. - * @param salt optional salt. A possibly non-secret random value. If no salt is - * provided (i.e. if - * salt has length 0) then an array of 0s of the same size as the hash - * digest is used as salt. - * @param info optional context and application specific information. - * @param size The length of the generated pseudorandom string in bytes. The maximal - * size is - * 255.DigestSize, where DigestSize is the size of the underlying HMAC. - * @return size pseudorandom bytes. - */ - public static byte[] computeHkdf( - String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) { - Mac mac = null; - try { - mac = Mac.getInstance(macAlgorithm); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("No such algorithm: " + macAlgorithm, e); - } - if (size > 255 * mac.getMacLength()) { - throw new RuntimeException("size too large"); - } - try { - if (salt == null || salt.length == 0) { - // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided - // then HKDF uses a salt that is an array of zeros of the same length as the hash - // digest. - mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm)); - } else { - mac.init(new SecretKeySpec(salt, macAlgorithm)); - } - byte[] prk = mac.doFinal(ikm); - byte[] result = new byte[size]; - int ctr = 1; - int pos = 0; - mac.init(new SecretKeySpec(prk, macAlgorithm)); - byte[] digest = new byte[0]; - while (true) { - mac.update(digest); - mac.update(info); - mac.update((byte) ctr); - digest = mac.doFinal(); - if (pos + digest.length < size) { - System.arraycopy(digest, 0, result, pos, digest.length); - pos += digest.length; - ctr++; - } else { - System.arraycopy(digest, 0, result, pos, size - pos); - break; - } - } - return result; - } catch (InvalidKeyException e) { - throw new RuntimeException("Error MACing", e); - } - } - - static byte[] stripLeadingZeroes(byte[] value) { - int n = 0; - while (n < value.length && value[n] == 0) { - n++; - } - int newLen = value.length - n; - byte[] ret = new byte[newLen]; - int m = 0; - while (n < value.length) { - ret[m++] = value[n++]; - } - return ret; - } - - public static void hexdump(String name, byte[] data) { - int n, m, o; - StringBuilder sb = new StringBuilder(); - Formatter fmt = new Formatter(sb); - for (n = 0; n < data.length; n += 16) { - fmt.format("%04x ", n); - for (m = 0; m < 16 && n + m < data.length; m++) { - fmt.format("%02x ", data[n + m]); - } - for (o = m; o < 16; o++) { - sb.append(" "); - } - sb.append(" "); - for (m = 0; m < 16 && n + m < data.length; m++) { - int c = data[n + m] & 0xff; - fmt.format("%c", Character.isISOControl(c) ? '.' : c); - } - sb.append("\n"); - } - sb.append("\n"); - Log.e(TAG, name + ": dumping " + data.length + " bytes\n" + fmt.toString()); - } - - - // This returns a SessionTranscript which satisfy the requirement - // that the uncompressed X and Y coordinates of the public key for the - // mDL's ephemeral key-pair appear somewhere in the encoded - // DeviceEngagement. - public static byte[] buildSessionTranscript(KeyPair ephemeralKeyPair) { - // Make the coordinates appear in an already encoded bstr - this - // mimics how the mDL COSE_Key appear as encoded data inside the - // encoded DeviceEngagement - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW(); - // X and Y are always positive so for interop we remove any leading zeroes - // inserted by the BigInteger encoder. - byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); - byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); - baos.write(new byte[]{42}); - baos.write(x); - baos.write(y); - baos.write(new byte[]{43, 44}); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - byte[] blobWithCoords = baos.toByteArray(); - - baos = new ByteArrayOutputStream(); - try { - new CborEncoder(baos).encode(new CborBuilder() - .addArray() - .add(blobWithCoords) - .end() - .build()); - } catch (CborException e) { - e.printStackTrace(); - return null; - } - ByteString encodedDeviceEngagementItem = new ByteString(baos.toByteArray()); - ByteString encodedEReaderKeyItem = new ByteString(cborEncodeString("doesn't matter")); - encodedDeviceEngagementItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - encodedEReaderKeyItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - - baos = new ByteArrayOutputStream(); - try { - new CborEncoder(baos).encode(new CborBuilder() - .addArray() - .add(encodedDeviceEngagementItem) - .add(encodedEReaderKeyItem) - .end() - .build()); - } catch (CborException e) { - e.printStackTrace(); - return null; - } - return baos.toByteArray(); - } - - /* - * Helper function to create a CBOR data for requesting data items. The IntentToRetain - * value will be set to false for all elements. - * - * <p>The returned CBOR data conforms to the following CDDL schema:</p> - * - * <pre> - * ItemsRequest = { - * ? "docType" : DocType, - * "nameSpaces" : NameSpaces, - * ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide - * } - * - * NameSpaces = { - * + NameSpace => DataElements ; Requested data elements for each NameSpace - * } - * - * DataElements = { - * + DataElement => IntentToRetain - * } - * - * DocType = tstr - * - * DataElement = tstr - * IntentToRetain = bool - * NameSpace = tstr - * </pre> - * - * @param entriesToRequest The entries to request, organized as a map of namespace - * names with each value being a collection of data elements - * in the given namespace. - * @param docType The document type or {@code null} if there is no document - * type. - * @return CBOR data conforming to the CDDL mentioned above. - */ - public static @NonNull byte[] createItemsRequest( - @NonNull Map<String, Collection<String>> entriesToRequest, - @Nullable String docType) { - CborBuilder builder = new CborBuilder(); - MapBuilder<CborBuilder> mapBuilder = builder.addMap(); - if (docType != null) { - mapBuilder.put("docType", docType); - } - - MapBuilder<MapBuilder<CborBuilder>> nsMapBuilder = mapBuilder.putMap("nameSpaces"); - for (String namespaceName : entriesToRequest.keySet()) { - Collection<String> entryNames = entriesToRequest.get(namespaceName); - MapBuilder<MapBuilder<MapBuilder<CborBuilder>>> entryNameMapBuilder = - nsMapBuilder.putMap(namespaceName); - for (String entryName : entryNames) { - entryNameMapBuilder.put(entryName, false); - } - } - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CborEncoder encoder = new CborEncoder(baos); - try { - encoder.encode(builder.build()); - } catch (CborException e) { - throw new RuntimeException("Error encoding CBOR", e); - } - return baos.toByteArray(); - } - - public static KeyPair createEphemeralKeyPair() { - try { - KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC); - ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1"); - kpg.initialize(ecSpec); - KeyPair keyPair = kpg.generateKeyPair(); - return keyPair; - } catch (NoSuchAlgorithmException - | InvalidAlgorithmParameterException e) { - throw new RuntimeException("Error generating ephemeral key-pair", e); - } - } - - public static byte[] getPopSha256FromAuthKeyCert(X509Certificate cert) { - byte[] octetString = cert.getExtensionValue("1.3.6.1.4.1.11129.2.1.26"); - if (octetString == null) { - return null; - } - Util.hexdump("octetString", octetString); - - try { - ASN1InputStream asn1InputStream = new ASN1InputStream(octetString); - byte[] cborBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets(); - Util.hexdump("cborBytes", cborBytes); - - ByteArrayInputStream bais = new ByteArrayInputStream(cborBytes); - List<DataItem> dataItems = new CborDecoder(bais).decode(); - if (dataItems.size() != 1) { - throw new RuntimeException("Expected 1 item, found " + dataItems.size()); - } - if (!(dataItems.get(0) instanceof co.nstant.in.cbor.model.Array)) { - throw new RuntimeException("Item is not a map"); - } - co.nstant.in.cbor.model.Array array = (co.nstant.in.cbor.model.Array) dataItems.get(0); - List<DataItem> items = array.getDataItems(); - if (items.size() < 2) { - throw new RuntimeException( - "Expected at least 2 array items, found " + items.size()); - } - if (!(items.get(0) instanceof UnicodeString)) { - throw new RuntimeException("First array item is not a string"); - } - String id = ((UnicodeString) items.get(0)).getString(); - if (!id.equals("ProofOfBinding")) { - throw new RuntimeException("Expected ProofOfBinding, got " + id); - } - if (!(items.get(1) instanceof ByteString)) { - throw new RuntimeException("Second array item is not a bytestring"); - } - byte[] popSha256 = ((ByteString) items.get(1)).getBytes(); - if (popSha256.length != 32) { - throw new RuntimeException( - "Expected bstr to be 32 bytes, it is " + popSha256.length); - } - return popSha256; - } catch (IOException e) { - throw new RuntimeException("Error decoding extension data", e); - } catch (CborException e) { - throw new RuntimeException("Error decoding data", e); - } - } - -} diff --git a/identity/util/test/java/com/android/security/identity/internal/HkdfTest.java b/identity/util/test/java/com/android/security/identity/internal/HkdfTest.java deleted file mode 100644 index 6a75090e..00000000 --- a/identity/util/test/java/com/android/security/identity/internal/HkdfTest.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.security.identity.internal; - -import androidx.test.runner.AndroidJUnit4; -import com.android.security.identity.internal.Util; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.security.GeneralSecurityException; -import java.util.Random; - -/* - * This is based on https://github.com/google/tink/blob/master/java/src/test/java/com/google - * /crypto/tink/subtle/HkdfTest.java - * which is also Copyright (c) Google and licensed under the Apache 2 license. - */ -@RunWith(AndroidJUnit4.class) -public class HkdfTest { - - static Random sRandom = new Random(); - - /** Encodes a byte array to hex. */ - static String hexEncode(final byte[] bytes) { - String chars = "0123456789abcdef"; - StringBuilder result = new StringBuilder(2 * bytes.length); - for (byte b : bytes) { - // convert to unsigned - int val = b & 0xff; - result.append(chars.charAt(val / 16)); - result.append(chars.charAt(val % 16)); - } - return result.toString(); - } - - /** Decodes a hex string to a byte array. */ - static byte[] hexDecode(String hex) { - if (hex.length() % 2 != 0) { - throw new IllegalArgumentException("Expected a string of even length"); - } - int size = hex.length() / 2; - byte[] result = new byte[size]; - for (int i = 0; i < size; i++) { - int hi = Character.digit(hex.charAt(2 * i), 16); - int lo = Character.digit(hex.charAt(2 * i + 1), 16); - if ((hi == -1) || (lo == -1)) { - throw new IllegalArgumentException("input is not hexadecimal"); - } - result[i] = (byte) (16 * hi + lo); - } - return result; - } - - static byte[] randBytes(int numBytes) { - byte[] bytes = new byte[numBytes]; - sRandom.nextBytes(bytes); - return bytes; - } - - @Test - public void testNullSaltOrInfo() throws Exception { - byte[] ikm = randBytes(20); - byte[] info = randBytes(20); - int size = 40; - - byte[] hkdfWithNullSalt = Util.computeHkdf("HmacSha256", ikm, null, info, size); - byte[] hkdfWithEmptySalt = Util.computeHkdf("HmacSha256", ikm, new byte[0], info, size); - assertArrayEquals(hkdfWithNullSalt, hkdfWithEmptySalt); - - byte[] salt = randBytes(20); - byte[] hkdfWithNullInfo = Util.computeHkdf("HmacSha256", ikm, salt, null, size); - byte[] hkdfWithEmptyInfo = Util.computeHkdf("HmacSha256", ikm, salt, new byte[0], size); - assertArrayEquals(hkdfWithNullInfo, hkdfWithEmptyInfo); - } - - @Test - public void testInvalidCodeSize() throws Exception { - try { - Util.computeHkdf("HmacSha256", new byte[0], new byte[0], new byte[0], 32 * 256); - fail("Invalid size, should have thrown exception"); - } catch (RuntimeException expected) { - - // Expected - } - } - - /** - * Tests the implementation against the test vectors from RFC 5869. - */ - @Test - public void testVectors() throws Exception { - // Test case 1 - assertEquals( - "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf" - + "1a5a4c5db02d56ecc4c5bf34007208d5b887185865", - computeHkdfHex("HmacSha256", - "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "000102030405060708090a0b0c", - "f0f1f2f3f4f5f6f7f8f9", - 42)); - - // Test case 2 - assertEquals( - "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c" - + "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71" - + "cc30c58179ec3e87c14c01d5c1f3434f1d87", - computeHkdfHex("HmacSha256", - "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" - + "404142434445464748494a4b4c4d4e4f", - "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" - + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" - + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", - "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" - + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" - + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", - 82)); - - // Test case 3: salt is empty - assertEquals( - "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d" - + "9d201395faa4b61a96c8", - computeHkdfHex("HmacSha256", - "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "", - 42)); - - // Test Case 4 - assertEquals( - "085a01ea1b10f36933068b56efa5ad81a4f14b822f" - + "5b091568a9cdd4f155fda2c22e422478d305f3f896", - computeHkdfHex( - "HmacSha1", - "0b0b0b0b0b0b0b0b0b0b0b", - "000102030405060708090a0b0c", - "f0f1f2f3f4f5f6f7f8f9", - 42)); - - // Test Case 5 - assertEquals( - "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe" - + "8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e" - + "927336d0441f4c4300e2cff0d0900b52d3b4", - computeHkdfHex( - "HmacSha1", - "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" - + "404142434445464748494a4b4c4d4e4f", - "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" - + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" - + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", - "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" - + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" - + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", - 82)); - - // Test Case 6: salt is empty - assertEquals( - "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0" - + "ea00033de03984d34918", - computeHkdfHex("HmacSha1", "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "", - 42)); - - // Test Case 7 - assertEquals( - "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5" - + "673a081d70cce7acfc48", - computeHkdfHex("HmacSha1", "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "", "", - 42)); - } - - /** - * Test version of Hkdf where all inputs and outputs are hexadecimal. - */ - private String computeHkdfHex(String macAlgorithm, String ikmHex, String saltHex, - String infoHex, - int size) throws GeneralSecurityException { - return hexEncode( - Util.computeHkdf(macAlgorithm, hexDecode(ikmHex), hexDecode(saltHex), - hexDecode(infoHex), size)); - } - -} diff --git a/identity/util/test/java/com/android/security/identity/internal/UtilUnitTests.java b/identity/util/test/java/com/android/security/identity/internal/UtilUnitTests.java deleted file mode 100644 index 9c27c140..00000000 --- a/identity/util/test/java/com/android/security/identity/internal/UtilUnitTests.java +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.security.identity.internal; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; -import com.android.security.identity.internal.Util; - -import androidx.test.runner.AndroidJUnit4; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.List; - -import java.security.cert.X509Certificate; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.CborDecoder; -import co.nstant.in.cbor.CborEncoder; -import co.nstant.in.cbor.CborException; -import co.nstant.in.cbor.builder.ArrayBuilder; -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.DoublePrecisionFloat; -import co.nstant.in.cbor.model.HalfPrecisionFloat; -import co.nstant.in.cbor.model.NegativeInteger; -import co.nstant.in.cbor.model.SimpleValue; -import co.nstant.in.cbor.model.SimpleValueType; -import co.nstant.in.cbor.model.SinglePrecisionFloat; -import co.nstant.in.cbor.model.UnicodeString; -import co.nstant.in.cbor.model.UnsignedInteger; - -@RunWith(AndroidJUnit4.class) -public class UtilUnitTests { - @Test - public void prettyPrintMultipleCompleteTypes() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new CborBuilder() - .add("text") // add string - .add(1234) // add integer - .add(new byte[]{0x10}) // add byte array - .addArray() // add array - .add(1) - .add("text") - .end() - .build()); - assertEquals("'text',\n" - + "1234,\n" - + "[0x10],\n" - + "[1, 'text']", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintString() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new UnicodeString("foobar")); - assertEquals("'foobar'", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintBytestring() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new ByteString(new byte[]{1, 2, 33, (byte) 254})); - assertEquals("[0x01, 0x02, 0x21, 0xfe]", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintUnsignedInteger() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new UnsignedInteger(42)); - assertEquals("42", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintNegativeInteger() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new NegativeInteger(-42)); - assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintDouble() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new DoublePrecisionFloat(1.1)); - assertEquals("1.1", Util.cborPrettyPrint(baos.toByteArray())); - - baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new DoublePrecisionFloat(-42.0000000001)); - assertEquals("-42.0000000001", Util.cborPrettyPrint(baos.toByteArray())); - - baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new DoublePrecisionFloat(-5)); - assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintFloat() throws CborException { - ByteArrayOutputStream baos; - - // TODO: These two tests yield different results on different devices, disable for now - /* - baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new SinglePrecisionFloat(1.1f)); - assertEquals("1.100000023841858", Util.cborPrettyPrint(baos.toByteArray())); - - baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new SinglePrecisionFloat(-42.0001f)); - assertEquals("-42.000099182128906", Util.cborPrettyPrint(baos.toByteArray())); - */ - - baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new SinglePrecisionFloat(-5f)); - assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintHalfFloat() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new HalfPrecisionFloat(1.1f)); - assertEquals("1.099609375", Util.cborPrettyPrint(baos.toByteArray())); - - baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new HalfPrecisionFloat(-42.0001f)); - assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray())); - - baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new HalfPrecisionFloat(-5f)); - assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintFalse() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.FALSE)); - assertEquals("false", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintTrue() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.TRUE)); - assertEquals("true", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintNull() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.NULL)); - assertEquals("null", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintUndefined() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.UNDEFINED)); - assertEquals("undefined", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintTag() throws CborException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new CborBuilder() - .addTag(0) - .add("ABC") - .build()); - byte[] data = baos.toByteArray(); - assertEquals("tag 0 'ABC'", Util.cborPrettyPrint(data)); - } - - @Test - public void prettyPrintArrayNoCompounds() throws CborException { - // If an array has no compound elements, no newlines are used. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new CborBuilder() - .addArray() // add array - .add(1) - .add("text") - .add(new ByteString(new byte[]{1, 2, 3})) - .end() - .build()); - assertEquals("[1, 'text', [0x01, 0x02, 0x03]]", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintArray() throws CborException { - // This array contains a compound value so will use newlines - CborBuilder array = new CborBuilder(); - ArrayBuilder<CborBuilder> arrayBuilder = array.addArray(); - arrayBuilder.add(2); - arrayBuilder.add(3); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new CborBuilder() - .addArray() // add array - .add(1) - .add("text") - .add(new ByteString(new byte[]{1, 2, 3})) - .add(array.build().get(0)) - .end() - .build()); - assertEquals("[\n" - + " 1,\n" - + " 'text',\n" - + " [0x01, 0x02, 0x03],\n" - + " [2, 3]\n" - + "]", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void prettyPrintMap() throws CborException { - // If an array has no compound elements, no newlines are used. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CborEncoder(baos).encode(new CborBuilder() - .addMap() - .put("Foo", 42) - .put("Bar", "baz") - .put(43, 44) - .put(new UnicodeString("bstr"), new ByteString(new byte[]{1, 2, 3})) - .put(new ByteString(new byte[]{1, 2, 3}), new UnicodeString("other way")) - .end() - .build()); - assertEquals("{\n" - + " 43 : 44,\n" - + " [0x01, 0x02, 0x03] : 'other way',\n" - + " 'Bar' : 'baz',\n" - + " 'Foo' : 42,\n" - + " 'bstr' : [0x01, 0x02, 0x03]\n" - + "}", Util.cborPrettyPrint(baos.toByteArray())); - } - - @Test - public void testCanonicalizeCbor() throws Exception { - // {"one":1, 2:"two"} - byte[] first = - new byte[]{(byte) 0xA2, 0x63, 0x6F, 0x6E, 0x65, 0x01, 0x02, 0x63, 0x74, 0x77, 0x6F}; - - // {2: "two", "one": 1} - byte[] second = - new byte[]{(byte) 0xA2, 0x02, 0x63, 0x74, 0x77, 0x6F, 0x63, 0x6F, 0x6E, 0x65, 0x01}; - - assertArrayEquals(Util.canonicalizeCbor(first), Util.canonicalizeCbor(second)); - } - - @Test - public void cborEncodeDecodeSingle() throws Exception { - List<DataItem> items = new CborBuilder() - .addMap().put(1,"one").put("one", 1).end() - .addArray().add(42).add(true).addMap().end().end() - .add("STRING") - .build(); - for (DataItem item: items) { - assertEquals(item, Util.cborToDataItem(Util.cborEncode(item))); - } - } - - @Test - public void cborEncodeDecodeBoolean() { - assertEquals(true, Util.cborDecodeBoolean(Util.cborEncodeBoolean(true))); - assertEquals(false, Util.cborDecodeBoolean(Util.cborEncodeBoolean(false))); - } - - @Test - public void cborEncodeDecodeString() { - assertEquals("foo bar", Util.cborDecodeString(Util.cborEncodeString("foo bar"))); - } - - @Test - public void cborEncodeDecodeBytestring() { - byte[] bits = new byte[256]; - for (int i = 0; i < bits.length; ++i) { - bits[i] = (byte)i; - } - assertArrayEquals(bits, Util.cborDecodeBytestring(Util.cborEncodeBytestring(bits))); - } - - @Test - public void cborEncodeDecodeInt() { - assertEquals(0, Util.cborDecodeInt(Util.cborEncodeInt(0))); - assertEquals(Integer.MAX_VALUE, Util.cborDecodeInt(Util.cborEncodeInt(Integer.MAX_VALUE))); - assertEquals(Integer.MIN_VALUE, Util.cborDecodeInt(Util.cborEncodeInt(Integer.MIN_VALUE))); - } - - @Test - public void prependSemanticTagForEncodedCbor() throws Exception { - byte[] inputBytes = new byte[] {1, 2, 3, 4}; - byte[] encodedInput = Util.cborEncodeBytestring(inputBytes); - byte[] encodedWithTag = Util.prependSemanticTagForEncodedCbor(encodedInput); - - ByteString decodedWithTag = (ByteString)Util.cborToDataItem(encodedWithTag); - assertEquals(decodedWithTag.getTag().getValue(), 24); // RFC 8949 defines 24 - - byte[] decodedBytes = Util.cborDecodeBytestring(decodedWithTag.getBytes()); - assertArrayEquals(inputBytes, decodedBytes); - } - - private KeyPair coseGenerateKeyPair() throws Exception { - KeyPairGenerator kpg = KeyPairGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); - KeyGenParameterSpec.Builder builder = - new KeyGenParameterSpec.Builder( - "coseTestKeyPair", - KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) - .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512); - kpg.initialize(builder.build()); - return kpg.generateKeyPair(); - } - - @Test - public void coseSignAndVerify() throws Exception { - KeyPair keyPair = coseGenerateKeyPair(); - byte[] data = new byte[] {0x10, 0x11, 0x12, 0x13}; - byte[] detachedContent = new byte[] {}; - byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null); - assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic())); - assertArrayEquals(data, Util.coseSign1GetData(sig)); - assertEquals(new ArrayList() {}, Util.coseSign1GetX5Chain(sig)); - } - - @Test - public void coseSignAndVerifyDetachedContent() throws Exception { - KeyPair keyPair = coseGenerateKeyPair(); - byte[] data = new byte[] {}; - byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24}; - byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null); - assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic())); - assertArrayEquals(data, Util.coseSign1GetData(sig)); - assertEquals(new ArrayList() {}, Util.coseSign1GetX5Chain(sig)); - } - - @Test - public void coseSignAndVerifySingleCertificate() throws Exception { - KeyPair keyPair = coseGenerateKeyPair(); - byte[] data = new byte[] {}; - byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24}; - ArrayList<X509Certificate> certs = new ArrayList() {}; - certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair")); - byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs); - assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic())); - assertArrayEquals(data, Util.coseSign1GetData(sig)); - assertEquals(certs, Util.coseSign1GetX5Chain(sig)); - } - - @Test - public void coseSignAndVerifyMultipleCertificates() throws Exception { - KeyPair keyPair = coseGenerateKeyPair(); - byte[] data = new byte[] {}; - byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24}; - ArrayList<X509Certificate> certs = new ArrayList() {}; - certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair")); - certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair")); - certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair")); - byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs); - assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic())); - assertArrayEquals(data, Util.coseSign1GetData(sig)); - assertEquals(certs, Util.coseSign1GetX5Chain(sig)); - } - - @Test - public void coseMac0() throws Exception { - SecretKey secretKey = new SecretKeySpec(new byte[32], ""); - byte[] data = new byte[] {0x10, 0x11, 0x12, 0x13}; - byte[] detachedContent = new byte[] {}; - byte[] mac = Util.coseMac0(secretKey, data, detachedContent); - assertEquals("[\n" - + " [0xa1, 0x01, 0x05],\n" - + " {},\n" - + " [0x10, 0x11, 0x12, 0x13],\n" - + " [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, " - + "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, " - + "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n" - + "]", Util.cborPrettyPrint(mac)); - } - - @Test - public void coseMac0DetachedContent() throws Exception { - SecretKey secretKey = new SecretKeySpec(new byte[32], ""); - byte[] data = new byte[] {}; - byte[] detachedContent = new byte[] {0x10, 0x11, 0x12, 0x13}; - byte[] mac = Util.coseMac0(secretKey, data, detachedContent); - // Same HMAC as in coseMac0 test, only difference is that payload is null. - assertEquals("[\n" - + " [0xa1, 0x01, 0x05],\n" - + " {},\n" - + " null,\n" - + " [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, " - + "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, " - + "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n" - + "]", Util.cborPrettyPrint(mac)); - } - - @Test - public void replaceLineTest() { - assertEquals("foo", - Util.replaceLine("Hello World", 0, "foo")); - assertEquals("foo\n", - Util.replaceLine("Hello World\n", 0, "foo")); - assertEquals("Hello World", - Util.replaceLine("Hello World", 1, "foo")); - assertEquals("Hello World\n", - Util.replaceLine("Hello World\n", 1, "foo")); - assertEquals("foo\ntwo\nthree", - Util.replaceLine("one\ntwo\nthree", 0, "foo")); - assertEquals("one\nfoo\nthree", - Util.replaceLine("one\ntwo\nthree", 1, "foo")); - assertEquals("one\ntwo\nfoo", - Util.replaceLine("one\ntwo\nthree", 2, "foo")); - assertEquals("one\ntwo\nfoo", - Util.replaceLine("one\ntwo\nthree", -1, "foo")); - assertEquals("one\ntwo\nthree\nfoo", - Util.replaceLine("one\ntwo\nthree\nfour", -1, "foo")); - assertEquals("one\ntwo\nfoo\nfour", - Util.replaceLine("one\ntwo\nthree\nfour", -2, "foo")); - } - -} diff --git a/keystore-engine/Android.bp b/keystore-engine/Android.bp index cb75cde7..0cecfd82 100644 --- a/keystore-engine/Android.bp +++ b/keystore-engine/Android.bp @@ -36,7 +36,7 @@ cc_library_shared { ], shared_libs: [ - "android.system.keystore2-V1-ndk", + "android.system.keystore2-V1-ndk_platform", "libbinder_ndk", "libcrypto", "libcutils", @@ -66,7 +66,7 @@ cc_library_shared { ], shared_libs: [ - "android.system.keystore2-V1-ndk", + "android.system.keystore2-V1-ndk_platform", "libbase", "libbinder_ndk", "libcrypto", diff --git a/keystore-engine/keystore2_engine.cpp b/keystore-engine/keystore2_engine.cpp index 69caf510..69d2ca6f 100644 --- a/keystore-engine/keystore2_engine.cpp +++ b/keystore-engine/keystore2_engine.cpp @@ -23,13 +23,11 @@ #include <private/android_filesystem_config.h> -#include <openssl/bio.h> #include <openssl/bn.h> #include <openssl/ec.h> #include <openssl/ec_key.h> #include <openssl/ecdsa.h> #include <openssl/engine.h> -#include <openssl/pem.h> #include <openssl/rsa.h> #include <openssl/x509.h> @@ -329,31 +327,6 @@ extern "C" int ecdsa_sign(const uint8_t* digest, size_t digest_len, uint8_t* sig return 1; } -bssl::UniquePtr<EVP_PKEY> extractPubKey(const std::vector<uint8_t>& cert_bytes) { - const uint8_t* p = cert_bytes.data(); - bssl::UniquePtr<X509> decoded_cert(d2i_X509(nullptr, &p, cert_bytes.size())); - if (!decoded_cert) { - LOG(INFO) << AT << "Could not decode the cert, trying decoding as PEM"; - bssl::UniquePtr<BIO> cert_bio(BIO_new_mem_buf(cert_bytes.data(), cert_bytes.size())); - if (!cert_bio) { - LOG(ERROR) << AT << "Failed to create BIO"; - return {}; - } - decoded_cert = - bssl::UniquePtr<X509>(PEM_read_bio_X509(cert_bio.get(), nullptr, nullptr, nullptr)); - } - if (!decoded_cert) { - LOG(ERROR) << AT << "Could not decode the cert."; - return {}; - } - bssl::UniquePtr<EVP_PKEY> pub_key(X509_get_pubkey(decoded_cert.get())); - if (!pub_key) { - LOG(ERROR) << AT << "Could not extract public key."; - return {}; - } - return pub_key; -} - } // namespace /* EVP_PKEY_from_keystore returns an |EVP_PKEY| that contains either an RSA or @@ -384,7 +357,7 @@ extern "C" EVP_PKEY* EVP_PKEY_from_keystore2(const char* key_id) { // If the key_id starts with the grant id prefix, we parse the following string as numeric // grant id. We can then use the grant domain without alias to load the designated key. - if (android::base::StartsWith(alias, keystore2_grant_id_prefix)) { + if (alias.find(keystore2_grant_id_prefix) == 0) { std::stringstream s(alias.substr(keystore2_grant_id_prefix.size())); s >> std::hex >> reinterpret_cast<uint64_t&>(descriptor.nspace); descriptor.domain = ks2::Domain::GRANT; @@ -410,7 +383,13 @@ extern "C" EVP_PKEY* EVP_PKEY_from_keystore2(const char* key_id) { return nullptr; } - auto pkey = extractPubKey(*response.metadata.certificate); + const uint8_t* p = response.metadata.certificate->data(); + bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &p, response.metadata.certificate->size())); + if (!x509) { + LOG(ERROR) << AT << "Failed to parse x509 certificate."; + return nullptr; + } + bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get())); if (!pkey) { LOG(ERROR) << AT << "Failed to extract public key."; return nullptr; diff --git a/keystore/Android.bp b/keystore/Android.bp index 892c5b41..0f2000cd 100644 --- a/keystore/Android.bp +++ b/keystore/Android.bp @@ -36,10 +36,7 @@ cc_defaults { cc_binary { name: "keystore_cli_v2", - defaults: [ - "keystore_defaults", - "keystore2_use_latest_aidl_ndk_shared", - ], + defaults: ["keystore_defaults"], cflags: [ "-DKEYMASTER_NAME_TAGS", @@ -50,7 +47,8 @@ cc_binary { "keystore_client.proto", ], shared_libs: [ - "android.security.apc-ndk", + "android.security.apc-ndk_platform", + "android.system.keystore2-V1-ndk_platform", "libbinder", "libbinder_ndk", "libchrome", @@ -65,7 +63,7 @@ cc_binary { // Library used by both keystore and credstore for generating the ASN.1 stored // in Tag::ATTESTATION_APPLICATION_ID -cc_library { +cc_library_shared { name: "libkeystore-attestation-application-id", defaults: ["keystore_defaults"], @@ -89,7 +87,7 @@ cc_library { } // Library for keystore clients using the WiFi HIDL interface -cc_library { +cc_library_shared { name: "libkeystore-wifi-hidl", defaults: ["keystore_defaults"], diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp index d01c67d4..43f72a99 100644 --- a/keystore/keystore_cli_v2.cpp +++ b/keystore/keystore_cli_v2.cpp @@ -19,7 +19,6 @@ #include <iostream> #include <memory> #include <string> -#include <variant> #include <vector> #include <base/command_line.h> @@ -617,9 +616,9 @@ keymint::AuthorizationSet GetRSAEncryptParameters(uint32_t key_size) { return std::move(parameters); } -keymint::AuthorizationSet GetECDSAParameters(keymint::EcCurve curve, bool sha256_only) { +keymint::AuthorizationSet GetECDSAParameters(uint32_t key_size, bool sha256_only) { keymint::AuthorizationSetBuilder parameters; - parameters.EcdsaSigningKey(curve) + parameters.EcdsaSigningKey(key_size) .Digest(keymint::Digest::SHA_2_256) .Authorization(keymint::TAG_NO_AUTH_REQUIRED); if (!sha256_only) { @@ -663,12 +662,11 @@ std::vector<TestCase> GetTestCases() { {"RSA-2048 Encrypt", true, GetRSAEncryptParameters(2048)}, {"RSA-3072 Encrypt", false, GetRSAEncryptParameters(3072)}, {"RSA-4096 Encrypt", false, GetRSAEncryptParameters(4096)}, - {"ECDSA-P256 Sign", true, GetECDSAParameters(keymint::EcCurve::P_256, true)}, - {"ECDSA-P256 Sign (more digests)", false, - GetECDSAParameters(keymint::EcCurve::P_256, false)}, - {"ECDSA-P224 Sign", false, GetECDSAParameters(keymint::EcCurve::P_224, false)}, - {"ECDSA-P384 Sign", false, GetECDSAParameters(keymint::EcCurve::P_384, false)}, - {"ECDSA-P521 Sign", false, GetECDSAParameters(keymint::EcCurve::P_521, false)}, + {"ECDSA-P256 Sign", true, GetECDSAParameters(256, true)}, + {"ECDSA-P256 Sign (more digests)", false, GetECDSAParameters(256, false)}, + {"ECDSA-P224 Sign", false, GetECDSAParameters(224, false)}, + {"ECDSA-P384 Sign", false, GetECDSAParameters(384, false)}, + {"ECDSA-P521 Sign", false, GetECDSAParameters(521, false)}, {"AES-128", true, GetAESParameters(128, false)}, {"AES-256", true, GetAESParameters(256, false)}, {"AES-128-GCM", false, GetAESParameters(128, true)}, @@ -1025,7 +1023,7 @@ int Confirmation(const std::string& promptText, const std::string& extraDataHex, return 1; } - auto listener = ndk::SharedRefBase::make<ConfirmationListener>(); + auto listener = std::make_shared<ConfirmationListener>(); auto future = listener->get_future(); auto rc = apcService->presentPrompt(listener, promptText, extraData, locale, uiOptionsAsFlags); diff --git a/keystore/tests/Android.bp b/keystore/tests/Android.bp index f51cc2f5..39601ebb 100644 --- a/keystore/tests/Android.bp +++ b/keystore/tests/Android.bp @@ -62,7 +62,7 @@ cc_test { "libgtest_main", "libutils", "liblog", - "android.security.apc-ndk", + "android.security.apc-ndk_platform", ], shared_libs: [ "libbinder_ndk", diff --git a/keystore/tests/confirmationui_invocation_test.cpp b/keystore/tests/confirmationui_invocation_test.cpp index 822e6a4c..7f8a3738 100644 --- a/keystore/tests/confirmationui_invocation_test.cpp +++ b/keystore/tests/confirmationui_invocation_test.cpp @@ -55,7 +55,7 @@ TEST(ConfirmationInvocationTest, InvokeAndCancel) { std::string locale("en"); std::vector<uint8_t> extraData{0xaa, 0xff, 0x00, 0x55}; - auto listener = ndk::SharedRefBase::make<ConfirmationListener>(); + auto listener = std::make_shared<ConfirmationListener>(); auto future = listener->get_future(); diff --git a/keystore/tests/fuzzer/Android.bp b/keystore/tests/fuzzer/Android.bp deleted file mode 100644 index 4116ae14..00000000 --- a/keystore/tests/fuzzer/Android.bp +++ /dev/null @@ -1,108 +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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "system_security_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["system_security_license"], -} - -cc_fuzz { - name: "keystoreGetWifiHidl_fuzzer", - vendor: true, - srcs: [ - "keystoreGetWifiHidl_fuzzer.cpp", - ], - static_libs: [ - "libkeystore-wifi-hidl", - ], - shared_libs: [ - "android.system.wifi.keystore@1.0", - "libhidlbase", - "liblog", - "libutils", - ], - fuzz_config: { - cc: [ - "android-media-fuzzing-reports@google.com", - ], - componentid: 155276, - }, -} - -cc_defaults { - name: "keystoreAttestation_defaults", - static_libs: [ - "libkeystore-attestation-application-id", - "liblog", - "libbase", - "libhidlbase", - ], - shared_libs: [ - "libbinder", - "libcrypto", - "libutils", - ], - fuzz_config: { - cc: [ - "android-media-fuzzing-reports@google.com", - ], - componentid: 155276, - }, -} - -cc_fuzz { - name: "keystoreSignature_fuzzer", - srcs: [ - "keystoreSignature_fuzzer.cpp", - ], - defaults: [ - "keystoreAttestation_defaults", - ], -} - -cc_fuzz { - name: "keystorePackageInfo_fuzzer", - srcs: [ - "keystorePackageInfo_fuzzer.cpp", - ], - defaults: [ - "keystoreAttestation_defaults", - ], -} - -cc_fuzz { - name: "keystoreApplicationId_fuzzer", - srcs: [ - "keystoreApplicationId_fuzzer.cpp", - ], - defaults: [ - "keystoreAttestation_defaults", - ], -} - -cc_fuzz { - name: "keystoreAttestationId_fuzzer", - srcs: [ - "keystoreAttestationId_fuzzer.cpp", - ], - defaults: [ - "keystoreAttestation_defaults", - ], -} diff --git a/keystore/tests/fuzzer/README.md b/keystore/tests/fuzzer/README.md deleted file mode 100644 index 25d53aba..00000000 --- a/keystore/tests/fuzzer/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# Fuzzer for libkeystore -## Table of contents -+ [libkeystore-get-wifi-hidl](#libkeystore-get-wifi-hidl) -+ [libkeystore_attestation_application_id](#libkeystore_attestation_application_id) - -# <a name="libkeystore-get-wifi-hidl"></a> Fuzzer for libkeystore-get-wifi-hidl -## Plugin Design Considerations -The fuzzer plugin for libkeystore-get-wifi-hidl is designed based on the understanding of the library and tries to achieve the following: - -##### Maximize code coverage -The configuration parameters are not hardcoded, but instead selected based on -incoming data. This ensures more code paths are reached by the fuzzer. - -libkeystore-get-wifi-hidl supports the following parameters: -1. Key (parameter name: `key`) - -| Parameter| Valid Values| Configured Value| -|------------- |-------------| ----- | -| `key` | `String` | Value obtained from FuzzedDataProvider| - -This also ensures that the plugin is always deterministic for any given input. - -##### Maximize utilization of input data -The plugin feeds the entire input data to the libkeystore-get-wifi-hidl module. -This ensures that the plugin tolerates any kind of input (empty, huge, -malformed, etc) and doesnt `exit()` on any input and thereby increasing the -chance of identifying vulnerabilities. - -## Build - -This describes steps to build keystoreGetWifiHidl_fuzzer binary. - -### Android - -#### Steps to build -Build the fuzzer -``` - $ mm -j$(nproc) keystoreGetWifiHidl_fuzzer -``` -#### Steps to run - -To run on device -``` - $ adb sync data - $ adb shell /data/fuzz/${TARGET_ARCH}/keystoreGetWifiHidl_fuzzer/keystoreGetWifiHidl_fuzzer -``` - -# <a name="libkeystore_attestation_application_id"></a> Fuzzer for libkeystore_attestation_application_id -## Plugin Design Considerations -The fuzzer plugin for libkeystore-attestation-application-id are designed based on the understanding of the library and tries to achieve the following: - -##### Maximize code coverage -The configuration parameters are not hardcoded, but instead selected based on -incoming data. This ensures more code paths are reached by the fuzzer. - -libkeystore-attestation-application-id supports the following parameters: -1. Package Name (parameter name: `packageName`) -2. Version Code (parameter name: `versionCode`) -3. Uid (parameter name: `uid`) - - -| Parameter| Valid Values| Configured Value| -|------------- |-------------| ----- | -| `packageName` | `String` | Value obtained from FuzzedDataProvider| -| `versionCode` | `INT64_MIN` to `INT64_MAX` | Value obtained from FuzzedDataProvider| -| `uid` | `0` to `1000` | Value obtained from FuzzedDataProvider| - -This also ensures that the plugin is always deterministic for any given input. - -##### Maximize utilization of input data -The plugins feed the entire input data to the libkeystore_attestation_application_id module. -This ensures that the plugin tolerates any kind of input (empty, huge, -malformed, etc) and doesnt `exit()` on any input and thereby increasing the -chance of identifying vulnerabilities. - -## Build - -This describes steps to build keystoreSignature_fuzzer, keystorePackageInfo_fuzzer, keystoreApplicationId_fuzzer and keystoreAttestationId_fuzzer binary. - -### Android - -#### Steps to build -Build the fuzzer -``` - $ mm -j$(nproc) keystoreSignature_fuzzer - $ mm -j$(nproc) keystorePackageInfo_fuzzer - $ mm -j$(nproc) keystoreApplicationId_fuzzer - $ mm -j$(nproc) keystoreAttestationId_fuzzer -``` -#### Steps to run - -To run on device -``` - $ adb sync data - $ adb shell /data/fuzz/${TARGET_ARCH}/keystoreSignature_fuzzer/keystoreSignature_fuzzer - $ adb shell /data/fuzz/${TARGET_ARCH}/keystorePackageInfo_fuzzer/keystorePackageInfo_fuzzer - $ adb shell /data/fuzz/${TARGET_ARCH}/keystoreApplicationId_fuzzer/keystoreApplicationId_fuzzer - $ adb shell /data/fuzz/${TARGET_ARCH}/keystoreAttestationId_fuzzer/keystoreAttestationId_fuzzer -``` - -## References: - * http://llvm.org/docs/LibFuzzer.html - * https://github.com/google/oss-fuzz diff --git a/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp b/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp deleted file mode 100644 index 0eddb9a8..00000000 --- a/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp +++ /dev/null @@ -1,67 +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 "keystoreCommon.h" -#include <keystore/KeyAttestationApplicationId.h> - -using ::security::keymaster::KeyAttestationApplicationId; - -constexpr size_t kPackageVectorSizeMin = 1; -constexpr size_t kPackageVectorSizeMax = 10; - -class KeystoreApplicationId { - public: - void process(const uint8_t* data, size_t size); - ~KeystoreApplicationId() {} - - private: - void invokeApplicationId(); - std::unique_ptr<FuzzedDataProvider> mFdp; -}; - -void KeystoreApplicationId::invokeApplicationId() { - std::optional<KeyAttestationApplicationId> applicationId; - bool shouldUsePackageInfoVector = mFdp->ConsumeBool(); - if (shouldUsePackageInfoVector) { - KeyAttestationApplicationId::PackageInfoVector 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)); - } - applicationId = KeyAttestationApplicationId(std::move(packageInfoVector)); - } else { - auto packageInfoData = initPackageInfoData(mFdp.get()); - applicationId = KeyAttestationApplicationId(make_optional<KeyAttestationPackageInfo>( - String16((packageInfoData.packageName).c_str()), packageInfoData.versionCode, - packageInfoData.sharedSignaturesVector)); - } - invokeReadWriteParcel(&applicationId.value()); -} - -void KeystoreApplicationId::process(const uint8_t* data, size_t size) { - mFdp = std::make_unique<FuzzedDataProvider>(data, size); - invokeApplicationId(); -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - KeystoreApplicationId keystoreApplicationId; - keystoreApplicationId.process(data, size); - return 0; -} diff --git a/keystore/tests/fuzzer/keystoreAttestationId_fuzzer.cpp b/keystore/tests/fuzzer/keystoreAttestationId_fuzzer.cpp deleted file mode 100644 index 581da466..00000000 --- a/keystore/tests/fuzzer/keystoreAttestationId_fuzzer.cpp +++ /dev/null @@ -1,29 +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 <keystore/keystore_attestation_id.h> - -#include "fuzzer/FuzzedDataProvider.h" - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - FuzzedDataProvider fdp = FuzzedDataProvider(data, size); - uint32_t uid = fdp.ConsumeIntegral<uint32_t>(); - auto result = android::security::gather_attestation_application_id(uid); - result.isOk(); - result.status(); - result.value(); - return 0; -} diff --git a/keystore/tests/fuzzer/keystoreCommon.h b/keystore/tests/fuzzer/keystoreCommon.h deleted file mode 100644 index 7af3ba8c..00000000 --- a/keystore/tests/fuzzer/keystoreCommon.h +++ /dev/null @@ -1,77 +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. - */ -#ifndef KEYSTORECOMMON_H -#define KEYSTORECOMMON_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; - -constexpr size_t kSignatureSizeMin = 1; -constexpr size_t kSignatureSizeMax = 1000; -constexpr size_t kRandomStringLength = 256; -constexpr size_t kSignatureVectorSizeMin = 1; -constexpr size_t kSignatureVectorSizeMax = 1000; - -struct PackageInfoData { - string packageName; - int64_t versionCode; - KeyAttestationPackageInfo::SharedSignaturesVector sharedSignaturesVector; -}; - -inline void invokeReadWriteParcel(Parcelable* obj) { - Parcel parcel; - obj->writeToParcel(&parcel); - parcel.setDataPosition(0); - obj->readFromParcel(&parcel); -} - -inline vector<uint8_t> initSignatureData(FuzzedDataProvider* fdp) { - size_t signatureSize = fdp->ConsumeIntegralInRange(kSignatureSizeMin, kSignatureSizeMax); - vector<uint8_t> signatureData = fdp->ConsumeBytes<uint8_t>(signatureSize); - return signatureData; -} - -inline PackageInfoData initPackageInfoData(FuzzedDataProvider* fdp) { - PackageInfoData packageInfoData; - packageInfoData.packageName = fdp->ConsumeRandomLengthString(kRandomStringLength); - packageInfoData.versionCode = fdp->ConsumeIntegral<int64_t>(); - size_t signatureVectorSize = - fdp->ConsumeIntegralInRange(kSignatureVectorSizeMin, kSignatureVectorSizeMax); - KeyAttestationPackageInfo::SignaturesVector 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)); - } else { - signatureVector.push_back(std::nullopt); - } - } - packageInfoData.sharedSignaturesVector = - make_shared<KeyAttestationPackageInfo::SignaturesVector>(move(signatureVector)); - return packageInfoData; -} -#endif // KEYSTORECOMMON_H diff --git a/keystore/tests/fuzzer/keystoreGetWifiHidl_fuzzer.cpp b/keystore/tests/fuzzer/keystoreGetWifiHidl_fuzzer.cpp deleted file mode 100644 index 1e033c8d..00000000 --- a/keystore/tests/fuzzer/keystoreGetWifiHidl_fuzzer.cpp +++ /dev/null @@ -1,63 +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 "fuzzer/FuzzedDataProvider.h" -#include <inttypes.h> -#include <keystore/keystore_get.h> - -using namespace std; - -constexpr int32_t kMaxKeySize = 256; -const string kValidStrKeyPrefix[] = {"USRSKEY_", - "PLATFORM_VPN_", - "USRPKEY_", - "CACERT_", - "VPN_" - "USRCERT_", - "WIFI_"}; -constexpr char kStrGrantKeyPrefix[] = "ks2_keystore-engine_grant_id:"; -constexpr char kStrKeySuffix[] = "LOCKDOWN_VPN"; -constexpr size_t kGrantIdSize = 20; - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - FuzzedDataProvider fdp = FuzzedDataProvider(data, size); - size_t keyLength = fdp.ConsumeIntegralInRange<size_t>(0, kMaxKeySize); - bool usePrefix = fdp.ConsumeBool(); - string strKeyPrefix; - size_t strKeyPrefixLength = 0; - size_t strKeySuffixLength = min(fdp.remaining_bytes(), keyLength); - if (usePrefix) { - strKeyPrefix = fdp.PickValueInArray(kValidStrKeyPrefix); - strKeyPrefixLength = sizeof(strKeyPrefix); - strKeySuffixLength = - (strKeySuffixLength > strKeyPrefixLength) ? strKeySuffixLength - strKeyPrefixLength : 0; - } - string strKeySuffix = - fdp.ConsumeBool() ? string(kStrKeySuffix) : fdp.ConsumeBytesAsString(strKeySuffixLength); - string strKey; - strKey = usePrefix ? strKeyPrefix + strKeySuffix : strKeySuffix; - if (fdp.ConsumeBool()) { - uint64_t grant = fdp.ConsumeIntegral<uint64_t>(); - char grantId[kGrantIdSize] = ""; - snprintf(grantId, kGrantIdSize, "%" PRIx64, grant); - strKey = strKey + string(kStrGrantKeyPrefix) + grantId; - } - const char* key = strKey.c_str(); - uint8_t* value = nullptr; - keystore_get(key, strlen(key), &value); - free(value); - return 0; -} diff --git a/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp b/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp deleted file mode 100644 index 63899ff8..00000000 --- a/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp +++ /dev/null @@ -1,46 +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 "keystoreCommon.h" - -class KeystorePackageInfoFuzzer { - public: - void process(const uint8_t* data, size_t size); - ~KeystorePackageInfoFuzzer() {} - - private: - void invokePackageInfo(); - std::unique_ptr<FuzzedDataProvider> mFdp; -}; - -void KeystorePackageInfoFuzzer::invokePackageInfo() { - auto packageInfoData = initPackageInfoData(mFdp.get()); - KeyAttestationPackageInfo packageInfo(String16((packageInfoData.packageName).c_str()), - packageInfoData.versionCode, - packageInfoData.sharedSignaturesVector); - invokeReadWriteParcel(&packageInfo); -} - -void KeystorePackageInfoFuzzer::process(const uint8_t* data, size_t size) { - mFdp = std::make_unique<FuzzedDataProvider>(data, size); - invokePackageInfo(); -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - KeystorePackageInfoFuzzer keystorePackageInfoFuzzer; - keystorePackageInfoFuzzer.process(data, size); - return 0; -} diff --git a/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp b/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp deleted file mode 100644 index b8f8a73e..00000000 --- a/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp +++ /dev/null @@ -1,50 +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 "keystoreCommon.h" -#include <keystore/Signature.h> - -class KeystoreSignatureFuzzer { - public: - void process(const uint8_t* data, size_t size); - ~KeystoreSignatureFuzzer() {} - - private: - void invokeSignature(); - std::unique_ptr<FuzzedDataProvider> mFdp; -}; - -void KeystoreSignatureFuzzer::invokeSignature() { - std::optional<Signature> signature; - bool shouldUseParameterizedConstructor = mFdp->ConsumeBool(); - if (shouldUseParameterizedConstructor) { - std::vector<uint8_t> signatureData = initSignatureData(mFdp.get()); - signature = Signature(signatureData); - } else { - signature = Signature(); - } - invokeReadWriteParcel(&signature.value()); -} - -void KeystoreSignatureFuzzer::process(const uint8_t* data, size_t size) { - mFdp = std::make_unique<FuzzedDataProvider>(data, size); - invokeSignature(); -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - KeystoreSignatureFuzzer keystoreSignatureFuzzer; - keystoreSignatureFuzzer.process(data, size); - return 0; -} diff --git a/keystore2/Android.bp b/keystore2/Android.bp index e6cb4fb5..0069f958 100644 --- a/keystore2/Android.bp +++ b/keystore2/Android.bp @@ -25,11 +25,9 @@ rust_defaults { name: "libkeystore2_defaults", crate_name: "keystore2", srcs: ["src/lib.rs"], - defaults: [ - "keymint_use_latest_hal_aidl_rust", - ], rustlibs: [ + "android.hardware.security.keymint-V1-rust", "android.hardware.security.secureclock-V1-rust", "android.hardware.security.sharedsecret-V1-rust", "android.os.permissions_aidl-rust", @@ -39,23 +37,24 @@ rust_defaults { "android.security.maintenance-rust", "android.security.metrics-rust", "android.security.remoteprovisioning-rust", - "android.system.keystore2-V2-rust", + "android.system.keystore2-V1-rust", "libanyhow", "libbinder_rs", + "libcutils_bindgen", "libkeystore2_aaid-rust", "libkeystore2_apc_compat-rust", "libkeystore2_crypto_rust", "libkeystore2_km_compat", "libkeystore2_selinux", + "libkeystore2_system_property-rust", "libkeystore2_vintf_rust", "liblazy_static", "liblibc", + "liblibsqlite3_sys", "liblog_event_list", "liblog_rust", "librand", - "librustutils", - "libserde", - "libserde_cbor", + "librusqlite", "libthiserror", ], shared_libs: [ @@ -69,59 +68,15 @@ rust_defaults { rust_library { name: "libkeystore2", defaults: ["libkeystore2_defaults"], - rustlibs: [ - "liblibsqlite3_sys", - "librusqlite", - ], } rust_library { name: "libkeystore2_test_utils", crate_name: "keystore2_test_utils", srcs: ["test_utils/lib.rs"], - defaults: ["keymint_use_latest_hal_aidl_rust"], - rustlibs: [ - "android.system.keystore2-V2-rust", - "libbinder_rs", - "libkeystore2_selinux", - "liblog_rust", - "libnix", - "librand", - "libserde", - "libserde_cbor", - ], -} - -rust_library { - name: "libkeystore2_with_test_utils", - defaults: ["libkeystore2_defaults"], - features: [ - "keystore2_blob_test_utils", - ], - rustlibs: [ - "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"], - test_suites: ["general-tests"], - require_root: true, - auto_gen_config: true, - compile_multilib: "first", rustlibs: [ - "android.system.keystore2-V2-rust", - "libbinder_rs", - "libkeystore2_selinux", "liblog_rust", - "libnix", "librand", - "libserde", - "libserde_cbor", ], } @@ -135,25 +90,23 @@ rust_test { rustlibs: [ "libandroid_logger", "libkeystore2_test_utils", - "liblibsqlite3_sys", "libnix", - "librusqlite", - "libkeystore2_with_test_utils", ], // The test should always include watchdog. features: [ "watchdog", - "keystore2_blob_test_utils", ], } -rust_defaults { - name: "keystore2_defaults", +rust_binary { + name: "keystore2", srcs: ["src/keystore2_main.rs"], rustlibs: [ "libandroid_logger", "libbinder_rs", + "libkeystore2", "liblog_rust", + "liblegacykeystore-rust", ], init_rc: ["keystore2.rc"], @@ -165,18 +118,31 @@ rust_defaults { // selection available in the build system. prefer_rlib: true, + // TODO(b/187412695) + // This is a hack to work around the build system not installing + // dynamic dependencies of rlibs to the device. This section should + // be removed once that works correctly. + shared_libs: [ + "android.hardware.confirmationui@1.0", + "android.hardware.security.sharedsecret-V1-ndk_platform", + "android.security.compat-ndk_platform", + "libc", + "libdl_android", + "libdl", + "libandroidicu", + "libkeymint", + "libkeystore2_aaid", + "libkeystore2_apc_compat", + "libkeystore2_crypto", + "libkeystore2_vintf_cpp", + "libkm_compat_service", + "libkm_compat", + "libm", + "libstatspull", + "libstatssocket", + ], + vintf_fragments: ["android.system.keystore2-service.xml"], required: ["keystore_cli_v2"], } - -rust_binary { - name: "keystore2", - defaults: ["keystore2_defaults"], - rustlibs: [ - "libkeystore2", - "liblegacykeystore-rust", - "librusqlite", - ], - afdo: true, -} diff --git a/keystore2/TEST_MAPPING b/keystore2/TEST_MAPPING index 5d0a7dd3..16b6f858 100644 --- a/keystore2/TEST_MAPPING +++ b/keystore2/TEST_MAPPING @@ -10,26 +10,7 @@ "name": "keystore2_test" }, { - "name": "keystore2_test_utils_test" - }, - { - "name": "keystore2_legacy_blobs_test" - }, - { "name": "CtsIdentityTestCases" - }, - { - "name": "CtsKeystoreTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.RequiresDevice" - } - ] - } - ], - "postsubmit": [ - { - "name": "CtsKeystorePerformanceTestCases" } ] } diff --git a/keystore2/aaid/Android.bp b/keystore2/aaid/Android.bp index 3417960d..c04ce511 100644 --- a/keystore2/aaid/Android.bp +++ b/keystore2/aaid/Android.bp @@ -57,13 +57,3 @@ rust_library { "libkeystore2_aaid", ], } - -rust_test { - name: "libkeystore2_aaid_bindgen_test", - srcs: [":libkeystore2_aaid_bindgen"], - crate_name: "keystore2_aaid_bindgen_test", - test_suites: ["general-tests"], - auto_gen_config: true, - clippy_lints: "none", - lints: "none", -} diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp index ae08567d..4a7b7b46 100644 --- a/keystore2/aidl/Android.bp +++ b/keystore2/aidl/Android.bp @@ -24,11 +24,12 @@ package { aidl_interface { name: "android.security.attestationmanager", srcs: [ "android/security/attestationmanager/*.aidl", ], - imports: [ "android.hardware.security.keymint-V2" ], + imports: [ "android.hardware.security.keymint-V1" ], unstable: true, backend: { java: { platform_apis: true, + srcs_available: true, }, rust: { enabled: true, @@ -44,13 +45,14 @@ aidl_interface { name: "android.security.authorization", srcs: [ "android/security/authorization/*.aidl" ], imports: [ - "android.hardware.security.keymint-V2", + "android.hardware.security.keymint-V1", "android.hardware.security.secureclock-V1", ], unstable: true, backend: { java: { platform_apis: true, + srcs_available: true, }, rust: { enabled: true, @@ -69,6 +71,7 @@ aidl_interface { backend: { java: { enabled: true, + srcs_available: true, }, rust: { enabled: true, @@ -83,7 +86,7 @@ aidl_interface { name: "android.security.compat", srcs: [ "android/security/compat/*.aidl" ], imports: [ - "android.hardware.security.keymint-V2", + "android.hardware.security.keymint-V1", "android.hardware.security.secureclock-V1", "android.hardware.security.sharedsecret-V1", ], @@ -91,6 +94,7 @@ aidl_interface { backend: { java: { platform_apis: true, + srcs_available: true, }, rust: { enabled: true, @@ -106,12 +110,13 @@ aidl_interface { name: "android.security.remoteprovisioning", srcs: [ "android/security/remoteprovisioning/*.aidl" ], imports: [ - "android.hardware.security.keymint-V2", + "android.hardware.security.keymint-V1", ], unstable: true, backend: { java: { platform_apis: true, + srcs_available: true, }, ndk: { enabled: true, @@ -127,12 +132,13 @@ aidl_interface { name: "android.security.maintenance", srcs: [ "android/security/maintenance/*.aidl" ], imports: [ - "android.system.keystore2-V2", + "android.system.keystore2-V1", ], unstable: true, backend: { java: { platform_apis: true, + srcs_available: true, }, rust: { enabled: true, @@ -151,6 +157,7 @@ aidl_interface { backend: { java: { platform_apis: true, + srcs_available: true, }, rust: { enabled: true, @@ -166,12 +173,13 @@ aidl_interface { name: "android.security.metrics", srcs: [ "android/security/metrics/*.aidl" ], imports: [ - "android.system.keystore2-V2", + "android.system.keystore2-V1", ], unstable: true, backend: { java: { platform_apis: true, + srcs_available: true, }, rust: { enabled: true, @@ -183,19 +191,3 @@ aidl_interface { }, } -// cc_defaults that includes the latest Keystore2 AIDL library. -// Modules that depend on KeyMint directly can include this cc_defaults to avoid -// managing dependency versions explicitly. -cc_defaults { - name: "keystore2_use_latest_aidl_ndk_static", - static_libs: [ - "android.system.keystore2-V2-ndk", - ], -} - -cc_defaults { - name: "keystore2_use_latest_aidl_ndk_shared", - shared_libs: [ - "android.system.keystore2-V2-ndk", - ], -} diff --git a/keystore2/aidl/android/security/compat/IKeystoreCompatService.aidl b/keystore2/aidl/android/security/compat/IKeystoreCompatService.aidl index 8e347f0e..50bfa19f 100644 --- a/keystore2/aidl/android/security/compat/IKeystoreCompatService.aidl +++ b/keystore2/aidl/android/security/compat/IKeystoreCompatService.aidl @@ -29,17 +29,8 @@ import android.hardware.security.sharedsecret.ISharedSecret; */ interface IKeystoreCompatService { /** - * Return an implementation of IKeyMintDevice, that it implemented by Keystore 2.0 itself. - * The underlying implementation depends on the requested securityLevel: - * - TRUSTED_ENVIRONMENT or STRONGBOX: implementation is by means of a hardware-backed - * Keymaster 4.x instance. In this case, the returned device supports version 1 of - * the IKeyMintDevice interface, with some small omissions: - * - KeyPurpose::ATTEST_KEY is not supported (b/216437537) - * - Specification of the MGF1 digest for RSA-OAEP is not supported (b/216436980) - * - Specification of CERTIFICATE_{SUBJECT,SERIAL} is not supported for keys attested - * by hardware (b/216468666). - * - SOFTWARE: implementation is entirely software based. In this case, the returned device - * supports the current version of the IKeyMintDevice interface. + * Return an implementation of IKeyMintDevice, that it implemented by Keystore 2.0 itself + * by means of Keymaster 4.1 or lower. */ IKeyMintDevice getKeyMintDevice (SecurityLevel securityLevel); diff --git a/keystore2/aidl/android/security/metrics/EcCurve.aidl b/keystore2/aidl/android/security/metrics/EcCurve.aidl index 7b1a5a28..b190d839 100644 --- a/keystore2/aidl/android/security/metrics/EcCurve.aidl +++ b/keystore2/aidl/android/security/metrics/EcCurve.aidl @@ -29,5 +29,4 @@ enum EcCurve { P_256 = 2, P_384 = 3, P_521 = 4, - CURVE_25519 = 5, }
\ No newline at end of file diff --git a/keystore2/aidl/android/security/metrics/RkpErrorStats.aidl b/keystore2/aidl/android/security/metrics/RkpErrorStats.aidl index dcd51227..616d129e 100644 --- a/keystore2/aidl/android/security/metrics/RkpErrorStats.aidl +++ b/keystore2/aidl/android/security/metrics/RkpErrorStats.aidl @@ -17,7 +17,6 @@ package android.security.metrics; import android.security.metrics.RkpError; -import android.security.metrics.SecurityLevel; /** * Atom that encapsulates error information in remote key provisioning events. * @hide @@ -25,5 +24,4 @@ import android.security.metrics.SecurityLevel; @RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) parcelable RkpErrorStats { RkpError rkpError; - SecurityLevel security_level; }
\ No newline at end of file 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/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/android.system.keystore2-service.xml b/keystore2/android.system.keystore2-service.xml index 20c2fba9..6b8d0cb4 100644 --- a/keystore2/android.system.keystore2-service.xml +++ b/keystore2/android.system.keystore2-service.xml @@ -1,7 +1,6 @@ <manifest version="1.0" type="framework"> <hal format="aidl"> <name>android.system.keystore2</name> - <version>2</version> <interface> <name>IKeystoreService</name> <instance>default</instance> diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp index df7521e1..bf216757 100644 --- a/keystore2/apc_compat/Android.bp +++ b/keystore2/apc_compat/Android.bp @@ -63,13 +63,3 @@ rust_library { "libkeystore2_apc_compat", ], } - -rust_test { - name: "libkeystore2_apc_compat_bindgen_test", - srcs: [":libkeystore2_apc_compat_bindgen"], - crate_name: "keystore2_apc_compat_bindgen_test", - test_suites: ["general-tests"], - auto_gen_config: true, - clippy_lints: "none", - lints: "none", -} diff --git a/keystore2/keystore2.rc b/keystore2/keystore2.rc index 6f88dd39..82bf3b86 100644 --- a/keystore2/keystore2.rc +++ b/keystore2/keystore2.rc @@ -10,4 +10,4 @@ service keystore2 /system/bin/keystore2 /data/misc/keystore class early_hal user keystore group keystore readproc log - task_profiles ProcessCapacityHigh + writepid /dev/cpuset/foreground/tasks diff --git a/keystore2/legacykeystore/Android.bp b/keystore2/legacykeystore/Android.bp index 505b1653..fb6f60f6 100644 --- a/keystore2/legacykeystore/Android.bp +++ b/keystore2/legacykeystore/Android.bp @@ -21,8 +21,8 @@ package { default_applicable_licenses: ["system_security_license"], } -rust_defaults { - name: "liblegacykeystore-rust_defaults", +rust_library { + name: "liblegacykeystore-rust", crate_name: "legacykeystore", srcs: [ "lib.rs", @@ -31,22 +31,14 @@ rust_defaults { "android.security.legacykeystore-rust", "libanyhow", "libbinder_rs", + "libcutils_bindgen", + "libkeystore2", "liblog_rust", "librusqlite", - "librustutils", "libthiserror", ], } -rust_library { - name: "liblegacykeystore-rust", - defaults: ["liblegacykeystore-rust_defaults"], - rustlibs: [ - "libkeystore2", - "librusqlite", - ], -} - rust_test { name: "legacykeystore_test", crate_name: "legacykeystore", @@ -57,11 +49,11 @@ rust_test { "android.security.legacykeystore-rust", "libanyhow", "libbinder_rs", + "libcutils_bindgen", "libkeystore2", "libkeystore2_test_utils", "liblog_rust", "librusqlite", - "librustutils", "libthiserror", ], } diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs index e2d952d9..efa0870f 100644 --- a/keystore2/legacykeystore/lib.rs +++ b/keystore2/legacykeystore/lib.rs @@ -25,9 +25,8 @@ use android_security_legacykeystore::binder::{ }; use anyhow::{Context, Result}; use keystore2::{ - async_task::AsyncTask, error::anyhow_error_to_cstring, globals::SUPER_KEY, - legacy_blob::LegacyBlobLoader, maintenance::DeleteListener, maintenance::Domain, - utils::uid_to_android_user, utils::watchdog as wd, + async_task::AsyncTask, legacy_blob::LegacyBlobLoader, maintenance::DeleteListener, + maintenance::Domain, utils::watchdog as wd, }; use rusqlite::{ params, Connection, OptionalExtension, Transaction, TransactionBehavior, NO_PARAMS, @@ -162,7 +161,7 @@ impl DB { self.with_transaction(TransactionBehavior::Immediate, |tx| { tx.execute( "DELETE FROM profiles WHERE cast ( ( owner/? ) as int) = ?;", - params![rustutils::users::AID_USER_OFFSET, user_id], + params![cutils_bindgen::AID_USER_OFFSET, user_id], ) .context("In remove_uid: Failed to delete.") })?; @@ -227,10 +226,7 @@ where if log_error { log::error!("{:?}", e); } - Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )) + Err(BinderStatus::new_service_specific_error(rc, None)) }, handle_ok, ) @@ -316,8 +312,8 @@ impl LegacyKeystore { if let Some(entry) = db.get(uid, alias).context("In get: Trying to load entry from DB.")? { return Ok(entry); } - if self.get_legacy(uid, alias).context("In get: Trying to import legacy blob.")? { - // If we were able to import a legacy blob try again. + if self.get_legacy(uid, alias).context("In get: Trying to migrate legacy blob.")? { + // If we were able to migrate a legacy blob try again. if let Some(entry) = db.get(uid, alias).context("In get: Trying to load entry from DB.")? { @@ -329,20 +325,19 @@ impl LegacyKeystore { fn put(&self, alias: &str, uid: i32, entry: &[u8]) -> Result<()> { let uid = Self::get_effective_uid(uid).context("In put.")?; + // In order to make sure that we don't have stale legacy entries, make sure they are + // migrated before replacing them. + let _ = self.get_legacy(uid, alias); let mut db = self.open_db().context("In put.")?; - db.put(uid, alias, entry).context("In put: Trying to insert entry into DB.")?; - // When replacing an entry, make sure that there is no stale legacy file entry. - let _ = self.remove_legacy(uid, alias); - Ok(()) + db.put(uid, alias, entry).context("In put: Trying to insert entry into DB.") } fn remove(&self, alias: &str, uid: i32) -> Result<()> { let uid = Self::get_effective_uid(uid).context("In remove.")?; let mut db = self.open_db().context("In remove.")?; - - if self.remove_legacy(uid, alias).context("In remove: trying to remove legacy entry")? { - return Ok(()); - } + // In order to make sure that we don't have stale legacy entries, make sure they are + // migrated before removing them. + let _ = self.get_legacy(uid, alias); let removed = db.remove(uid, alias).context("In remove: Trying to remove entry from DB.")?; if removed { @@ -432,30 +427,17 @@ impl LegacyKeystore { return Ok(true); } let mut db = DB::new(&state.db_path).context("In open_db: Failed to open db.")?; - let imported = - Self::import_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db) - .context("Trying to import legacy keystore entries.")?; - if imported { + let migrated = + Self::migrate_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db) + .context("Trying to migrate legacy keystore entries.")?; + if migrated { state.recently_imported.insert((uid, alias)); } - Ok(imported) + Ok(migrated) }) .context("In get_legacy.") } - fn remove_legacy(&self, uid: u32, alias: &str) -> Result<bool> { - let alias = alias.to_string(); - self.do_serialized(move |state| { - if state.recently_imported.contains(&(uid, alias.clone())) { - return Ok(false); - } - state - .legacy_loader - .remove_legacy_keystore_entry(uid, &alias) - .context("Trying to remove legacy entry.") - }) - } - fn bulk_delete_uid(&self, uid: u32) -> Result<()> { self.do_serialized(move |state| { let entries = state @@ -488,31 +470,21 @@ impl LegacyKeystore { }) } - fn import_one_legacy_entry( + fn migrate_one_legacy_entry( uid: u32, alias: &str, legacy_loader: &LegacyBlobLoader, db: &mut DB, ) -> 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 as u32)) - { - key.decrypt(ciphertext, iv, tag) - } else { - Err(Error::sys()).context("No key found for user. Device may be locked.") - } - }) - .context("In import_one_legacy_entry: Trying to read legacy keystore entry.")?; + .read_legacy_keystore_entry(uid, alias) + .context("In migrate_one_legacy_entry: Trying to read legacy keystore entry.")?; if let Some(entry) = blob { db.put(uid, alias, &entry) - .context("In import_one_legacy_entry: Trying to insert entry into DB.")?; + .context("In migrate_one_legacy_entry: Trying to insert entry into DB.")?; legacy_loader .remove_legacy_keystore_entry(uid, alias) - .context("In import_one_legacy_entry: Trying to delete legacy keystore entry.")?; + .context("In migrate_one_legacy_entry: Trying to delete legacy keystore entry.")?; Ok(true) } else { Ok(false) @@ -554,7 +526,7 @@ mod db_test { use std::time::Duration; use std::time::Instant; - static TEST_ALIAS: &str = "test_alias"; + static TEST_ALIAS: &str = &"test_alias"; static TEST_BLOB1: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; static TEST_BLOB2: &[u8] = &[2, 2, 3, 4, 5, 6, 7, 8, 9, 0]; static TEST_BLOB3: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0]; @@ -628,9 +600,9 @@ mod db_test { .expect("Failed to open database."); // Insert three entries for owner 2. - db.put(2 + 2 * rustutils::users::AID_USER_OFFSET, "test1", TEST_BLOB1) + db.put(2 + 2 * cutils_bindgen::AID_USER_OFFSET, "test1", TEST_BLOB1) .expect("Failed to insert test1."); - db.put(4 + 2 * rustutils::users::AID_USER_OFFSET, "test2", TEST_BLOB2) + db.put(4 + 2 * cutils_bindgen::AID_USER_OFFSET, "test2", TEST_BLOB2) .expect("Failed to insert test2."); db.put(3, "test3", TEST_BLOB3).expect("Failed to insert test3."); @@ -638,12 +610,12 @@ mod db_test { assert_eq!( Vec::<String>::new(), - db.list(2 + 2 * rustutils::users::AID_USER_OFFSET).expect("Failed to list entries.") + db.list(2 + 2 * cutils_bindgen::AID_USER_OFFSET).expect("Failed to list entries.") ); assert_eq!( Vec::<String>::new(), - db.list(4 + 2 * rustutils::users::AID_USER_OFFSET).expect("Failed to list entries.") + db.list(4 + 2 * cutils_bindgen::AID_USER_OFFSET).expect("Failed to list entries.") ); assert_eq!(vec!["test3".to_string(),], db.list(3).expect("Failed to list entries.")); @@ -722,9 +694,9 @@ mod db_test { } let mut db = DB::new(&db_path3).expect("Failed to open database."); - db.put(3, TEST_ALIAS, TEST_BLOB3).expect("Failed to add entry (3)."); + db.put(3, &TEST_ALIAS, TEST_BLOB3).expect("Failed to add entry (3)."); - db.remove(3, TEST_ALIAS).expect("Remove failed (3)."); + db.remove(3, &TEST_ALIAS).expect("Remove failed (3)."); } }); @@ -738,7 +710,7 @@ mod db_test { let mut db = DB::new(&db_path).expect("Failed to open database."); // This may return Some or None but it must not fail. - db.get(3, TEST_ALIAS).expect("Failed to get entry (4)."); + db.get(3, &TEST_ALIAS).expect("Failed to get entry (4)."); } }); diff --git a/keystore2/selinux/Android.bp b/keystore2/selinux/Android.bp index 254f95e7..748e4063 100644 --- a/keystore2/selinux/Android.bp +++ b/keystore2/selinux/Android.bp @@ -63,24 +63,3 @@ rust_test { "libthiserror", ], } - -rust_test { - name: "keystore2_selinux_concurrency_test", - srcs: [ - "src/concurrency_test.rs", - ], - crate_name: "keystore2_selinux_concurrency_test", - test_suites: ["general-tests"], - auto_gen_config: true, - - rustlibs: [ - "libandroid_logger", - "libanyhow", - "libkeystore2_selinux", - "liblazy_static", - "liblog_rust", - "libnix", - "libnum_cpus", - "libthiserror", - ], -} diff --git a/keystore2/selinux/src/concurrency_test.rs b/keystore2/selinux/src/concurrency_test.rs deleted file mode 100644 index a5d2df2c..00000000 --- a/keystore2/selinux/src/concurrency_test.rs +++ /dev/null @@ -1,190 +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. - -use keystore2_selinux::{check_access, Context}; -use nix::sched::sched_setaffinity; -use nix::sched::CpuSet; -use nix::unistd::getpid; -use std::thread; -use std::{ - sync::{atomic::AtomicU8, atomic::Ordering, Arc}, - time::{Duration, Instant}, -}; - -#[derive(Clone, Copy)] -struct CatCount(u8, u8, u8, u8); - -impl CatCount { - fn next(&mut self) -> CatCount { - let result = *self; - if self.3 == 255 { - if self.2 == 254 { - if self.1 == 253 { - if self.0 == 252 { - self.0 = 255; - } - self.0 += 1; - self.1 = self.0; - } - self.1 += 1; - self.2 = self.1; - } - self.2 += 1; - self.3 = self.2; - } - self.3 += 1; - result - } - - fn make_string(&self) -> String { - format!("c{},c{},c{},c{}", self.0, self.1, self.2, self.3) - } -} - -impl Default for CatCount { - fn default() -> Self { - Self(0, 1, 2, 3) - } -} - -/// This test calls selinux_check_access concurrently causing access vector cache misses -/// in libselinux avc. The test then checks if any of the threads fails to report back -/// after a burst of access checks. The purpose of the test is to draw out a specific -/// access vector cache corruption that sends a calling thread into an infinite loop. -/// This was observed when keystore2 used libselinux concurrently in a non thread safe -/// way. See b/184006658. -#[test] -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), - ); - - let cpus = num_cpus::get(); - let turnpike = Arc::new(AtomicU8::new(0)); - let complete_count = Arc::new(AtomicU8::new(0)); - let mut threads: Vec<thread::JoinHandle<()>> = Vec::new(); - - for i in 0..cpus { - log::info!("Spawning thread {}", i); - let turnpike_clone = turnpike.clone(); - let complete_count_clone = complete_count.clone(); - threads.push(thread::spawn(move || { - let mut cpu_set = CpuSet::new(); - cpu_set.set(i).unwrap(); - sched_setaffinity(getpid(), &cpu_set).unwrap(); - let mut cat_count: CatCount = Default::default(); - - log::info!("Thread 0 reached turnpike"); - loop { - turnpike_clone.fetch_add(1, Ordering::Relaxed); - loop { - match turnpike_clone.load(Ordering::Relaxed) { - 0 => break, - 255 => return, - _ => {} - } - } - - for _ in 0..250 { - let (tctx, sctx, perm, class) = ( - Context::new("u:object_r:keystore:s0").unwrap(), - Context::new(&format!( - "u:r:untrusted_app:s0:{}", - cat_count.next().make_string() - )) - .unwrap(), - "use", - "keystore2_key", - ); - - check_access(&sctx, &tctx, class, perm).unwrap(); - } - - complete_count_clone.fetch_add(1, Ordering::Relaxed); - while complete_count_clone.load(Ordering::Relaxed) as usize != cpus { - thread::sleep(Duration::from_millis(5)); - } - } - })); - } - - let mut i = 0; - let run_time = Instant::now(); - - loop { - const TEST_ITERATIONS: u32 = 500; - const MAX_SLEEPS: u64 = 500; - const SLEEP_MILLISECONDS: u64 = 5; - let mut sleep_count: u64 = 0; - while turnpike.load(Ordering::Relaxed) as usize != cpus { - thread::sleep(Duration::from_millis(SLEEP_MILLISECONDS)); - sleep_count += 1; - assert!( - sleep_count < MAX_SLEEPS, - "Waited too long to go ready on iteration {}, only {} are ready", - i, - turnpike.load(Ordering::Relaxed) - ); - } - - if i % 100 == 0 { - let elapsed = run_time.elapsed().as_secs(); - println!("{:02}:{:02}: Iteration {}", elapsed / 60, elapsed % 60, i); - } - - // Give the threads some time to reach and spin on the turn pike. - assert_eq!(turnpike.load(Ordering::Relaxed) as usize, cpus, "i = {}", i); - if i >= TEST_ITERATIONS { - turnpike.store(255, Ordering::Relaxed); - break; - } - - // Now go. - complete_count.store(0, Ordering::Relaxed); - turnpike.store(0, Ordering::Relaxed); - i += 1; - - // Wait for them to all complete. - sleep_count = 0; - while complete_count.load(Ordering::Relaxed) as usize != cpus { - thread::sleep(Duration::from_millis(SLEEP_MILLISECONDS)); - sleep_count += 1; - if sleep_count >= MAX_SLEEPS { - // Enable the following block to park the thread to allow attaching a debugger. - if false { - println!( - "Waited {} seconds and we seem stuck. Going to sleep forever.", - (MAX_SLEEPS * SLEEP_MILLISECONDS) as f32 / 1000.0 - ); - loop { - thread::park(); - } - } else { - assert!( - sleep_count < MAX_SLEEPS, - "Waited too long to complete on iteration {}, only {} are complete", - i, - complete_count.load(Ordering::Relaxed) - ); - } - } - } - } - - for t in threads { - t.join().unwrap(); - } -} diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs index c0593b7a..5197cf65 100644 --- a/keystore2/selinux/src/lib.rs +++ b/keystore2/selinux/src/lib.rs @@ -130,7 +130,7 @@ impl Deref for Context { fn deref(&self) -> &Self::Target { match self { Self::Raw(p) => unsafe { CStr::from_ptr(*p) }, - Self::CString(cstr) => cstr, + Self::CString(cstr) => &cstr, } } } @@ -321,323 +321,6 @@ pub fn check_access(source: &CStr, target: &CStr, tclass: &str, perm: &str) -> R } } -/// Safe wrapper around setcon. -pub fn setcon(target: &CStr) -> std::io::Result<()> { - // SAFETY: `setcon` takes a const char* and only performs read accesses on it - // using strdup and strcmp. `setcon` does not retain a pointer to `target` - // and `target` outlives the call to `setcon`. - if unsafe { selinux::setcon(target.as_ptr()) } != 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(()) - } -} - -/// Represents an SEPolicy permission belonging to a specific class. -pub trait ClassPermission { - /// The permission string of the given instance as specified in the class vector. - fn name(&self) -> &'static str; - /// The class of the permission. - fn class_name(&self) -> &'static str; -} - -/// This macro implements an enum with values mapped to SELinux permission names. -/// The example below implements `enum MyPermission with public visibility: -/// * From<i32> and Into<i32> are implemented. Where the implementation of From maps -/// any variant not specified to the default `None` with value `0`. -/// * `MyPermission` implements ClassPermission. -/// * An implicit default values `MyPermission::None` is created with a numeric representation -/// of `0` and a string representation of `"none"`. -/// * Specifying a value is optional. If the value is omitted it is set to the value of the -/// previous variant left shifted by 1. -/// -/// ## Example -/// ``` -/// implement_class!( -/// /// MyPermission documentation. -/// #[derive(Clone, Copy, Debug, Eq, PartialEq)] -/// #[selinux(class_name = my_class)] -/// pub enum MyPermission { -/// #[selinux(name = foo)] -/// Foo = 1, -/// #[selinux(name = bar)] -/// Bar = 2, -/// #[selinux(name = snafu)] -/// Snafu, // Implicit value: MyPermission::Bar << 1 -> 4 -/// } -/// assert_eq!(MyPermission::Foo.name(), &"foo"); -/// assert_eq!(MyPermission::Foo.class_name(), &"my_class"); -/// assert_eq!(MyPermission::Snafu as i32, 4); -/// ); -/// ``` -#[macro_export] -macro_rules! implement_class { - // First rule: Public interface. - ( - $(#[$($enum_meta:tt)+])* - $enum_vis:vis enum $enum_name:ident $body:tt - ) => { - implement_class! { - @extract_class - [] - [$(#[$($enum_meta)+])*] - $enum_vis enum $enum_name $body - } - }; - - // The next two rules extract the #[selinux(class_name = <name>)] meta field from - // the types meta list. - // This first rule finds the field and terminates the recursion through the meta fields. - ( - @extract_class - [$(#[$mout:meta])*] - [ - #[selinux(class_name = $class_name:ident)] - $(#[$($mtail:tt)+])* - ] - $enum_vis:vis enum $enum_name:ident { - $( - $(#[$($emeta:tt)+])* - $vname:ident$( = $vval:expr)? - ),* $(,)? - } - ) => { - implement_class!{ - @extract_perm_name - $class_name - $(#[$mout])* - $(#[$($mtail)+])* - $enum_vis enum $enum_name { - 1; - [] - [$( - [] [$(#[$($emeta)+])*] - $vname$( = $vval)?, - )*] - } - } - }; - - // The second rule iterates through the type global meta fields. - ( - @extract_class - [$(#[$mout:meta])*] - [ - #[$front:meta] - $(#[$($mtail:tt)+])* - ] - $enum_vis:vis enum $enum_name:ident $body:tt - ) => { - implement_class!{ - @extract_class - [ - $(#[$mout])* - #[$front] - ] - [$(#[$($mtail)+])*] - $enum_vis enum $enum_name $body - } - }; - - // The next four rules implement two nested recursions. The outer iterates through - // the enum variants and the inner iterates through the meta fields of each variant. - // The first two rules find the #[selinux(name = <name>)] stanza, terminate the inner - // recursion and descend a level in the outer recursion. - // The first rule matches variants with explicit initializer $vval. And updates the next - // value to ($vval << 1). - ( - @extract_perm_name - $class_name:ident - $(#[$enum_meta:meta])* - $enum_vis:vis enum $enum_name:ident { - $next_val:expr; - [$($out:tt)*] - [ - [$(#[$mout:meta])*] - [ - #[selinux(name = $selinux_name:ident)] - $(#[$($mtail:tt)+])* - ] - $vname:ident = $vval:expr, - $($tail:tt)* - ] - } - ) => { - implement_class!{ - @extract_perm_name - $class_name - $(#[$enum_meta])* - $enum_vis enum $enum_name { - ($vval << 1); - [ - $($out)* - $(#[$mout])* - $(#[$($mtail)+])* - $selinux_name $vname = $vval, - ] - [$($tail)*] - } - } - }; - - // The second rule differs form the previous in that there is no explicit initializer. - // Instead $next_val is used as initializer and the next value is set to (&next_val << 1). - ( - @extract_perm_name - $class_name:ident - $(#[$enum_meta:meta])* - $enum_vis:vis enum $enum_name:ident { - $next_val:expr; - [$($out:tt)*] - [ - [$(#[$mout:meta])*] - [ - #[selinux(name = $selinux_name:ident)] - $(#[$($mtail:tt)+])* - ] - $vname:ident, - $($tail:tt)* - ] - } - ) => { - implement_class!{ - @extract_perm_name - $class_name - $(#[$enum_meta])* - $enum_vis enum $enum_name { - ($next_val << 1); - [ - $($out)* - $(#[$mout])* - $(#[$($mtail)+])* - $selinux_name $vname = $next_val, - ] - [$($tail)*] - } - } - }; - - // The third rule descends a step in the inner recursion. - ( - @extract_perm_name - $class_name:ident - $(#[$enum_meta:meta])* - $enum_vis:vis enum $enum_name:ident { - $next_val:expr; - [$($out:tt)*] - [ - [$(#[$mout:meta])*] - [ - #[$front:meta] - $(#[$($mtail:tt)+])* - ] - $vname:ident$( = $vval:expr)?, - $($tail:tt)* - ] - } - ) => { - implement_class!{ - @extract_perm_name - $class_name - $(#[$enum_meta])* - $enum_vis enum $enum_name { - $next_val; - [$($out)*] - [ - [ - $(#[$mout])* - #[$front] - ] - [$(#[$($mtail)+])*] - $vname$( = $vval)?, - $($tail)* - ] - } - } - }; - - // The fourth rule terminates the outer recursion and transitions to the - // implementation phase @spill. - ( - @extract_perm_name - $class_name:ident - $(#[$enum_meta:meta])* - $enum_vis:vis enum $enum_name:ident { - $next_val:expr; - [$($out:tt)*] - [] - } - ) => { - implement_class!{ - @spill - $class_name - $(#[$enum_meta])* - $enum_vis enum $enum_name { - $($out)* - } - } - }; - - ( - @spill - $class_name:ident - $(#[$enum_meta:meta])* - $enum_vis:vis enum $enum_name:ident { - $( - $(#[$emeta:meta])* - $selinux_name:ident $vname:ident = $vval:expr, - )* - } - ) => { - $(#[$enum_meta])* - $enum_vis enum $enum_name { - /// The default variant of the enum. - None = 0, - $( - $(#[$emeta])* - $vname = $vval, - )* - } - - impl From<i32> for $enum_name { - #[allow(non_upper_case_globals)] - fn from (p: i32) -> Self { - // Creating constants forces the compiler to evaluate the value expressions - // so that they can be used in the match statement below. - $(const $vname: i32 = $vval;)* - match p { - 0 => Self::None, - $($vname => Self::$vname,)* - _ => Self::None, - } - } - } - - impl From<$enum_name> for i32 { - fn from(p: $enum_name) -> i32 { - p as i32 - } - } - - impl ClassPermission for $enum_name { - fn name(&self) -> &'static str { - match self { - Self::None => &"none", - $(Self::$vname => stringify!($selinux_name),)* - } - } - fn class_name(&self) -> &'static str { - stringify!($class_name) - } - } - }; -} - -/// Calls `check_access` on the given class permission. -pub fn check_permission<T: ClassPermission>(source: &CStr, target: &CStr, perm: T) -> Result<()> { - check_access(source, target, perm.class_name(), perm.name()) -} - #[cfg(test)] mod tests { use super::*; diff --git a/keystore2/src/apc.rs b/keystore2/src/apc.rs index 7d56dc9f..0096686a 100644 --- a/keystore2/src/apc.rs +++ b/keystore2/src/apc.rs @@ -21,7 +21,6 @@ use std::{ sync::{mpsc::Sender, Arc, Mutex}, }; -use crate::error::anyhow_error_to_cstring; use crate::utils::{compat_2_response_code, ui_opts_2_compat, watchdog as wd}; use android_security_apc::aidl::android::security::apc::{ IConfirmationCallback::IConfirmationCallback, @@ -111,10 +110,7 @@ where _ => ResponseCode::SYSTEM_ERROR.0, }, }; - Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )) + Err(BinderStatus::new_service_specific_error(rc, None)) }, handle_ok, ) diff --git a/keystore2/src/async_task.rs b/keystore2/src/async_task.rs index 0515c8f8..e130024f 100644 --- a/keystore2/src/async_task.rs +++ b/keystore2/src/async_task.rs @@ -417,9 +417,7 @@ mod tests { Err(RecvTimeoutError::Timeout) ); done_receiver.recv().unwrap(); - // Now that the last low-priority job has completed, the idle task should - // fire pretty much immediately. - idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap(); + idle_done_receiver.recv_timeout(Duration::from_millis(1)).unwrap(); // Idle callback not executed again even if we wait for a while. assert_eq!( @@ -440,7 +438,7 @@ mod tests { Err(RecvTimeoutError::Timeout) ); done_receiver.recv().unwrap(); - idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap(); + idle_done_receiver.recv_timeout(Duration::from_millis(1)).unwrap(); } #[test] diff --git a/keystore2/src/attestation_key_utils.rs b/keystore2/src/attestation_key_utils.rs index 8354ba5c..ca00539b 100644 --- a/keystore2/src/attestation_key_utils.rs +++ b/keystore2/src/attestation_key_utils.rs @@ -35,7 +35,6 @@ use keystore2_crypto::parse_subject_from_certificate; /// handled quite differently, thus the different representations. pub enum AttestationKeyInfo { RemoteProvisioned { - key_id_guard: KeyIdGuard, attestation_key: AttestationKey, attestation_certs: Certificate, }, @@ -61,22 +60,18 @@ pub fn get_attest_key_info( let challenge_present = params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE); match attest_key_descriptor { None if challenge_present => rem_prov_state - .get_remotely_provisioned_attestation_key_and_certs(key, caller_uid, params, db) + .get_remotely_provisioned_attestation_key_and_certs(&key, caller_uid, params, db) .context(concat!( "In get_attest_key_and_cert_chain: ", "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, - } + result.map(|(attestation_key, attestation_certs)| { + AttestationKeyInfo::RemoteProvisioned { attestation_key, attestation_certs } }) }), None => Ok(None), - Some(attest_key) => get_user_generated_attestation_key(attest_key, caller_uid, db) + Some(attest_key) => get_user_generated_attestation_key(&attest_key, caller_uid, db) .context("In get_attest_key_and_cert_chain: Trying to load attest key") .map(Some), } @@ -88,7 +83,7 @@ fn get_user_generated_attestation_key( db: &mut KeystoreDB, ) -> Result<AttestationKeyInfo> { let (key_id_guard, blob, cert, blob_metadata) = - load_attest_key_blob_and_cert(key, caller_uid, db) + load_attest_key_blob_and_cert(&key, caller_uid, db) .context("In get_user_generated_attestation_key: Failed to load blob and cert")?; let issuer_subject: Vec<u8> = parse_subject_from_certificate(&cert).context( @@ -110,11 +105,11 @@ fn load_attest_key_blob_and_cert( _ => { let (key_id_guard, mut key_entry) = db .load_key_entry( - key, + &key, KeyType::Client, KeyEntryLoadBits::BOTH, caller_uid, - |k, av| check_key_permission(KeyPerm::Use, k, &av), + |k, av| check_key_permission(KeyPerm::use_(), k, &av), ) .context("In load_attest_key_blob_and_cert: Failed to load key.")?; diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs index 8265dd02..777089f4 100644 --- a/keystore2/src/authorization.rs +++ b/keystore2/src/authorization.rs @@ -15,8 +15,7 @@ //! This module implements IKeystoreAuthorization AIDL interface. use crate::error::Error as KeystoreError; -use crate::error::anyhow_error_to_cstring; -use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_IMPORTER}; +use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_MIGRATOR}; use crate::permission::KeystorePerm; use crate::super_key::UserState; use crate::utils::{check_keystore_permission, watchdog as wd}; @@ -89,10 +88,7 @@ where // as well. _ => ResponseCode::SYSTEM_ERROR.0, }; - return Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )); + return Err(BinderStatus::new_service_specific_error(rc, None)); } let rc = match root_cause.downcast_ref::<Error>() { Some(Error::Rc(rcode)) => rcode.0, @@ -102,10 +98,7 @@ where _ => ResponseCode::SYSTEM_ERROR.0, }, }; - Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )) + Err(BinderStatus::new_service_specific_error(rc, None)) }, handle_ok, ) @@ -126,7 +119,7 @@ impl AuthorizationManager { fn add_auth_token(&self, auth_token: &HardwareAuthToken) -> Result<()> { // Check keystore permission. - check_keystore_permission(KeystorePerm::AddAuth).context("In add_auth_token.")?; + check_keystore_permission(KeystorePerm::add_auth()).context("In add_auth_token.")?; ENFORCEMENTS.add_auth_token(auth_token.clone()); Ok(()) @@ -150,14 +143,12 @@ impl AuthorizationManager { (LockScreenEvent::UNLOCK, Some(password)) => { // This corresponds to the unlock() method in legacy keystore API. // check permission - check_keystore_permission(KeystorePerm::Unlock) + check_keystore_permission(KeystorePerm::unlock()) .context("In on_lock_screen_event: Unlock with password.")?; ENFORCEMENTS.set_device_locked(user_id, false); - let mut skm = SUPER_KEY.write().unwrap(); - DB.with(|db| { - skm.unlock_screen_lock_bound_key( + SUPER_KEY.unlock_screen_lock_bound_key( &mut db.borrow_mut(), user_id as u32, &password, @@ -168,9 +159,10 @@ impl AuthorizationManager { // Unlock super key. if let UserState::Uninitialized = DB .with(|db| { - skm.unlock_and_get_user_state( + UserState::get_with_password_unlock( &mut db.borrow_mut(), - &LEGACY_IMPORTER, + &LEGACY_MIGRATOR, + &SUPER_KEY, user_id as u32, &password, ) @@ -185,23 +177,21 @@ impl AuthorizationManager { Ok(()) } (LockScreenEvent::UNLOCK, None) => { - check_keystore_permission(KeystorePerm::Unlock) + check_keystore_permission(KeystorePerm::unlock()) .context("In on_lock_screen_event: 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) + SUPER_KEY.try_unlock_user_with_biometric(&mut db.borrow_mut(), user_id as u32) }) .context("In on_lock_screen_event: try_unlock_user_with_biometric failed")?; Ok(()) } (LockScreenEvent::LOCK, None) => { - check_keystore_permission(KeystorePerm::Lock) + check_keystore_permission(KeystorePerm::lock()) .context("In on_lock_screen_event: Lock")?; ENFORCEMENTS.set_device_locked(user_id, true); - let mut skm = SUPER_KEY.write().unwrap(); DB.with(|db| { - skm.lock_screen_lock_bound_key( + SUPER_KEY.lock_screen_lock_bound_key( &mut db.borrow_mut(), user_id as u32, unlocking_sids.unwrap_or(&[]), @@ -225,7 +215,7 @@ 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) + check_keystore_permission(KeystorePerm::get_auth_token()) .context("In get_auth_tokens_for_credstore.")?; // If the challenge is zero, return error @@ -275,7 +265,7 @@ impl IKeystoreAuthorization for AuthorizationManager { challenge: i64, secure_user_id: i64, auth_token_max_age_millis: i64, - ) -> binder::Result<AuthorizationTokens> { + ) -> binder::public_api::Result<AuthorizationTokens> { let _wp = wd::watch_millis("IKeystoreAuthorization::getAuthTokensForCredStore", 500); map_or_log_err( self.get_auth_tokens_for_credstore( diff --git a/keystore2/src/boot_level_keys.rs b/keystore2/src/boot_level_keys.rs index 08c52af1..1110caf9 100644 --- a/keystore2/src/boot_level_keys.rs +++ b/keystore2/src/boot_level_keys.rs @@ -243,40 +243,40 @@ mod test { fn test_output_is_consistent() -> Result<()> { let initial_key = b"initial key"; let mut blkc = BootLevelKeyCache::new(ZVec::try_from(initial_key as &[u8])?); - assert!(blkc.level_accessible(0)); - assert!(blkc.level_accessible(9)); - assert!(blkc.level_accessible(10)); - assert!(blkc.level_accessible(100)); + assert_eq!(true, blkc.level_accessible(0)); + assert_eq!(true, blkc.level_accessible(9)); + assert_eq!(true, blkc.level_accessible(10)); + assert_eq!(true, blkc.level_accessible(100)); let v0 = blkc.aes_key(0).unwrap().unwrap(); let v10 = blkc.aes_key(10).unwrap().unwrap(); assert_eq!(Some(&v0), blkc.aes_key(0)?.as_ref()); assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref()); blkc.advance_boot_level(5)?; - assert!(!blkc.level_accessible(0)); - assert!(blkc.level_accessible(9)); - assert!(blkc.level_accessible(10)); - assert!(blkc.level_accessible(100)); + assert_eq!(false, blkc.level_accessible(0)); + assert_eq!(true, blkc.level_accessible(9)); + assert_eq!(true, blkc.level_accessible(10)); + assert_eq!(true, blkc.level_accessible(100)); assert_eq!(None, blkc.aes_key(0)?); assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref()); blkc.advance_boot_level(10)?; - assert!(!blkc.level_accessible(0)); - assert!(!blkc.level_accessible(9)); - assert!(blkc.level_accessible(10)); - assert!(blkc.level_accessible(100)); + assert_eq!(false, blkc.level_accessible(0)); + assert_eq!(false, blkc.level_accessible(9)); + assert_eq!(true, blkc.level_accessible(10)); + assert_eq!(true, blkc.level_accessible(100)); assert_eq!(None, blkc.aes_key(0)?); assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref()); blkc.advance_boot_level(0)?; - assert!(!blkc.level_accessible(0)); - assert!(!blkc.level_accessible(9)); - assert!(blkc.level_accessible(10)); - assert!(blkc.level_accessible(100)); + assert_eq!(false, blkc.level_accessible(0)); + assert_eq!(false, blkc.level_accessible(9)); + assert_eq!(true, blkc.level_accessible(10)); + assert_eq!(true, blkc.level_accessible(100)); assert_eq!(None, blkc.aes_key(0)?); assert_eq!(Some(v10), blkc.aes_key(10)?); blkc.finish(); - assert!(!blkc.level_accessible(0)); - assert!(!blkc.level_accessible(9)); - assert!(!blkc.level_accessible(10)); - assert!(!blkc.level_accessible(100)); + assert_eq!(false, blkc.level_accessible(0)); + assert_eq!(false, blkc.level_accessible(9)); + assert_eq!(false, blkc.level_accessible(10)); + assert_eq!(false, blkc.level_accessible(100)); assert_eq!(None, blkc.aes_key(0)?); assert_eq!(None, blkc.aes_key(10)?); Ok(()) diff --git a/keystore2/src/crypto/Android.bp b/keystore2/src/crypto/Android.bp index c3f6f3c8..3ba47cd3 100644 --- a/keystore2/src/crypto/Android.bp +++ b/keystore2/src/crypto/Android.bp @@ -35,7 +35,6 @@ rust_library { "libkeystore2_crypto", "libcrypto", ], - vendor_available: true, } cc_library { @@ -49,7 +48,6 @@ cc_library { "libcrypto", "liblog", ], - vendor_available: true, } rust_bindgen { @@ -58,11 +56,9 @@ rust_bindgen { crate_name: "keystore2_crypto_bindgen", source_stem: "bindings", host_supported: true, - 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", @@ -129,13 +125,3 @@ cc_test { "libcrypto", ], } - -rust_test { - name: "libkeystore2_crypto_bindgen_test", - srcs: [":libkeystore2_crypto_bindgen"], - crate_name: "keystore2_crypto_bindgen_test", - test_suites: ["general-tests"], - auto_gen_config: true, - clippy_lints: "none", - lints: "none", -} diff --git a/keystore2/src/crypto/certificate_utils.cpp b/keystore2/src/crypto/certificate_utils.cpp index 64bf1d00..24b37934 100644 --- a/keystore2/src/crypto/certificate_utils.cpp +++ b/keystore2/src/crypto/certificate_utils.cpp @@ -19,7 +19,6 @@ #include <openssl/err.h> #include <openssl/evp.h> #include <openssl/mem.h> -#include <openssl/ossl_typ.h> #include <openssl/x509v3.h> #include <functional> @@ -518,7 +517,10 @@ std::variant<CertUtilsError, ASN1_STRING_Ptr> buildRsaPssParameter(Digest digest return ASN1_STRING_Ptr(algo_str); } -std::variant<CertUtilsError, X509_ALGOR_Ptr> makeAlgo(Algo algo, Padding padding, Digest digest) { +CertUtilsError makeAndSetAlgo(X509_ALGOR* algo_field, Algo algo, Padding padding, Digest digest) { + if (algo_field == nullptr) { + return CertUtilsError::UnexpectedNullPointer; + } ASN1_STRING_Ptr param; int param_type = V_ASN1_UNDEF; int nid = 0; @@ -587,29 +589,23 @@ std::variant<CertUtilsError, X509_ALGOR_Ptr> makeAlgo(Algo algo, Padding padding return CertUtilsError::InvalidArgument; } - X509_ALGOR_Ptr result(X509_ALGOR_new()); - if (!result) { - return CertUtilsError::MemoryAllocation; - } - if (!X509_ALGOR_set0(result.get(), OBJ_nid2obj(nid), param_type, param.get())) { + if (!X509_ALGOR_set0(algo_field, OBJ_nid2obj(nid), param_type, param.get())) { return CertUtilsError::Encoding; } // The X509 struct took ownership. param.release(); - return result; + return CertUtilsError::Ok; } // This function allows for signing a CertUtilsError signCertWith(X509* certificate, std::function<std::vector<uint8_t>(const uint8_t*, size_t)> sign, Algo algo, Padding padding, Digest digest) { - auto algo_objV = makeAlgo(algo, padding, digest); - if (auto error = std::get_if<CertUtilsError>(&algo_objV)) { - return *error; + if (auto error = makeAndSetAlgo(certificate->sig_alg, algo, padding, digest)) { + return error; } - auto& algo_obj = std::get<X509_ALGOR_Ptr>(algo_objV); - if (!X509_set1_signature_algo(certificate, algo_obj.get())) { - return CertUtilsError::BoringSsl; + if (auto error = makeAndSetAlgo(certificate->cert_info->signature, algo, padding, digest)) { + return error; } uint8_t* cert_buf = nullptr; @@ -624,10 +620,13 @@ CertUtilsError signCertWith(X509* certificate, return CertUtilsError::SignatureFailed; } - if (!X509_set1_signature_value(certificate, signature.data(), signature.size())) { + if (!ASN1_STRING_set(certificate->signature, signature.data(), signature.size())) { return CertUtilsError::BoringSsl; } + certificate->signature->flags &= ~(0x07); + certificate->signature->flags |= ASN1_STRING_FLAG_BITS_LEFT; + return CertUtilsError::Ok; } diff --git a/keystore2/src/crypto/crypto.cpp b/keystore2/src/crypto/crypto.cpp index 34a9a403..5d360a1e 100644 --- a/keystore2/src/crypto/crypto.cpp +++ b/keystore2/src/crypto/crypto.cpp @@ -25,7 +25,6 @@ #include <openssl/ecdh.h> #include <openssl/evp.h> #include <openssl/hkdf.h> -#include <openssl/hmac.h> #include <openssl/rand.h> #include <openssl/x509.h> @@ -67,14 +66,6 @@ const EVP_CIPHER* getAesCipherForKey(size_t key_size) { return cipher; } -bool hmacSha256(const uint8_t* key, size_t key_size, const uint8_t* msg, size_t msg_size, - uint8_t* out, size_t out_size) { - const EVP_MD* digest = EVP_sha256(); - unsigned int actual_out_size = out_size; - uint8_t* p = HMAC(digest, key, key_size, msg, msg_size, out, &actual_out_size); - return (p != nullptr); -} - bool randomBytes(uint8_t* out, size_t len) { return RAND_bytes(out, len); } diff --git a/keystore2/src/crypto/crypto.hpp b/keystore2/src/crypto/crypto.hpp index d66532f7..f841eb38 100644 --- a/keystore2/src/crypto/crypto.hpp +++ b/keystore2/src/crypto/crypto.hpp @@ -22,8 +22,6 @@ #include <stddef.h> extern "C" { - bool hmacSha256(const uint8_t* key, size_t key_size, const uint8_t* msg, size_t msg_size, - uint8_t* out, size_t out_size); bool randomBytes(uint8_t* out, size_t len); bool AES_gcm_encrypt(const uint8_t* in, uint8_t* out, size_t len, const uint8_t* key, size_t key_size, const uint8_t* iv, uint8_t* tag); diff --git a/keystore2/src/crypto/error.rs b/keystore2/src/crypto/error.rs index 48a2d4cb..a369012c 100644 --- a/keystore2/src/crypto/error.rs +++ b/keystore2/src/crypto/error.rs @@ -13,7 +13,6 @@ // limitations under the License. //! This module implements Error for the keystore2_crypto library. -use crate::zvec; /// Crypto specific error codes. #[derive(Debug, thiserror::Error, Eq, PartialEq)] @@ -94,12 +93,4 @@ pub enum Error { /// This is returned if the C implementation of extractSubjectFromCertificate failed. #[error("Failed to extract certificate subject.")] ExtractSubjectFailed, - - /// This is returned if the C implementation of hmacSha256 failed. - #[error("Failed to calculate HMAC-SHA256.")] - HmacSha256Failed, - - /// Zvec error. - #[error(transparent)] - ZVec(#[from] zvec::Error), } diff --git a/keystore2/src/crypto/include/certificate_utils.h b/keystore2/src/crypto/include/certificate_utils.h index cad82b61..b483d88d 100644 --- a/keystore2/src/crypto/include/certificate_utils.h +++ b/keystore2/src/crypto/include/certificate_utils.h @@ -39,7 +39,6 @@ DEFINE_OPENSSL_OBJECT_POINTER(ASN1_OCTET_STRING); DEFINE_OPENSSL_OBJECT_POINTER(ASN1_TIME); DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY); DEFINE_OPENSSL_OBJECT_POINTER(X509); -DEFINE_OPENSSL_OBJECT_POINTER(X509_ALGOR); DEFINE_OPENSSL_OBJECT_POINTER(X509_EXTENSION); DEFINE_OPENSSL_OBJECT_POINTER(X509_NAME); DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY_CTX); diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs index 14bdf045..5f8a2ef2 100644 --- a/keystore2/src/crypto/lib.rs +++ b/keystore2/src/crypto/lib.rs @@ -16,11 +16,11 @@ //! Keystore 2.0. mod error; -pub mod zvec; +mod zvec; pub use error::Error; use keystore2_crypto_bindgen::{ - extractSubjectFromCertificate, generateKeyFromPassword, hmacSha256, randomBytes, - AES_gcm_decrypt, AES_gcm_encrypt, ECDHComputeKey, ECKEYGenerateKey, ECKEYMarshalPrivateKey, + extractSubjectFromCertificate, generateKeyFromPassword, 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, }; @@ -39,8 +39,6 @@ pub const AES_256_KEY_LENGTH: usize = 32; pub const AES_128_KEY_LENGTH: usize = 16; /// Length of the expected salt for key from password generation. pub const SALT_LENGTH: usize = 16; -/// Length of an HMAC-SHA256 tag in bytes. -pub const HMAC_SHA256_LEN: usize = 32; /// Older versions of keystore produced IVs with four extra /// ignored zero bytes at the end; recognise and trim those. @@ -74,21 +72,6 @@ pub fn generate_random_data(size: usize) -> Result<Vec<u8>, Error> { } } -/// Perform HMAC-SHA256. -pub fn hmac_sha256(key: &[u8], msg: &[u8]) -> Result<Vec<u8>, Error> { - let mut tag = vec![0; HMAC_SHA256_LEN]; - // Safety: The first two pairs of arguments must point to const buffers with - // size given by the second arg of the pair. The final pair of arguments - // must point to an output buffer with size given by the second arg of the - // pair. - match unsafe { - hmacSha256(key.as_ptr(), key.len(), msg.as_ptr(), msg.len(), tag.as_mut_ptr(), tag.len()) - } { - true => Ok(tag), - false => Err(Error::HmacSha256Failed), - } -} - /// Uses AES GCM to decipher a message given an initialization vector, aead tag, and key. /// This function accepts 128 and 256-bit keys and uses AES128 and AES256 respectively based /// on the key length. @@ -582,18 +565,4 @@ mod tests { assert_eq!(left_key, right_key); Ok(()) } - - #[test] - fn test_hmac_sha256() { - let key = b"This is the key"; - let msg1 = b"This is a message"; - let msg2 = b"This is another message"; - let tag1a = hmac_sha256(key, msg1).unwrap(); - assert_eq!(tag1a.len(), HMAC_SHA256_LEN); - let tag1b = hmac_sha256(key, msg1).unwrap(); - assert_eq!(tag1a, tag1b); - let tag2 = hmac_sha256(key, msg2).unwrap(); - assert_eq!(tag2.len(), HMAC_SHA256_LEN); - assert_ne!(tag1a, tag2); - } } diff --git a/keystore2/src/crypto/tests/certificate_utils_test.cpp b/keystore2/src/crypto/tests/certificate_utils_test.cpp index bd949282..ebd67929 100644 --- a/keystore2/src/crypto/tests/certificate_utils_test.cpp +++ b/keystore2/src/crypto/tests/certificate_utils_test.cpp @@ -334,4 +334,4 @@ TEST(TimeStringTests, toTimeStringTest) { // And one millisecond earlier must be GeneralizedTime. // This also checks that the rounding direction does not flip when the input is negative. ASSERT_EQ(std::string(toTimeString(-631152000001)->data()), "19491231235959Z"); -} +}
\ No newline at end of file diff --git a/keystore2/src/crypto/zvec.rs b/keystore2/src/crypto/zvec.rs index 5a173c30..78b474ea 100644 --- a/keystore2/src/crypto/zvec.rs +++ b/keystore2/src/crypto/zvec.rs @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Implements ZVec, a vector that is mlocked during its lifetime and zeroed -//! when dropped. - +use crate::error::Error; use nix::sys::mman::{mlock, munlock}; use std::convert::TryFrom; use std::fmt; @@ -31,14 +29,6 @@ pub struct ZVec { len: usize, } -/// ZVec specific error codes. -#[derive(Debug, thiserror::Error, Eq, PartialEq)] -pub enum Error { - /// Underlying libc error. - #[error(transparent)] - NixError(#[from] nix::Error), -} - impl ZVec { /// Create a ZVec with the given size. pub fn new(size: usize) -> Result<Self, Error> { @@ -58,14 +48,6 @@ impl ZVec { self.len = len; } } - - /// Attempts to make a clone of the Zvec. This may fail due trying to mlock - /// the new memory region. - pub fn try_clone(&self) -> Result<Self, Error> { - let mut result = Self::new(self.len())?; - result[..].copy_from_slice(&self[..]); - Ok(result) - } } impl Drop for ZVec { diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs index a3979bd5..c7887200 100644 --- a/keystore2/src/database.rs +++ b/keystore2/src/database.rs @@ -45,8 +45,6 @@ mod perboot; 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::metrics_store::log_rkp_error_stats; @@ -56,6 +54,7 @@ use crate::{ error::{Error as KsError, ErrorCode, ResponseCode}, super_key::SuperKeyType, }; +use crate::{gc::Gc, super_key::USER_SUPER_KEY}; use anyhow::{anyhow, Context, Result}; use std::{convert::TryFrom, convert::TryInto, ops::Deref, time::SystemTimeError}; use utils as db_utils; @@ -83,7 +82,7 @@ use log::error; #[cfg(not(test))] use rand::prelude::random; use rusqlite::{ - params, params_from_iter, + params, types::FromSql, types::FromSqlResult, types::ToSqlOutput, @@ -143,7 +142,7 @@ impl KeyMetaData { let db_tag: i64 = row.get(0).context("Failed to read tag.")?; metadata.insert( db_tag, - KeyMetaEntry::new_from_sql(db_tag, &SqlField::new(1, row)) + KeyMetaEntry::new_from_sql(db_tag, &SqlField::new(1, &row)) .context("Failed to read KeyMetaEntry.")?, ); Ok(()) @@ -218,7 +217,7 @@ impl BlobMetaData { let db_tag: i64 = row.get(0).context("Failed to read tag.")?; metadata.insert( db_tag, - BlobMetaEntry::new_from_sql(db_tag, &SqlField::new(1, row)) + BlobMetaEntry::new_from_sql(db_tag, &SqlField::new(1, &row)) .context("Failed to read BlobMetaEntry.")?, ); Ok(()) @@ -324,8 +323,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 = 20000; - /// Indicates how the sensitive part of this key blob is encrypted. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum EncryptedBy { @@ -391,12 +388,12 @@ impl DateTime { } /// Returns unix epoch time in milliseconds. - pub fn to_millis_epoch(self) -> i64 { + pub fn to_millis_epoch(&self) -> i64 { self.0 } /// Returns unix epoch time in seconds. - pub fn to_secs_epoch(self) -> i64 { + pub fn to_secs_epoch(&self) -> i64 { self.0 / 1000 } } @@ -581,36 +578,6 @@ pub struct CertificateInfo { cert_chain: Option<Vec<u8>>, } -/// This type represents a Blob with its metadata and an optional superseded blob. -#[derive(Debug)] -pub struct BlobInfo<'a> { - blob: &'a [u8], - metadata: &'a BlobMetaData, - /// Superseded blobs are an artifact of legacy import. In some rare occasions - /// the key blob needs to be upgraded during import. In that case two - /// blob are imported, the superseded one will have to be imported first, - /// so that the garbage collector can reap it. - superseded_blob: Option<(&'a [u8], &'a BlobMetaData)>, -} - -impl<'a> BlobInfo<'a> { - /// Create a new instance of blob info with blob and corresponding metadata - /// and no superseded blob info. - pub fn new(blob: &'a [u8], metadata: &'a BlobMetaData) -> Self { - Self { blob, metadata, superseded_blob: None } - } - - /// Create a new instance of blob info with blob and corresponding metadata - /// as well as superseded blob info. - pub fn new_with_superseded( - blob: &'a [u8], - metadata: &'a BlobMetaData, - superseded_blob: Option<(&'a [u8], &'a BlobMetaData)>, - ) -> Self { - Self { blob, metadata, superseded_blob } - } -} - impl CertificateInfo { /// Constructs a new CertificateInfo object from `cert` and `cert_chain` pub fn new(cert: Option<Vec<u8>>, cert_chain: Option<Vec<u8>>) -> Self { @@ -865,7 +832,7 @@ impl KeystoreDB { const UPGRADERS: &'static [fn(&Transaction) -> Result<u32>] = &[Self::from_0_to_1]; /// Name of the file that holds the cross-boot persistent database. - pub const PERSISTENT_DB_FILENAME: &'static str = "persistent.sqlite"; + pub const PERSISTENT_DB_FILENAME: &'static str = &"persistent.sqlite"; /// This will create a new database connection connecting the two /// files persistent.sqlite and perboot.sqlite in the given directory. @@ -875,7 +842,7 @@ impl KeystoreDB { pub fn new(db_root: &Path, gc: Option<Arc<Gc>>) -> Result<Self> { let _wp = wd::watch_millis("KeystoreDB::new", 500); - let persistent_path = Self::make_persistent_path(db_root)?; + let persistent_path = Self::make_persistent_path(&db_root)?; let conn = Self::make_connection(&persistent_path)?; let mut db = Self { conn, gc, perboot: perboot::PERBOOT_DB.clone() }; @@ -1062,7 +1029,7 @@ impl KeystoreDB { params: &[&str], ) -> Result<StorageStats> { let (total, unused) = self.with_transaction(TransactionBehavior::Deferred, |tx| { - tx.query_row(query, params_from_iter(params), |row| Ok((row.get(0)?, row.get(1)?))) + tx.query_row(query, params, |row| Ok((row.get(0)?, row.get(1)?))) .with_context(|| { format!("get_storage_stat: Error size of storage type {}", storage_type.0) }) @@ -1277,7 +1244,7 @@ impl KeystoreDB { self.with_transaction(TransactionBehavior::Immediate, |tx| { let key_descriptor = KeyDescriptor { domain, nspace, alias: Some(alias.to_string()), blob: None }; - let result = Self::load_key_entry_id(tx, &key_descriptor, key_type); + let result = Self::load_key_entry_id(&tx, &key_descriptor, key_type); match result { Ok(_) => Ok(true), Err(error) => match error.root_cause().downcast_ref::<KsError>() { @@ -1323,7 +1290,7 @@ impl KeystoreDB { key_metadata.store_in_db(key_id, tx).context("KeyMetaData::store_in_db failed")?; Self::set_blob_internal( - tx, + &tx, key_id, SubComponentType::KEY_BLOB, Some(blob), @@ -1353,10 +1320,10 @@ impl KeystoreDB { alias: Some(key_type.alias.into()), blob: None, }; - let id = Self::load_key_entry_id(tx, &key_descriptor, KeyType::Super); + let id = Self::load_key_entry_id(&tx, &key_descriptor, KeyType::Super); match id { Ok(id) => { - let key_entry = Self::load_key_components(tx, KeyEntryLoadBits::KM, id) + let key_entry = Self::load_key_components(&tx, KeyEntryLoadBits::KM, id) .context("In load_super_key. Failed to load key entry.")?; Ok(Some((KEY_ID_LOCK.get(id), key_entry))) } @@ -1416,7 +1383,7 @@ impl KeystoreDB { let (id, entry) = match id { Some(id) => ( id, - Self::load_key_components(tx, KeyEntryLoadBits::KM, id) + Self::load_key_components(&tx, KeyEntryLoadBits::KM, id) .context("In get_or_create_key_with.")?, ), @@ -1442,7 +1409,7 @@ impl KeystoreDB { let (blob, metadata) = create_new_key().context("In get_or_create_key_with.")?; Self::set_blob_internal( - tx, + &tx, id, SubComponentType::KEY_BLOB, Some(&blob), @@ -1593,7 +1560,7 @@ impl KeystoreDB { .context("In create_key_entry")?, ); Self::set_blob_internal( - tx, + &tx, key_id.0, SubComponentType::KEY_BLOB, Some(private_key), @@ -1602,7 +1569,7 @@ impl KeystoreDB { 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)?; + metadata.store_in_db(key_id.0, &tx)?; Ok(()).no_gc() }) .context("In create_attestation_key_entry") @@ -1625,7 +1592,7 @@ impl KeystoreDB { let _wp = wd::watch_millis("KeystoreDB::set_blob", 500); self.with_transaction(TransactionBehavior::Immediate, |tx| { - Self::set_blob_internal(tx, key_id.0, sc_type, blob, blob_metadata).need_gc() + Self::set_blob_internal(&tx, key_id.0, sc_type, blob, blob_metadata).need_gc() }) .context("In set_blob.") } @@ -1639,7 +1606,7 @@ impl KeystoreDB { self.with_transaction(TransactionBehavior::Immediate, |tx| { Self::set_blob_internal( - tx, + &tx, Self::UNASSIGNED_KEY_ID, SubComponentType::KEY_BLOB, Some(blob), @@ -1732,7 +1699,7 @@ impl KeystoreDB { #[cfg(test)] fn insert_key_metadata(&mut self, key_id: &KeyIdGuard, metadata: &KeyMetaData) -> Result<()> { self.with_transaction(TransactionBehavior::Immediate, |tx| { - metadata.store_in_db(key_id.0, tx).no_gc() + metadata.store_in_db(key_id.0, &tx).no_gc() }) .context("In insert_key_metadata.") } @@ -1794,16 +1761,16 @@ impl KeystoreDB { metadata.add(KeyMetaEntry::AttestationExpirationDate(DateTime::from_millis_epoch( expiration_date, ))); - metadata.store_in_db(key_id, tx).context("Failed to insert key metadata.")?; + metadata.store_in_db(key_id, &tx).context("Failed to insert key metadata.")?; Self::set_blob_internal( - tx, + &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) + Self::set_blob_internal(&tx, key_id, SubComponentType::CERT, Some(batch_cert), None) .context("Failed to insert cert")?; Ok(()).no_gc() }) @@ -1864,9 +1831,7 @@ impl KeystoreDB { ) .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); + log_rkp_error_stats(MetricsRkpError::OUT_OF_KEYS); return Err(KsError::Rc(ResponseCode::OUT_OF_KEYS)).context("Out of keys."); } else if result > 1 { return Err(KsError::sys()) @@ -1944,15 +1909,12 @@ impl KeystoreDB { )? .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, + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64, ); 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)? { + if Self::mark_unreferenced(&tx, id)? { num_deleted += 1; } } @@ -1979,7 +1941,7 @@ impl KeystoreDB { .context("Failed to execute statement")?; let num_deleted = keys_to_delete .iter() - .map(|id| Self::mark_unreferenced(tx, *id)) + .map(|id| Self::mark_unreferenced(&tx, *id)) .collect::<Result<Vec<bool>>>() .context("Failed to execute mark_unreferenced on a keyid")? .into_iter() @@ -2057,41 +2019,6 @@ impl KeystoreDB { .context("In get_attestation_pool_status: ") } - 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. @@ -2100,7 +2027,7 @@ impl KeystoreDB { domain: Domain, namespace: i64, km_uuid: &Uuid, - ) -> Result<Option<(KeyIdGuard, CertificateChain)>> { + ) -> Result<Option<CertificateChain>> { let _wp = wd::watch_millis("KeystoreDB::retrieve_attestation_key_and_cert_chain", 500); match domain { @@ -2110,71 +2037,69 @@ impl KeystoreDB { .context(format!("Domain {:?} must be either App or SELinux.", domain)); } } - - self.delete_expired_attestation_keys().context( - "In retrieve_attestation_key_and_cert_chain: failed to prune expired attestation keys", - )?; - let tx = self.conn.unchecked_transaction().context( - "In retrieve_attestation_key_and_cert_chain: 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("In retrieve_attestation_key_and_cert_chain: Failed to commit keyid query")?; - let key_id_guard = KEY_ID_LOCK.get(key_id); - let tx = self.conn.unchecked_transaction().context( - "In retrieve_attestation_key_and_cert_chain: 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; + self.with_transaction(TransactionBehavior::Deferred, |tx| { + let mut stmt = tx.prepare( + "SELECT subcomponent_type, blob + FROM persistent.blobentry + WHERE keyentryid IN + (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| Ok((row.get(0)?, row.get(1)?)), + )? + .collect::<rusqlite::Result<Vec<(SubComponentType, Vec<u8>)>>>() + .context("query failed.")?; + if rows.is_empty() { + return Ok(None).no_gc(); + } 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.")?, } - _ => Err(KsError::sys()).context("Unknown or incorrect subcomponent type.")?, } - } - Ok(Some(( - key_id_guard, - CertificateChain { + Ok(Some(CertificateChain { private_key: ZVec::try_from(km_blob)?, batch_cert: batch_cert_blob, cert_chain: cert_chain_blob, - }, - ))) + })) + .no_gc() + }) + .context("In retrieve_attestation_key_and_cert_chain:") } /// Updates the alias column of the given key id `newid` with the given alias, @@ -2302,13 +2227,13 @@ impl KeystoreDB { /// fields, and rebinds the given alias to the new key. /// The boolean returned is a hint for the garbage collector. If true, a key was replaced, /// is now unreferenced and needs to be collected. - #[allow(clippy::too_many_arguments)] + #[allow(clippy::clippy::too_many_arguments)] pub fn store_new_key( &mut self, key: &KeyDescriptor, key_type: KeyType, params: &[KeyParameter], - blob_info: &BlobInfo, + blob_info: &(&[u8], &BlobMetaData), cert_info: &CertificateInfo, metadata: &KeyMetaData, km_uuid: &Uuid, @@ -2328,37 +2253,17 @@ impl KeystoreDB { self.with_transaction(TransactionBehavior::Immediate, |tx| { let key_id = Self::create_key_entry_internal(tx, &domain, namespace, key_type, km_uuid) .context("Trying to create new key entry.")?; - let BlobInfo { blob, metadata: blob_metadata, superseded_blob } = *blob_info; - - // In some occasions the key blob is already upgraded during the import. - // In order to make sure it gets properly deleted it is inserted into the - // database here and then immediately replaced by the superseding blob. - // The garbage collector will then subject the blob to deleteKey of the - // KM back end to permanently invalidate the key. - let need_gc = if let Some((blob, blob_metadata)) = superseded_blob { - Self::set_blob_internal( - tx, - key_id.id(), - SubComponentType::KEY_BLOB, - Some(blob), - Some(blob_metadata), - ) - .context("Trying to insert superseded key blob.")?; - true - } else { - false - }; - + let (blob, blob_metadata) = *blob_info; Self::set_blob_internal( tx, key_id.id(), SubComponentType::KEY_BLOB, Some(blob), - Some(blob_metadata), + Some(&blob_metadata), ) .context("Trying to insert the key blob.")?; if let Some(cert) = &cert_info.cert { - Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT, Some(cert), None) + Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT, Some(&cert), None) .context("Trying to insert the certificate.")?; } if let Some(cert_chain) = &cert_info.cert_chain { @@ -2366,7 +2271,7 @@ impl KeystoreDB { tx, key_id.id(), SubComponentType::CERT_CHAIN, - Some(cert_chain), + Some(&cert_chain), None, ) .context("Trying to insert the certificate chain.")?; @@ -2374,9 +2279,8 @@ impl KeystoreDB { Self::insert_keyparameter_internal(tx, &key_id, params) .context("Trying to insert key parameters.")?; metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?; - let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type) - .context("Trying to rebind alias.")? - || need_gc; + let need_gc = Self::rebind_alias(tx, &key_id, &alias, &domain, namespace, key_type) + .context("Trying to rebind alias.")?; Ok(key_id).do_gc(need_gc) }) .context("In store_new_key.") @@ -2425,7 +2329,7 @@ impl KeystoreDB { metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?; - let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type) + let need_gc = Self::rebind_alias(tx, &key_id, &alias, &domain, namespace, key_type) .context("Trying to rebind alias.")?; Ok(key_id).do_gc(need_gc) }) @@ -2494,7 +2398,7 @@ impl KeystoreDB { if access_key.domain == Domain::APP { access_key.nspace = caller_uid as i64; } - let key_id = Self::load_key_entry_id(tx, &access_key, key_type) + let key_id = Self::load_key_entry_id(&tx, &access_key, key_type) .with_context(|| format!("With key.domain = {:?}.", access_key.domain))?; Ok((key_id, access_key, None)) @@ -2659,7 +2563,7 @@ impl KeystoreDB { let tag = Tag(row.get(0).context("Failed to read tag.")?); let sec_level = SecurityLevel(row.get(2).context("Failed to read sec_level.")?); parameters.push( - KeyParameter::new_from_sql(tag, &SqlField::new(1, row), sec_level) + KeyParameter::new_from_sql(tag, &SqlField::new(1, &row), sec_level) .context("Failed to read KeyParameter.")?, ); Ok(()) @@ -2893,33 +2797,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() @@ -2991,6 +2895,7 @@ impl KeystoreDB { ) OR ( key_type = ? AND namespace = ? + AND alias = ? AND state = ? );", aid_user_offset = AID_USER_OFFSET @@ -3010,6 +2915,7 @@ impl KeystoreDB { // OR super key: KeyType::Super, user_id, + USER_SUPER_KEY.alias, KeyLifeCycle::Live ]) .context("In unbind_keys_for_user. Failed to query the keys created by apps.")?; @@ -3035,7 +2941,7 @@ impl KeystoreDB { } } } - notify_gc = Self::mark_unreferenced(tx, key_id) + notify_gc = Self::mark_unreferenced(&tx, key_id) .context("In unbind_keys_for_user.")? || notify_gc; } @@ -3049,15 +2955,16 @@ impl KeystoreDB { load_bits: KeyEntryLoadBits, key_id: i64, ) -> Result<KeyEntry> { - let metadata = KeyMetaData::load_from_db(key_id, tx).context("In load_key_components.")?; + let metadata = KeyMetaData::load_from_db(key_id, &tx).context("In load_key_components.")?; let (has_km_blob, key_blob_info, cert_blob, cert_chain_blob) = - Self::load_blob_components(key_id, load_bits, tx).context("In load_key_components.")?; + Self::load_blob_components(key_id, load_bits, &tx) + .context("In load_key_components.")?; - let parameters = Self::load_key_parameters(key_id, tx) + let parameters = Self::load_key_parameters(key_id, &tx) .context("In load_key_components: Trying to load key parameters.")?; - let km_uuid = Self::get_key_km_uuid(tx, key_id) + let km_uuid = Self::get_key_km_uuid(&tx, key_id) .context("In load_key_components: Trying to get KM uuid.")?; Ok(KeyEntry { @@ -3141,7 +3048,7 @@ impl KeystoreDB { // But even if we load the access tuple by grant here, the permission // check denies the attempt to create a grant by grant descriptor. let (key_id, access_key_descriptor, _) = - Self::load_access_tuple(tx, key, KeyType::Client, caller_uid) + Self::load_access_tuple(&tx, key, KeyType::Client, caller_uid) .context("In grant")?; // Perform access control. It is vital that we return here if the permission @@ -3201,7 +3108,7 @@ impl KeystoreDB { // Load the key_id and complete the access control tuple. // We ignore the access vector here because grants cannot be granted. let (key_id, access_key_descriptor, _) = - Self::load_access_tuple(tx, key, KeyType::Client, caller_uid) + Self::load_access_tuple(&tx, key, KeyType::Client, caller_uid) .context("In ungrant.")?; // Perform access control. We must return here if the permission @@ -3303,7 +3210,7 @@ impl KeystoreDB { } #[cfg(test)] -pub mod tests { +mod tests { use super::*; use crate::key_parameter::{ @@ -3312,7 +3219,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; use keystore2_test_utils::TempDir; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, @@ -3327,14 +3234,13 @@ pub mod tests { 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; #[cfg(disabled)] use std::time::Instant; - pub fn new_test_db() -> Result<KeystoreDB> { + fn new_test_db() -> Result<KeystoreDB> { let conn = KeystoreDB::make_connection("file::memory:")?; let mut db = KeystoreDB { conn, gc: None, perboot: Arc::new(perboot::PerbootDB::new()) }; @@ -3348,7 +3254,7 @@ pub mod tests { where F: Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static, { - let super_key: Arc<RwLock<SuperKeyManager>> = Default::default(); + let super_key: Arc<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)); @@ -3553,18 +3459,15 @@ pub mod tests { #[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 expiration_date: i64 = 20; 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!(true, 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); @@ -3633,9 +3536,7 @@ pub mod tests { 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; + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64 + 10000; let namespace: i64 = 30; let namespace_del1: i64 = 45; let namespace_del2: i64 = 60; @@ -3646,7 +3547,7 @@ pub mod tests { 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)?; + load_attestation_key_pool(&mut db, 60, namespace_del2, 0x03)?; let blob_entry_row_count: u32 = db .conn @@ -3661,7 +3562,7 @@ pub mod tests { 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(); + 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()); @@ -3671,13 +3572,13 @@ pub mod tests { namespace_del1, &KEYSTORE_UUID, )?; - assert!(cert_chain.is_none()); + assert!(!cert_chain.is_some()); cert_chain = db.retrieve_attestation_key_and_cert_chain( Domain::APP, namespace_del2, &KEYSTORE_UUID, )?; - assert!(cert_chain.is_none()); + assert!(!cert_chain.is_some()); // Give the garbage collector half a second to catch up. std::thread::sleep(Duration::from_millis(500)); @@ -3693,73 +3594,6 @@ pub mod tests { 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()?; @@ -3871,8 +3705,8 @@ pub mod tests { alias: Some("key".to_string()), blob: None, }; - const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo]; - const PVEC2: KeyPermSet = key_perm_set![KeyPerm::Use]; + const PVEC1: KeyPermSet = key_perm_set![KeyPerm::use_(), KeyPerm::get_info()]; + const PVEC2: KeyPermSet = key_perm_set![KeyPerm::use_()]; // Reset totally predictable random number generator in case we // are not the first test running on this thread. @@ -4348,7 +4182,7 @@ pub mod tests { }, 1, 2, - key_perm_set![KeyPerm::Use], + key_perm_set![KeyPerm::use_()], |_k, _av| Ok(()), ) .unwrap(); @@ -4358,7 +4192,7 @@ pub mod tests { let (_key_guard, key_entry) = db .load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| { assert_eq!(Domain::GRANT, k.domain); - assert!(av.unwrap().includes(KeyPerm::Use)); + assert!(av.unwrap().includes(KeyPerm::use_())); Ok(()) }) .unwrap(); @@ -4405,7 +4239,7 @@ pub mod tests { }, OWNER_UID, GRANTEE_UID, - key_perm_set![KeyPerm::Use], + key_perm_set![KeyPerm::use_()], |_k, _av| Ok(()), ) .unwrap(); @@ -4424,7 +4258,7 @@ pub mod tests { |k, av| { assert_eq!(Domain::APP, k.domain); assert_eq!(OWNER_UID as i64, k.nspace); - assert!(av.unwrap().includes(KeyPerm::Use)); + assert!(av.unwrap().includes(KeyPerm::use_())); Ok(()) }, ) @@ -4475,8 +4309,8 @@ pub mod tests { let mut db = new_test_db()?; const SOURCE_UID: u32 = 1u32; const DESTINATION_UID: u32 = 2u32; - static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; - static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; + static SOURCE_ALIAS: &str = &"SOURCE_ALIAS"; + static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS"; let key_id_guard = make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; @@ -4544,8 +4378,8 @@ pub mod tests { const SOURCE_UID: u32 = 1u32; const DESTINATION_UID: u32 = 2u32; const DESTINATION_NAMESPACE: i64 = 1000i64; - static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; - static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; + static SOURCE_ALIAS: &str = &"SOURCE_ALIAS"; + static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS"; let key_id_guard = make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; @@ -4612,8 +4446,8 @@ pub mod tests { let mut db = new_test_db()?; const SOURCE_UID: u32 = 1u32; const DESTINATION_UID: u32 = 2u32; - static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; - static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; + static SOURCE_ALIAS: &str = &"SOURCE_ALIAS"; + static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS"; let key_id_guard = make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; @@ -4645,9 +4479,9 @@ pub mod tests { #[test] fn test_upgrade_0_to_1() { - const ALIAS1: &str = "test_upgrade_0_to_1_1"; - const ALIAS2: &str = "test_upgrade_0_to_1_2"; - const ALIAS3: &str = "test_upgrade_0_to_1_3"; + const ALIAS1: &str = &"test_upgrade_0_to_1_1"; + const ALIAS2: &str = &"test_upgrade_0_to_1_2"; + const ALIAS3: &str = &"test_upgrade_0_to_1_3"; const UID: u32 = 33; let temp_dir = Arc::new(TempDir::new("test_upgrade_0_to_1").unwrap()); let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap(); @@ -5132,7 +4966,10 @@ pub mod tests { Ok(KeyEntryRow { id: row.get(0)?, key_type: row.get(1)?, - domain: row.get::<_, Option<_>>(2)?.map(Domain), + domain: match row.get(2)? { + Some(i) => Some(Domain(i)), + None => None, + }, namespace: row.get(3)?, alias: row.get(4)?, state: row.get(5)?, @@ -5623,80 +5460,6 @@ pub mod tests { } #[test] - fn test_unbind_keys_for_user_removes_superkeys() -> Result<()> { - let mut db = new_test_db()?; - let super_key = keystore2_crypto::generate_aes256_key()?; - let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); - let (encrypted_super_key, metadata) = - SuperKeyManager::encrypt_with_password(&super_key, &pw)?; - - let key_name_enc = SuperKeyType { - alias: "test_super_key_1", - algorithm: SuperEncryptionAlgorithm::Aes256Gcm, - }; - - let key_name_nonenc = SuperKeyType { - alias: "test_super_key_2", - algorithm: SuperEncryptionAlgorithm::Aes256Gcm, - }; - - // Install two super keys. - db.store_super_key( - 1, - &key_name_nonenc, - &super_key, - &BlobMetaData::new(), - &KeyMetaData::new(), - )?; - db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?; - - // Check that both can be found in the database. - assert!(db.load_super_key(&key_name_enc, 1)?.is_some()); - assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some()); - - // Install the same keys for a different user. - db.store_super_key( - 2, - &key_name_nonenc, - &super_key, - &BlobMetaData::new(), - &KeyMetaData::new(), - )?; - db.store_super_key(2, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?; - - // Check that the second pair of keys can be found in the database. - assert!(db.load_super_key(&key_name_enc, 2)?.is_some()); - assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some()); - - // Delete only encrypted keys. - db.unbind_keys_for_user(1, true)?; - - // The encrypted superkey should be gone now. - assert!(db.load_super_key(&key_name_enc, 1)?.is_none()); - assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some()); - - // Reinsert the encrypted key. - db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?; - - // Check that both can be found in the database, again.. - assert!(db.load_super_key(&key_name_enc, 1)?.is_some()); - assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some()); - - // Delete all even unencrypted keys. - db.unbind_keys_for_user(1, false)?; - - // Both should be gone now. - assert!(db.load_super_key(&key_name_enc, 1)?.is_none()); - assert!(db.load_super_key(&key_name_nonenc, 1)?.is_none()); - - // Check that the second pair of keys was untouched. - assert!(db.load_super_key(&key_name_enc, 2)?.is_some()); - assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some()); - - Ok(()) - } - - #[test] fn test_store_super_key() -> Result<()> { let mut db = new_test_db()?; let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); @@ -5715,8 +5478,8 @@ pub mod tests { &KeyMetaData::new(), )?; - // Check if super key exists. - assert!(db.key_exists(Domain::APP, 1, USER_SUPER_KEY.alias, KeyType::Super)?); + //check if super key exists + assert!(db.key_exists(Domain::APP, 1, &USER_SUPER_KEY.alias, KeyType::Super)?); let (_, key_entry) = db.load_super_key(&USER_SUPER_KEY, 1)?.unwrap(); let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry( @@ -5726,9 +5489,9 @@ pub mod tests { None, )?; - let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?; + let decrypted_secret_bytes = + loaded_super_key.aes_gcm_decrypt(&encrypted_secret, &iv, &tag)?; assert_eq!(secret_bytes, &*decrypted_secret_bytes); - Ok(()) } @@ -5822,7 +5585,7 @@ pub mod tests { && updated_stats[&k].unused_size == baseline[&k].unused_size, "updated_stats:\n{}\nbaseline:\n{}", stringify(&updated_stats), - stringify(baseline) + stringify(&baseline) ); } } @@ -5916,7 +5679,7 @@ pub mod tests { }, OWNER as u32, 123, - key_perm_set![KeyPerm::Use], + key_perm_set![KeyPerm::use_()], |_, _| Ok(()), )?; diff --git a/keystore2/src/database/utils.rs b/keystore2/src/database/utils.rs index b4590dae..90f56160 100644 --- a/keystore2/src/database/utils.rs +++ b/keystore2/src/database/utils.rs @@ -44,7 +44,7 @@ where loop { match rows.next().context("In with_rows_extract_all: Failed to unpack row")? { Some(row) => { - row_extractor(row).context("In with_rows_extract_all.")?; + row_extractor(&row).context("In with_rows_extract_all.")?; } None => break Ok(()), } diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs index cb6a2667..29a3f0b0 100644 --- a/keystore2/src/enforcements.rs +++ b/keystore2/src/enforcements.rs @@ -28,13 +28,14 @@ use android_hardware_security_keymint::aidl::android::hardware::security::keymin KeyParameter::KeyParameter as KmKeyParameter, KeyPurpose::KeyPurpose, Tag::Tag, }; use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ - TimeStampToken::TimeStampToken, + ISecureClock::ISecureClock, TimeStampToken::TimeStampToken, }; use android_security_authorization::aidl::android::security::authorization::ResponseCode::ResponseCode as AuthzResponseCode; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, IKeystoreSecurityLevel::KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING, OperationChallenge::OperationChallenge, }; +use android_system_keystore2::binder::Strong; use anyhow::{Context, Result}; use std::{ collections::{HashMap, HashSet}, @@ -218,10 +219,13 @@ impl TokenReceiver { } fn get_timestamp_token(challenge: i64) -> Result<TimeStampToken, Error> { - let dev = get_timestamp_service().expect(concat!( - "Secure Clock service must be present ", - "if TimeStampTokens are required." - )); + let dev: Strong<dyn ISecureClock> = get_timestamp_service() + .expect(concat!( + "Secure Clock service must be present ", + "if TimeStampTokens are required." + )) + .get_interface() + .expect("Fatal: Timestamp service does not implement ISecureClock."); map_binder_status(dev.generateTimeStamp(challenge)) } @@ -450,7 +454,7 @@ impl Enforcements { KeyParameterValue::Algorithm(Algorithm::RSA) | KeyParameterValue::Algorithm(Algorithm::EC) => { return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)).context( - "In authorize_create: public operations on asymmetric keys are not \ + "In authorize_create: public operations on asymmetric keys are not supported.", ); } @@ -566,7 +570,8 @@ impl Enforcements { // if both NO_AUTH_REQUIRED and USER_SECURE_ID tags are present, return error if !user_secure_ids.is_empty() && no_auth_required { return Err(Error::Km(Ec::INVALID_KEY_BLOB)).context( - "In authorize_create: key has both NO_AUTH_REQUIRED and USER_SECURE_ID tags.", + "In authorize_create: key has both NO_AUTH_REQUIRED + and USER_SECURE_ID tags.", ); } @@ -575,8 +580,8 @@ impl Enforcements { || (user_auth_type.is_none() && !user_secure_ids.is_empty()) { return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context( - "In authorize_create: Auth required, but either auth type or secure ids \ - are not present.", + "In authorize_create: Auth required, but either auth type or secure ids + are not present.", ); } @@ -586,7 +591,8 @@ impl Enforcements { && op_params.iter().any(|kp| kp.tag == Tag::NONCE) { return Err(Error::Km(Ec::CALLER_NONCE_PROHIBITED)).context( - "In authorize_create, NONCE is present, although CALLER_NONCE is not present", + "In authorize_create, NONCE is present, + although CALLER_NONCE is not present", ); } @@ -600,7 +606,7 @@ impl Enforcements { } if let Some(level) = max_boot_level { - if !SUPER_KEY.read().unwrap().level_accessible(level) { + if !SUPER_KEY.level_accessible(level) { return Err(Error::Km(Ec::BOOT_LEVEL_EXCEEDED)) .context("In authorize_create: boot level is too late."); } @@ -627,7 +633,7 @@ impl Enforcements { let hat_and_last_off_body = if need_auth_token { let hat_and_last_off_body = Self::find_auth_token(|hat: &AuthTokenEntry| { - if let (Some(auth_type), true) = (user_auth_type, timeout_bound) { + if let (Some(auth_type), true) = (user_auth_type, has_sids) { hat.satisfies(&user_secure_ids, auth_type) } else { unlocked_device_required @@ -835,12 +841,8 @@ impl Enforcements { .context("In get_auth_tokens: No auth token found."); } } else { - return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND)).context( - concat!( - "In get_auth_tokens: No auth token found for ", - "the given challenge and passed-in auth token max age is zero." - ), - ); + return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND)) + .context("In get_auth_tokens: Passed-in auth token max age is zero."); } }; // Wait and obtain the timestamp token from secure clock service. diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs index f34c5daa..f969cb6c 100644 --- a/keystore2/src/error.rs +++ b/keystore2/src/error.rs @@ -37,7 +37,6 @@ use android_system_keystore2::binder::{ }; use keystore2_selinux as selinux; use std::cmp::PartialEq; -use std::ffi::CString; /// This is the main Keystore error type. It wraps the Keystore `ResponseCode` generated /// from AIDL in the `Rc` variant and Keymint `ErrorCode` in the Km variant. @@ -67,15 +66,10 @@ impl Error { Error::Rc(ResponseCode::SYSTEM_ERROR) } - /// Short hand for `Error::Rc(ResponseCode::PERMISSION_DENIED)` + /// Short hand for `Error::Rc(ResponseCode::PERMISSION_DENIED` pub fn perm() -> Self { Error::Rc(ResponseCode::PERMISSION_DENIED) } - - /// Short hand for `Error::Rc(ResponseCode::OUT_OF_KEYS)` - pub fn out_of_keys() -> Self { - Error::Rc(ResponseCode::OUT_OF_KEYS) - } } /// Helper function to map the binder status we get from calls into KeyMint @@ -190,20 +184,6 @@ where ) } -/// This function turns an anyhow error into an optional CString. -/// This is especially useful to add a message string to a service specific error. -/// If the formatted string was not convertible because it contained a nul byte, -/// None is returned and a warning is logged. -pub fn anyhow_error_to_cstring(e: &anyhow::Error) -> Option<CString> { - match CString::new(format!("{:?}", e)) { - Ok(msg) => Some(msg), - Err(_) => { - log::warn!("Cannot convert error message to CStr. It contained a nul byte."); - None - } - } -} - /// This function behaves similar to map_or_log_error, but it does not log the errors, instead /// it calls map_err on the error before mapping it to a binder result allowing callers to /// log or transform the error before mapping it. @@ -220,10 +200,7 @@ where |e| { let e = map_err(e); let rc = get_error_code(&e); - Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )) + Err(BinderStatus::new_service_specific_error(rc, None)) }, handle_ok, ) diff --git a/keystore2/src/fuzzers/Android.bp b/keystore2/src/fuzzers/Android.bp deleted file mode 100644 index 384ab77f..00000000 --- a/keystore2/src/fuzzers/Android.bp +++ /dev/null @@ -1,29 +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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -rust_fuzz { - name: "legacy_blob_fuzzer", - srcs: ["legacy_blob_fuzzer.rs"], - rustlibs: [ - "libkeystore2", - ], - fuzz_config: { - fuzz_on_haiku_device: true, - fuzz_on_haiku_host: false, - }, -} diff --git a/keystore2/src/fuzzers/legacy_blob_fuzzer.rs b/keystore2/src/fuzzers/legacy_blob_fuzzer.rs deleted file mode 100644 index 7e3e848c..00000000 --- a/keystore2/src/fuzzers/legacy_blob_fuzzer.rs +++ /dev/null @@ -1,26 +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. - -#![allow(missing_docs)] -#![no_main] -#[macro_use] -extern crate libfuzzer_sys; -use keystore2::legacy_blob::LegacyBlobLoader; - -fuzz_target!(|data: &[u8]| { - if !data.is_empty() { - let string = data.iter().filter_map(|c| std::char::from_u32(*c as u32)).collect::<String>(); - let _res = LegacyBlobLoader::decode_alias(&string); - } -}); diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs index 341aa0a6..2010c796 100644 --- a/keystore2/src/gc.rs +++ b/keystore2/src/gc.rs @@ -27,7 +27,7 @@ use anyhow::{Context, Result}; use async_task::AsyncTask; use std::sync::{ atomic::{AtomicU8, Ordering}, - Arc, RwLock, + Arc, }; pub struct Gc { @@ -47,7 +47,7 @@ impl Gc { F: FnOnce() -> ( Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, KeystoreDB, - Arc<RwLock<SuperKeyManager>>, + Arc<SuperKeyManager>, ) + Send + 'static, { @@ -87,7 +87,7 @@ struct GcInternal { invalidate_key: Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, db: KeystoreDB, async_task: std::sync::Weak<AsyncTask>, - super_key: Arc<RwLock<SuperKeyManager>>, + super_key: Arc<SuperKeyManager>, notified: Arc<AtomicU8>, } @@ -121,11 +121,9 @@ impl GcInternal { if let Some(uuid) = blob_metadata.km_uuid() { let blob = self .super_key - .read() - .unwrap() .unwrap_key_if_required(&blob_metadata, &blob) .context("In process_one_key: Trying to unwrap to-be-deleted blob.")?; - (self.invalidate_key)(uuid, &*blob) + (self.invalidate_key)(&uuid, &*blob) .context("In process_one_key: Trying to invalidate key.")?; } } diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs index 14b36010..8212213e 100644 --- a/keystore2/src/globals.rs +++ b/keystore2/src/globals.rs @@ -18,24 +18,21 @@ use crate::gc::Gc; use crate::legacy_blob::LegacyBlobLoader; -use crate::legacy_importer::LegacyImporter; +use crate::legacy_migrator::LegacyMigrator; use crate::super_key::SuperKeyManager; use crate::utils::watchdog as wd; +use crate::utils::Asp; 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, IRemotelyProvisionedComponent::IRemotelyProvisionedComponent, KeyMintHardwareInfo::KeyMintHardwareInfo, SecurityLevel::SecurityLevel, }; -use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ - ISecureClock::ISecureClock, -}; use android_hardware_security_keymint::binder::{StatusCode, Strong}; use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; use anyhow::{Context, Result}; @@ -88,33 +85,34 @@ thread_local! { RefCell::new(create_thread_local_db()); } -struct DevicesMap<T: FromIBinder + ?Sized> { - devices_by_uuid: HashMap<Uuid, (Strong<T>, KeyMintHardwareInfo)>, +#[derive(Default)] +struct DevicesMap { + devices_by_uuid: HashMap<Uuid, (Asp, KeyMintHardwareInfo)>, uuid_by_sec_level: HashMap<SecurityLevel, Uuid>, } -impl<T: FromIBinder + ?Sized> DevicesMap<T> { +impl DevicesMap { fn dev_by_sec_level( &self, sec_level: &SecurityLevel, - ) -> Option<(Strong<T>, KeyMintHardwareInfo, Uuid)> { + ) -> Option<(Asp, KeyMintHardwareInfo, Uuid)> { self.uuid_by_sec_level.get(sec_level).and_then(|uuid| self.dev_by_uuid(uuid)) } - fn dev_by_uuid(&self, uuid: &Uuid) -> Option<(Strong<T>, KeyMintHardwareInfo, Uuid)> { + fn dev_by_uuid(&self, uuid: &Uuid) -> Option<(Asp, KeyMintHardwareInfo, Uuid)> { self.devices_by_uuid .get(uuid) .map(|(dev, hw_info)| ((*dev).clone(), (*hw_info).clone(), *uuid)) } - fn devices(&self) -> Vec<Strong<T>> { - self.devices_by_uuid.values().map(|(dev, _)| dev.clone()).collect() + fn devices<T: FromIBinder + ?Sized>(&self) -> Vec<Strong<T>> { + self.devices_by_uuid.values().filter_map(|(asp, _)| asp.get_interface::<T>().ok()).collect() } /// The requested security level and the security level of the actual implementation may /// differ. So we map the requested security level to the uuid of the implementation /// so that there cannot be any confusion as to which KeyMint instance is requested. - fn insert(&mut self, sec_level: SecurityLevel, dev: Strong<T>, hw_info: KeyMintHardwareInfo) { + fn insert(&mut self, sec_level: SecurityLevel, dev: Asp, hw_info: KeyMintHardwareInfo) { // For now we use the reported security level of the KM instance as UUID. // TODO update this section once UUID was added to the KM hardware info. let uuid: Uuid = sec_level.into(); @@ -123,31 +121,17 @@ impl<T: FromIBinder + ?Sized> DevicesMap<T> { } } -impl<T: FromIBinder + ?Sized> Default for DevicesMap<T> { - fn default() -> Self { - Self { - devices_by_uuid: HashMap::<Uuid, (Strong<T>, KeyMintHardwareInfo)>::new(), - uuid_by_sec_level: Default::default(), - } - } -} - -struct RemotelyProvisionedDevicesMap<T: FromIBinder + ?Sized> { - devices_by_sec_level: HashMap<SecurityLevel, Strong<T>>, +#[derive(Default)] +struct RemotelyProvisionedDevicesMap { + devices_by_sec_level: HashMap<SecurityLevel, Asp>, } -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>> { +impl RemotelyProvisionedDevicesMap { + fn dev_by_sec_level(&self, sec_level: &SecurityLevel) -> Option<Asp> { self.devices_by_sec_level.get(sec_level).map(|dev| (*dev).clone()) } - fn insert(&mut self, sec_level: SecurityLevel, dev: Strong<T>) { + fn insert(&mut self, sec_level: SecurityLevel, dev: Asp) { self.devices_by_sec_level.insert(sec_level, dev); } } @@ -157,15 +141,13 @@ lazy_static! { pub static ref DB_PATH: RwLock<PathBuf> = RwLock::new( Path::new("/data/misc/keystore").to_path_buf()); /// Runtime database of unwrapped super keys. - pub static ref SUPER_KEY: Arc<RwLock<SuperKeyManager>> = Default::default(); + pub static ref SUPER_KEY: Arc<SuperKeyManager> = Default::default(); /// Map of KeyMint devices. - static ref KEY_MINT_DEVICES: Mutex<DevicesMap<dyn IKeyMintDevice>> = Default::default(); + static ref KEY_MINT_DEVICES: Mutex<DevicesMap> = Default::default(); /// Timestamp service. - static ref TIME_STAMP_DEVICE: Mutex<Option<Strong<dyn ISecureClock>>> = Default::default(); + static ref TIME_STAMP_DEVICE: Mutex<Option<Asp>> = Default::default(); /// RemotelyProvisionedComponent HAL devices. - static ref REMOTELY_PROVISIONED_COMPONENT_DEVICES: - Mutex<RemotelyProvisionedDevicesMap<dyn IRemotelyProvisionedComponent>> = - Default::default(); + static ref REMOTELY_PROVISIONED_COMPONENT_DEVICES: Mutex<RemotelyProvisionedDevicesMap> = 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(); @@ -176,15 +158,16 @@ lazy_static! { pub static ref LEGACY_BLOB_LOADER: Arc<LegacyBlobLoader> = Arc::new(LegacyBlobLoader::new( &DB_PATH.read().expect("Could not get the database path for legacy blob loader."))); /// Legacy migrator. Atomically migrates legacy blobs to the database. - pub static ref LEGACY_IMPORTER: Arc<LegacyImporter> = - Arc::new(LegacyImporter::new(Arc::new(Default::default()))); + pub static ref LEGACY_MIGRATOR: Arc<LegacyMigrator> = + Arc::new(LegacyMigrator::new(Arc::new(Default::default()))); /// Background thread which handles logging via statsd and logd pub static ref LOGS_HANDLER: Arc<AsyncTask> = Default::default(); static ref GC: Arc<Gc> = Arc::new(Gc::new_init_with(ASYNC_TASK.clone(), || { ( Box::new(|uuid, blob| { - let km_dev = get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?; + let km_dev: Strong<dyn IKeyMintDevice> = + get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?.get_interface()?; let _wp = wd::watch_millis("In invalidate key closure: calling deleteKey", 500); map_km_error(km_dev.deleteKey(&*blob)) .context("In invalidate key closure: Trying to invalidate key blob.") @@ -198,70 +181,40 @@ 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)>> { +/// Make a new connection to a KeyMint device of the given security level. +/// If no native KeyMint device can be found this function also brings +/// up the compatibility service and attempts to connect to the legacy wrapper. +fn connect_keymint(security_level: &SecurityLevel) -> Result<(Asp, KeyMintHardwareInfo)> { let keymint_instances = - get_aidl_instances("android.hardware.security.keymint", version as usize, "IKeyMintDevice"); + get_aidl_instances("android.hardware.security.keymint", 1, "IKeyMintDevice"); let service_name = match *security_level { SecurityLevel::TRUSTED_ENVIRONMENT => { - if keymint_instances.iter().any(|instance| *instance == "default") { + if keymint_instances.as_vec()?.iter().any(|instance| *instance == "default") { Some(format!("{}/default", KEYMINT_SERVICE_NAME)) } else { None } } SecurityLevel::STRONGBOX => { - if keymint_instances.iter().any(|instance| *instance == "strongbox") { + if keymint_instances.as_vec()?.iter().any(|instance| *instance == "strongbox") { Some(format!("{}/strongbox", KEYMINT_SERVICE_NAME)) } else { None } } _ => { - return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(format!( - "In keymint_service_name_by_version: Trying to find keymint V{} for security level: {:?}", - version, security_level - )); + return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + .context("In connect_keymint.") } }; - Ok(service_name.map(|service_name| (version, service_name))) -} - -/// Make a new connection to a KeyMint device of the given security level. -/// If no native KeyMint device can be found this function also brings -/// up the compatibility service and attempts to connect to the legacy wrapper. -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("In connect_keymint.")?; - - let (keymint, hal_version) = if let Some((version, service_name)) = service_name { - let km: Strong<dyn IKeyMintDevice> = + let (keymint, hal_version) = if let Some(service_name) = service_name { + ( map_binder_status_code(binder::get_interface(&service_name)) - .context("In connect_keymint: 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 - // etc. - (km, Some(version * 100)) + .context("In connect_keymint: Trying to connect to genuine KeyMint service.")?, + Some(100i32), // The HAL version code for KeyMint V1 is 100. + ) } else { // This is a no-op if it was called before. keystore2_km_compat::add_keymint_device_service(); @@ -282,48 +235,6 @@ 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(200) => { - // Current KeyMint version: use as-is. - log::info!( - "KeyMint device is current version ({:?}) for security level: {:?}", - hal_version, - security_level - ); - keymint - } - Some(100) => { - // KeyMint v1: perform software emulation. - log::info!( - "Add emulation wrapper around {:?} device for security level: {:?}", - hal_version, - security_level - ); - BacklevelKeyMintWrapper::wrap(KeyMintV1::new(*security_level), keymint) - .context("In connect_keymint: Trying to create V1 compatibility wrapper.")? - } - None => { - // Compatibility wrapper around a KeyMaster device: this roughly - // behaves like KeyMint V1 (e.g. it includes AGREE_KEY support, - // albeit in software.) - log::info!( - "Add emulation wrapper around Keymaster device for security level: {:?}", - security_level - ); - BacklevelKeyMintWrapper::wrap(KeyMintV1::new(*security_level), keymint).context( - "In connect_keymint: Trying to create km_compat V1 compatibility wrapper .", - )? - } - _ => { - return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(format!( - "In connect_keymint: unexpected hal_version {:?} for security level: {:?}", - hal_version, security_level - )) - } - }; - let wp = wd::watch_millis("In connect_keymint: calling getHardwareInfo()", 500); let mut hw_info = map_km_error(keymint.getHardwareInfo()) .context("In connect_keymint: Failed to get hardware info.")?; @@ -331,19 +242,16 @@ fn connect_keymint( // The legacy wrapper sets hw_info.versionNumber to the underlying HAL version like so: // 10 * <major> + <minor>, e.g., KM 3.0 = 30. So 30, 40, and 41 are the only viable values. - // - // For KeyMint the returned versionNumber is implementation defined and thus completely - // meaningless to Keystore 2.0. So set the versionNumber field that is returned to - // the rest of the code to be the <AIDL version> * 100, so KeyMint V1 is 100, KeyMint V2 is 200 - // and so on. - // - // This ensures that versionNumber value across KeyMaster and KeyMint is monotonically - // increasing (and so comparisons like `versionNumber >= KEY_MINT_1` are valid). + // For KeyMint the versionNumber is implementation defined and thus completely meaningless + // to Keystore 2.0. So at this point the versionNumber field is set to the HAL version, so + // that higher levels have a meaningful guide as to which feature set to expect from the + // implementation. As of this writing the only meaningful version number is 100 for KeyMint V1, + // and future AIDL versions should follow the pattern <AIDL version> * 100. if let Some(hal_version) = hal_version { hw_info.versionNumber = hal_version; } - Ok((keymint, hw_info)) + Ok((Asp::new(keymint.as_binder()), hw_info)) } /// Get a keymint device for the given security level either from our cache or @@ -351,9 +259,9 @@ fn connect_keymint( /// TODO the latter can be removed when the uuid is part of the hardware info. pub fn get_keymint_device( security_level: &SecurityLevel, -) -> Result<(Strong<dyn IKeyMintDevice>, KeyMintHardwareInfo, Uuid)> { +) -> Result<(Asp, KeyMintHardwareInfo, Uuid)> { let mut devices_map = KEY_MINT_DEVICES.lock().unwrap(); - if let Some((dev, hw_info, uuid)) = devices_map.dev_by_sec_level(security_level) { + 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("In get_keymint_device.")?; @@ -367,9 +275,7 @@ pub fn get_keymint_device( /// attempt to establish a new connection. It is assumed that the cache is already populated /// when this is called. This is a fair assumption, because service.rs iterates through all /// security levels when it gets instantiated. -pub fn get_keymint_dev_by_uuid( - uuid: &Uuid, -) -> Result<(Strong<dyn IKeyMintDevice>, KeyMintHardwareInfo)> { +pub fn get_keymint_dev_by_uuid(uuid: &Uuid) -> Result<(Asp, KeyMintHardwareInfo)> { let devices_map = KEY_MINT_DEVICES.lock().unwrap(); if let Some((dev, hw_info, _)) = devices_map.dev_by_uuid(uuid) { Ok((dev, hw_info)) @@ -388,12 +294,12 @@ static TIME_STAMP_SERVICE_NAME: &str = "android.hardware.security.secureclock.IS /// 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>> { +fn connect_secureclock() -> Result<Asp> { let secureclock_instances = get_aidl_instances("android.hardware.security.secureclock", 1, "ISecureClock"); let secure_clock_available = - secureclock_instances.iter().any(|instance| *instance == "default"); + secureclock_instances.as_vec()?.iter().any(|instance| *instance == "default"); let default_time_stamp_service_name = format!("{}/default", TIME_STAMP_SERVICE_NAME); @@ -419,12 +325,12 @@ fn connect_secureclock() -> Result<Strong<dyn ISecureClock>> { .context("In connect_secureclock: Trying to get Legacy wrapper.") }?; - Ok(secureclock) + Ok(Asp::new(secureclock.as_binder())) } /// Get the timestamp service that verifies auth token timeliness towards security levels with /// different clocks. -pub fn get_timestamp_service() -> Result<Strong<dyn ISecureClock>> { +pub fn get_timestamp_service() -> Result<Asp> { let mut ts_device = TIME_STAMP_DEVICE.lock().unwrap(); if let Some(dev) = &*ts_device { Ok(dev.clone()) @@ -438,22 +344,20 @@ pub fn get_timestamp_service() -> Result<Strong<dyn ISecureClock>> { static REMOTE_PROVISIONING_HAL_SERVICE_NAME: &str = "android.hardware.security.keymint.IRemotelyProvisionedComponent"; -fn connect_remotely_provisioned_component( - security_level: &SecurityLevel, -) -> Result<Strong<dyn IRemotelyProvisionedComponent>> { +fn connect_remotely_provisioned_component(security_level: &SecurityLevel) -> Result<Asp> { let remotely_prov_instances = get_aidl_instances("android.hardware.security.keymint", 1, "IRemotelyProvisionedComponent"); let service_name = match *security_level { SecurityLevel::TRUSTED_ENVIRONMENT => { - if remotely_prov_instances.iter().any(|instance| *instance == "default") { + if remotely_prov_instances.as_vec()?.iter().any(|instance| *instance == "default") { Some(format!("{}/default", REMOTE_PROVISIONING_HAL_SERVICE_NAME)) } else { None } } SecurityLevel::STRONGBOX => { - if remotely_prov_instances.iter().any(|instance| *instance == "strongbox") { + if remotely_prov_instances.as_vec()?.iter().any(|instance| *instance == "strongbox") { Some(format!("{}/strongbox", REMOTE_PROVISIONING_HAL_SERVICE_NAME)) } else { None @@ -471,16 +375,14 @@ fn connect_remotely_provisioned_component( " RemotelyProvisionedComponent service." )) .map_err(|e| e)?; - Ok(rem_prov_hal) + Ok(Asp::new(rem_prov_hal.as_binder())) } /// 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>> { +pub fn get_remotely_provisioned_component(security_level: &SecurityLevel) -> Result<Asp> { let mut devices_map = REMOTELY_PROVISIONED_COMPONENT_DEVICES.lock().unwrap(); - if let Some(dev) = devices_map.dev_by_sec_level(security_level) { + if let Some(dev) = devices_map.dev_by_sec_level(&security_level) { Ok(dev) } else { let dev = connect_remotely_provisioned_component(security_level) diff --git a/keystore2/src/id_rotation.rs b/keystore2/src/id_rotation.rs index e3992d89..dbf0fc9b 100644 --- a/keystore2/src/id_rotation.rs +++ b/keystore2/src/id_rotation.rs @@ -27,7 +27,7 @@ use std::path::{Path, PathBuf}; use std::time::Duration; const ID_ROTATION_PERIOD: Duration = Duration::from_secs(30 * 24 * 60 * 60); // Thirty days. -static TIMESTAMP_FILE_NAME: &str = "timestamp"; +static TIMESTAMP_FILE_NAME: &str = &"timestamp"; /// The IdRotationState stores the path to the timestamp file for deferred usage. The data /// partition is usually not available when Keystore 2.0 starts up. So this object is created @@ -83,7 +83,7 @@ mod 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."); - let id_rotation_state = IdRotationState::new(temp_dir.path()); + 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); diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs index 9854974d..771d609c 100644 --- a/keystore2/src/key_parameter.rs +++ b/keystore2/src/key_parameter.rs @@ -107,9 +107,6 @@ use android_system_keystore2::aidl::android::system::keystore2::Authorization::A use anyhow::{Context, Result}; use rusqlite::types::{Null, ToSql, ToSqlOutput}; use rusqlite::Result as SqlResult; -use serde::de::Deserializer; -use serde::ser::Serializer; -use serde::{Deserialize, Serialize}; /// This trait is used to associate a primitive to any type that can be stored inside a /// KeyParameterValue, especially the AIDL enum types, e.g., keymint::{Algorithm, Digest, ...}. @@ -124,7 +121,7 @@ use serde::{Deserialize, Serialize}; /// there is no wrapped type): /// `KeyParameterValue::$vname(<$vtype>::from_primitive(row.get(0)))` trait AssociatePrimitive { - type Primitive: Into<Primitive> + TryFrom<Primitive>; + type Primitive; fn from_primitive(v: Self::Primitive) -> Self; fn to_primitive(&self) -> Self::Primitive; @@ -180,7 +177,6 @@ implement_associate_primitive_identity! {i32} /// This enum allows passing a primitive value to `KeyParameterValue::new_from_tag_primitive_pair` /// Usually, it is not necessary to use this type directly because the function uses /// `Into<Primitive>` as a trait bound. -#[derive(Deserialize, Serialize)] pub enum Primitive { /// Wraps an i64. I64(i64), @@ -217,57 +213,37 @@ pub enum PrimitiveError { UnknownTag, } -impl TryFrom<Primitive> for i64 { +impl TryInto<i64> for Primitive { type Error = PrimitiveError; - fn try_from(p: Primitive) -> Result<i64, Self::Error> { - match p { - Primitive::I64(v) => Ok(v), + fn try_into(self) -> Result<i64, Self::Error> { + match self { + Self::I64(v) => Ok(v), _ => Err(Self::Error::TypeMismatch), } } } -impl TryFrom<Primitive> for i32 { +impl TryInto<i32> for Primitive { type Error = PrimitiveError; - fn try_from(p: Primitive) -> Result<i32, Self::Error> { - match p { - Primitive::I32(v) => Ok(v), + fn try_into(self) -> Result<i32, Self::Error> { + match self { + Self::I32(v) => Ok(v), _ => Err(Self::Error::TypeMismatch), } } } -impl TryFrom<Primitive> for Vec<u8> { +impl TryInto<Vec<u8>> for Primitive { type Error = PrimitiveError; - fn try_from(p: Primitive) -> Result<Vec<u8>, Self::Error> { - match p { - Primitive::Vec(v) => Ok(v), + fn try_into(self) -> Result<Vec<u8>, Self::Error> { + match self { + Self::Vec(v) => Ok(v), _ => Err(Self::Error::TypeMismatch), } } } -fn serialize_primitive<S, P>(v: &P, serializer: S) -> Result<S::Ok, S::Error> -where - S: Serializer, - P: AssociatePrimitive, -{ - let primitive: Primitive = v.to_primitive().into(); - primitive.serialize(serializer) -} - -fn deserialize_primitive<'de, D, T>(deserializer: D) -> Result<T, D::Error> -where - D: Deserializer<'de>, - T: AssociatePrimitive, -{ - let primitive: Primitive = serde::de::Deserialize::deserialize(deserializer)?; - Ok(T::from_primitive( - primitive.try_into().map_err(|_| serde::de::Error::custom("Type Mismatch"))?, - )) -} - /// Expands the list of KeyParameterValue variants as follows: /// /// Input: @@ -787,14 +763,6 @@ macro_rules! implement_key_parameter_value { value: KmKeyParameterValue::$field_name(Default::default())} ),*] } - - #[cfg(test)] - fn make_key_parameter_defaults_vector() -> Vec<KeyParameter> { - vec![$(KeyParameter{ - value: KeyParameterValue::$vname$((<$vtype as Default>::default()))?, - security_level: SecurityLevel(100), - }),*] - } } implement_try_from_to_km_parameter!( @@ -809,37 +777,27 @@ macro_rules! implement_key_parameter_value { implement_key_parameter_value! { /// KeyParameterValue holds a value corresponding to one of the Tags defined in /// the AIDL spec at hardware/interfaces/security/keymint -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum KeyParameterValue { /// Associated with Tag:INVALID #[key_param(tag = INVALID, field = Invalid)] Invalid, /// Set of purposes for which the key may be used - #[serde(deserialize_with = "deserialize_primitive")] - #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = PURPOSE, field = KeyPurpose)] KeyPurpose(KeyPurpose), /// Cryptographic algorithm with which the key is used - #[serde(deserialize_with = "deserialize_primitive")] - #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = ALGORITHM, field = Algorithm)] Algorithm(Algorithm), /// Size of the key , in bits #[key_param(tag = KEY_SIZE, field = Integer)] KeySize(i32), /// Block cipher mode(s) with which the key may be used - #[serde(deserialize_with = "deserialize_primitive")] - #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = BLOCK_MODE, field = BlockMode)] BlockMode(BlockMode), /// Digest algorithms that may be used with the key to perform signing and verification - #[serde(deserialize_with = "deserialize_primitive")] - #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = DIGEST, field = Digest)] Digest(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")] #[key_param(tag = PADDING, field = PaddingMode)] PaddingMode(PaddingMode), /// Can the caller provide a nonce for nonce-requiring operations @@ -849,8 +807,6 @@ pub enum KeyParameterValue { #[key_param(tag = MIN_MAC_LENGTH, field = Integer)] MinMacLength(i32), /// The elliptic curve - #[serde(deserialize_with = "deserialize_primitive")] - #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = EC_CURVE, field = EcCurve)] EcCurve(EcCurve), /// Value of the public exponent for an RSA key pair @@ -900,8 +856,6 @@ pub enum KeyParameterValue { #[key_param(tag = NO_AUTH_REQUIRED, field = BoolValue)] NoAuthRequired, /// The types of user authenticators that may be used to authorize this key - #[serde(deserialize_with = "deserialize_primitive")] - #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = USER_AUTH_TYPE, field = HardwareAuthenticatorType)] HardwareAuthenticatorType(HardwareAuthenticatorType), /// The time in seconds for which the key is authorized for use, after user authentication @@ -932,8 +886,6 @@ pub enum KeyParameterValue { #[key_param(tag = CREATION_DATETIME, field = DateTime)] CreationDateTime(i64), /// Specifies where the key was created, if known - #[serde(deserialize_with = "deserialize_primitive")] - #[serde(serialize_with = "serialize_primitive")] #[key_param(tag = ORIGIN, field = Origin)] KeyOrigin(KeyOrigin), /// The key used by verified boot to validate the operating system booted @@ -1029,11 +981,9 @@ impl From<&KmKeyParameter> for KeyParameterValue { } /// KeyParameter wraps the KeyParameterValue and the security level at which it is enforced. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub struct KeyParameter { value: KeyParameterValue, - #[serde(deserialize_with = "deserialize_primitive")] - #[serde(serialize_with = "serialize_primitive")] security_level: SecurityLevel, } @@ -1156,18 +1106,6 @@ mod generated_key_parameter_tests { fn key_parameter_value_field_matches_tag_type() { check_field_matches_tag_type(&KeyParameterValue::make_field_matches_tag_type_test_vector()); } - - #[test] - fn key_parameter_serialization_test() { - let params = KeyParameterValue::make_key_parameter_defaults_vector(); - let mut out_buffer: Vec<u8> = Default::default(); - serde_cbor::to_writer(&mut out_buffer, ¶ms) - .expect("Failed to serialize key parameters."); - let deserialized_params: Vec<KeyParameter> = - serde_cbor::from_reader(&mut out_buffer.as_slice()) - .expect("Failed to deserialize key parameters."); - assert_eq!(params, deserialized_params); - } } #[cfg(test)] diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs index 55f5d152..cf2ba043 100644 --- a/keystore2/src/keystore2_main.rs +++ b/keystore2/src/keystore2_main.rs @@ -19,24 +19,19 @@ use keystore2::globals::ENFORCEMENTS; use keystore2::maintenance::Maintenance; use keystore2::metrics::Metrics; use keystore2::metrics_store; -use keystore2::remote_provisioning::{ - RemoteProvisioningService, RemotelyProvisionedKeyPoolService, -}; +use keystore2::remote_provisioning::RemoteProvisioningService; use keystore2::service::KeystoreService; use keystore2::{apc::ApcManager, shared_secret_negotiation}; use keystore2::{authorization::AuthorizationManager, id_rotation::IdRotationState}; use legacykeystore::LegacyKeystore; use log::{error, info}; -use rusqlite::trace as sqlite_trace; -use std::{os::raw::c_int, panic, path::Path, sync::mpsc::channel}; +use std::{panic, path::Path, sync::mpsc::channel}; static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default"; 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"; @@ -44,10 +39,7 @@ static LEGACY_KEYSTORE_SERVICE_NAME: &str = "android.security.legacykeystore"; fn main() { // Initialize android logging. android_logger::init_once( - android_logger::Config::default() - .with_tag("keystore2") - .with_min_level(log::Level::Debug) - .with_log_id(android_logger::LogId::System), + android_logger::Config::default().with_tag("keystore2").with_min_level(log::Level::Debug), ); // Redirect panic messages to logcat. panic::set_hook(Box::new(|panic_info| { @@ -60,14 +52,6 @@ fn main() { let mut args = std::env::args(); args.next().expect("That's odd. How is there not even a first argument?"); - // This must happen early before any other sqlite operations. - log::info!("Setting up sqlite logging for keystore2"); - fn sqlite_log_handler(err: c_int, message: &str) { - log::error!("[SQLITE3] {}: {}", err, message); - } - unsafe { sqlite_trace::config_log(Some(sqlite_log_handler)) } - .expect("Error setting sqlite log callback."); - // Write/update keystore.crash_count system property. metrics_store::update_keystore_crash_sysprop(); @@ -79,7 +63,7 @@ fn main() { let db_path = Path::new(&dir); *keystore2::globals::DB_PATH.write().expect("Could not lock DB_PATH.") = db_path.to_path_buf(); - IdRotationState::new(db_path) + IdRotationState::new(&db_path) } else { panic!("Must specify a database directory."); }; @@ -152,25 +136,6 @@ fn main() { }); } - // 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 deleted file mode 100644 index 788beefe..00000000 --- a/keystore2/src/km_compat.rs +++ /dev/null @@ -1,588 +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. - -//! Provide a wrapper around a KeyMint device that allows up-level features to -//! be emulated on back-level devices. - -use crate::error::{map_binder_status, map_binder_status_code, map_or_log_err, Error, ErrorCode}; -use android_hardware_security_keymint::binder::{BinderFeatures, StatusCode, Strong}; -use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::TimeStampToken::TimeStampToken; -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - AttestationKey::AttestationKey, BeginResult::BeginResult, EcCurve::EcCurve, - HardwareAuthToken::HardwareAuthToken, IKeyMintDevice::BnKeyMintDevice, - IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics, - KeyCreationResult::KeyCreationResult, KeyFormat::KeyFormat, - KeyMintHardwareInfo::KeyMintHardwareInfo, KeyParameter::KeyParameter, - KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, - Tag::Tag, -}; -use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; -use anyhow::Context; -use keystore2_crypto::{hmac_sha256, HMAC_SHA256_LEN}; - -/// Key data associated with key generation/import. -#[derive(Debug, PartialEq, Eq)] -pub enum KeyImportData<'a> { - None, - Pkcs8(&'a [u8]), - Raw(&'a [u8]), -} - -impl<'a> KeyImportData<'a> { - /// Translate import parameters into a `KeyImportData` instance. - fn new(key_format: KeyFormat, key_data: &'a [u8]) -> binder::Result<Self> { - match key_format { - KeyFormat::PKCS8 => Ok(KeyImportData::Pkcs8(key_data)), - KeyFormat::RAW => Ok(KeyImportData::Raw(key_data)), - _ => Err(binder::Status::new_service_specific_error( - ErrorCode::UNSUPPORTED_KEY_FORMAT.0, - None, - )), - } - } -} - -/// A key blob that may be software-emulated or may be directly produced by an -/// underlying device. In either variant the inner data is the keyblob itself, -/// as seen by the relevant device. -#[derive(Debug, PartialEq, Eq)] -pub enum KeyBlob<'a> { - Raw(&'a [u8]), - Wrapped(&'a [u8]), -} - -/// Trait for detecting that software emulation of a current-version KeyMint -/// feature is required for a back-level KeyMint implementation. -pub trait EmulationDetector: Send + Sync { - /// Indicate whether software emulation is required for key - /// generation/import using the provided parameters. - fn emulation_required(&self, params: &[KeyParameter], import_data: &KeyImportData) -> bool; -} - -const KEYBLOB_PREFIX: &[u8] = b"SoftKeyMintForV1Blob"; -const KEYBLOB_HMAC_KEY: &[u8] = b"SoftKeyMintForV1HMACKey"; - -/// Wrap the provided keyblob: -/// - prefix it with an identifier specific to this wrapper -/// - suffix it with an HMAC tag, using the [`KEYBLOB_HMAC_KEY`] and `keyblob`. -fn wrap_keyblob(keyblob: &[u8]) -> anyhow::Result<Vec<u8>> { - let mut result = Vec::with_capacity(KEYBLOB_PREFIX.len() + keyblob.len() + HMAC_SHA256_LEN); - result.extend_from_slice(KEYBLOB_PREFIX); - result.extend_from_slice(keyblob); - let tag = hmac_sha256(KEYBLOB_HMAC_KEY, keyblob) - .context("In wrap_keyblob, failed to calculate HMAC-SHA256")?; - result.extend_from_slice(&tag); - Ok(result) -} - -/// 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 { - if !keyblob.starts_with(KEYBLOB_PREFIX) { - return KeyBlob::Raw(keyblob); - } - let without_prefix = &keyblob[KEYBLOB_PREFIX.len()..]; - if without_prefix.len() < HMAC_SHA256_LEN { - return KeyBlob::Raw(keyblob); - } - let (inner_keyblob, want_tag) = without_prefix.split_at(without_prefix.len() - HMAC_SHA256_LEN); - let got_tag = match hmac_sha256(KEYBLOB_HMAC_KEY, inner_keyblob) { - Ok(tag) => tag, - Err(e) => { - log::error!("Error calculating HMAC-SHA256 for keyblob unwrap: {:?}", e); - return KeyBlob::Raw(keyblob); - } - }; - // Comparison does not need to be constant-time here. - if want_tag == got_tag { - KeyBlob::Wrapped(inner_keyblob) - } else { - KeyBlob::Raw(keyblob) - } -} - -/// Wrapper around a real device that implements a back-level version of -/// `IKeyMintDevice` -pub struct BacklevelKeyMintWrapper<T: EmulationDetector> { - /// The `real` device implements some earlier version of `IKeyMintDevice` - real: Strong<dyn IKeyMintDevice>, - /// The `soft`ware device implements the current version of `IKeyMintDevice` - soft: Strong<dyn IKeyMintDevice>, - /// Detector for operations that are not supported by the earlier version of - /// `IKeyMintDevice`. Or possibly a large flightless bird, who can tell. - emu: T, -} - -impl<T> BacklevelKeyMintWrapper<T> -where - T: EmulationDetector + 'static, -{ - /// Create a wrapper around the provided back-level KeyMint device, so that - /// software emulation can be performed for any current-version features not - /// provided by the real device. - pub fn wrap( - emu: T, - real: Strong<dyn IKeyMintDevice>, - ) -> anyhow::Result<Strong<dyn IKeyMintDevice>> { - // This is a no-op if it was called before. - keystore2_km_compat::add_keymint_device_service(); - - let keystore_compat_service: Strong<dyn IKeystoreCompatService> = map_binder_status_code( - binder::get_interface("android.security.compat"), - ) - .context("In BacklevelKeyMintWrapper::wrap: Trying to connect to compat service.")?; - let soft = - map_binder_status(keystore_compat_service.getKeyMintDevice(SecurityLevel::SOFTWARE)) - .map_err(|e| match e { - Error::BinderTransaction(StatusCode::NAME_NOT_FOUND) => { - Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE) - } - e => e, - }) - .context("In BacklevelKeyMintWrapper::wrap: Trying to get software device.")?; - - Ok(BnKeyMintDevice::new_binder( - Self { real, soft, emu }, - BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, - )) - } -} - -impl<T> binder::Interface for BacklevelKeyMintWrapper<T> where T: EmulationDetector {} - -impl<T> IKeyMintDevice for BacklevelKeyMintWrapper<T> -where - T: EmulationDetector + 'static, -{ - // For methods that don't involve keyblobs, forward to either the real - // device, or to both real & emulated devices. - fn getHardwareInfo(&self) -> binder::Result<KeyMintHardwareInfo> { - self.real.getHardwareInfo() - } - fn addRngEntropy(&self, data: &[u8]) -> binder::Result<()> { - self.real.addRngEntropy(data) - } - fn deleteAllKeys(&self) -> binder::Result<()> { - self.real.deleteAllKeys() - } - fn destroyAttestationIds(&self) -> binder::Result<()> { - self.real.destroyAttestationIds() - } - fn deviceLocked( - &self, - password_only: bool, - timestamp_token: Option<&TimeStampToken>, - ) -> binder::Result<()> { - // Propagate to both real and software devices, but only pay attention - // to the result from the real device. - let _ = self.soft.deviceLocked(password_only, timestamp_token); - self.real.deviceLocked(password_only, timestamp_token) - } - fn earlyBootEnded(&self) -> binder::Result<()> { - // Propagate to both real and software devices, but only pay attention - // to the result from the real device. - let _ = self.soft.earlyBootEnded(); - self.real.earlyBootEnded() - } - - // For methods that emit keyblobs, check whether the underlying real device - // supports the relevant parameters, and forward to the appropriate device. - // If the emulated device is used, ensure that the created keyblob gets - // prefixed so we can recognize it in future. - fn generateKey( - &self, - key_params: &[KeyParameter], - attestation_key: Option<&AttestationKey>, - ) -> binder::Result<KeyCreationResult> { - if self.emu.emulation_required(key_params, &KeyImportData::None) { - let mut result = self.soft.generateKey(key_params, attestation_key)?; - result.keyBlob = map_or_log_err(wrap_keyblob(&result.keyBlob), Ok)?; - Ok(result) - } else { - self.real.generateKey(key_params, attestation_key) - } - } - fn importKey( - &self, - key_params: &[KeyParameter], - key_format: KeyFormat, - key_data: &[u8], - attestation_key: Option<&AttestationKey>, - ) -> binder::Result<KeyCreationResult> { - if self.emu.emulation_required(key_params, &KeyImportData::new(key_format, key_data)?) { - let mut result = - self.soft.importKey(key_params, key_format, key_data, attestation_key)?; - result.keyBlob = map_or_log_err(wrap_keyblob(&result.keyBlob), Ok)?; - Ok(result) - } else { - self.real.importKey(key_params, key_format, key_data, attestation_key) - } - } - fn importWrappedKey( - &self, - wrapped_key_data: &[u8], - wrapping_key_blob: &[u8], - masking_key: &[u8], - unwrapping_params: &[KeyParameter], - password_sid: i64, - biometric_sid: i64, - ) -> binder::Result<KeyCreationResult> { - // A wrapped key cannot be software-emulated, as the wrapping key is - // likely hardware-bound. - self.real.importWrappedKey( - wrapped_key_data, - wrapping_key_blob, - masking_key, - unwrapping_params, - password_sid, - biometric_sid, - ) - } - - // For methods that use keyblobs, determine which device to forward the - // operation to based on whether the keyblob is appropriately prefixed. - fn upgradeKey( - &self, - keyblob_to_upgrade: &[u8], - upgrade_params: &[KeyParameter], - ) -> binder::Result<Vec<u8>> { - match unwrap_keyblob(keyblob_to_upgrade) { - KeyBlob::Raw(keyblob) => self.real.upgradeKey(keyblob, upgrade_params), - KeyBlob::Wrapped(keyblob) => { - // Re-wrap the upgraded keyblob. - let upgraded_keyblob = self.soft.upgradeKey(keyblob, upgrade_params)?; - map_or_log_err(wrap_keyblob(&upgraded_keyblob), Ok) - } - } - } - fn deleteKey(&self, keyblob: &[u8]) -> binder::Result<()> { - match unwrap_keyblob(keyblob) { - KeyBlob::Raw(keyblob) => self.real.deleteKey(keyblob), - KeyBlob::Wrapped(keyblob) => { - // Forward to the software implementation for completeness, but - // this should always be a no-op. - self.soft.deleteKey(keyblob) - } - } - } - fn begin( - &self, - purpose: KeyPurpose, - keyblob: &[u8], - params: &[KeyParameter], - auth_token: Option<&HardwareAuthToken>, - ) -> binder::Result<BeginResult> { - match unwrap_keyblob(keyblob) { - KeyBlob::Raw(keyblob) => self.real.begin(purpose, keyblob, params, auth_token), - KeyBlob::Wrapped(keyblob) => self.soft.begin(purpose, keyblob, params, auth_token), - } - } - fn getKeyCharacteristics( - &self, - keyblob: &[u8], - app_id: &[u8], - app_data: &[u8], - ) -> binder::Result<Vec<KeyCharacteristics>> { - match unwrap_keyblob(keyblob) { - KeyBlob::Raw(keyblob) => self.real.getKeyCharacteristics(keyblob, app_id, app_data), - 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) - } -} - -/// Detector for current features that are not implemented by KeyMint V1. -#[derive(Debug)] -pub struct KeyMintV1 { - sec_level: SecurityLevel, -} - -impl KeyMintV1 { - pub fn new(sec_level: SecurityLevel) -> Self { - Self { sec_level } - } -} - -impl EmulationDetector for KeyMintV1 { - fn emulation_required(&self, params: &[KeyParameter], _import_data: &KeyImportData) -> bool { - // No current difference from KeyMint v1 for STRONGBOX (it doesn't - // support curve 25519). - if self.sec_level == SecurityLevel::STRONGBOX { - return false; - } - - // KeyMint V1 does not support the use of curve 25519, so hunt for that - // in the parameters. - if params.iter().any(|p| { - p.tag == Tag::EC_CURVE && p.value == KeyParameterValue::EcCurve(EcCurve::CURVE_25519) - }) { - return true; - } - // In theory, if the `import_data` is `KeyImportData::Pkcs8` we could - // check the imported keymaterial for the Ed25519 / X25519 OIDs in the - // PKCS8 keydata, and use that to decide to route to software. However, - // the KeyMint spec doesn't require that so don't attempt to parse the - // key material here. - false - } -} - -/// Detector for current features that are not implemented by KeyMaster, via the -/// km_compat wrapper. -#[derive(Debug)] -pub struct Keymaster { - v1: KeyMintV1, -} - -/// TODO(b/216434270): This could be used this to replace the emulation routing -/// in the km_compat C++ code, and allow support for imported ECDH keys along -/// the way. Would need to figure out what would happen to existing emulated -/// keys though. -#[allow(dead_code)] -impl Keymaster { - pub fn new(sec_level: SecurityLevel) -> Self { - Self { v1: KeyMintV1::new(sec_level) } - } -} - -impl EmulationDetector for Keymaster { - fn emulation_required(&self, params: &[KeyParameter], import_data: &KeyImportData) -> bool { - // The km_compat wrapper on top of Keymaster emulates the KeyMint V1 - // interface, so any feature from > v1 needs to be emulated. - if self.v1.emulation_required(params, import_data) { - return true; - } - - // Keymaster does not support ECDH (KeyPurpose::AGREE_KEY), so hunt for - // that in the parameters. - if params.iter().any(|p| { - p.tag == Tag::PURPOSE && p.value == KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY) - }) { - return true; - } - false - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_key_import_data() { - let data = vec![1, 2, 3]; - assert_eq!(KeyImportData::new(KeyFormat::PKCS8, &data), Ok(KeyImportData::Pkcs8(&data))); - assert_eq!(KeyImportData::new(KeyFormat::RAW, &data), Ok(KeyImportData::Raw(&data))); - assert!(KeyImportData::new(KeyFormat::X509, &data).is_err()); - } - - #[test] - fn test_wrap_keyblob() { - let keyblob = vec![1, 2, 3]; - let wrapped = wrap_keyblob(&keyblob).unwrap(); - assert_eq!(&wrapped[..KEYBLOB_PREFIX.len()], KEYBLOB_PREFIX); - assert_eq!(&wrapped[KEYBLOB_PREFIX.len()..KEYBLOB_PREFIX.len() + keyblob.len()], &keyblob); - assert_eq!(unwrap_keyblob(&keyblob), KeyBlob::Raw(&keyblob)); - assert_eq!(unwrap_keyblob(&wrapped), KeyBlob::Wrapped(&keyblob)); - - let mut corrupt_prefix = wrapped.clone(); - corrupt_prefix[0] ^= 0x01; - assert_eq!(unwrap_keyblob(&corrupt_prefix), KeyBlob::Raw(&corrupt_prefix)); - - let mut corrupt_suffix = wrapped.clone(); - corrupt_suffix[wrapped.len() - 1] ^= 0x01; - assert_eq!(unwrap_keyblob(&corrupt_suffix), KeyBlob::Raw(&corrupt_suffix)); - - let too_short = &wrapped[..wrapped.len() - 4]; - assert_eq!(unwrap_keyblob(too_short), KeyBlob::Raw(too_short)); - } - - #[test] - fn test_keymintv1_emulation_required() { - let tests = vec![ - (SecurityLevel::TRUSTED_ENVIRONMENT, vec![], false), - ( - SecurityLevel::TRUSTED_ENVIRONMENT, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), - }, - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::VERIFY), - }, - ], - false, - ), - ( - SecurityLevel::TRUSTED_ENVIRONMENT, - vec![KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY), - }], - false, - ), - ( - SecurityLevel::TRUSTED_ENVIRONMENT, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY), - }, - KeyParameter { - tag: Tag::EC_CURVE, - value: KeyParameterValue::EcCurve(EcCurve::P_256), - }, - ], - false, - ), - ( - SecurityLevel::TRUSTED_ENVIRONMENT, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY), - }, - KeyParameter { - tag: Tag::EC_CURVE, - value: KeyParameterValue::EcCurve(EcCurve::CURVE_25519), - }, - ], - true, - ), - ( - SecurityLevel::STRONGBOX, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY), - }, - KeyParameter { - tag: Tag::EC_CURVE, - value: KeyParameterValue::EcCurve(EcCurve::CURVE_25519), - }, - ], - false, - ), - ]; - for (sec_level, params, want) in tests { - let v1 = KeyMintV1::new(sec_level); - let got = v1.emulation_required(¶ms, &KeyImportData::None); - assert_eq!(got, want, "emulation_required({:?})={}, want {}", params, got, want); - } - } - - #[test] - fn test_keymaster_emulation_required() { - let tests = vec![ - (SecurityLevel::TRUSTED_ENVIRONMENT, vec![], false), - ( - SecurityLevel::TRUSTED_ENVIRONMENT, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), - }, - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::VERIFY), - }, - ], - false, - ), - ( - SecurityLevel::TRUSTED_ENVIRONMENT, - vec![KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY), - }], - true, - ), - ( - SecurityLevel::TRUSTED_ENVIRONMENT, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY), - }, - KeyParameter { - tag: Tag::EC_CURVE, - value: KeyParameterValue::EcCurve(EcCurve::P_256), - }, - ], - true, - ), - ( - SecurityLevel::TRUSTED_ENVIRONMENT, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY), - }, - KeyParameter { - tag: Tag::EC_CURVE, - value: KeyParameterValue::EcCurve(EcCurve::CURVE_25519), - }, - ], - true, - ), - ( - SecurityLevel::STRONGBOX, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::AGREE_KEY), - }, - KeyParameter { - tag: Tag::EC_CURVE, - value: KeyParameterValue::EcCurve(EcCurve::CURVE_25519), - }, - ], - true, - ), - ( - SecurityLevel::STRONGBOX, - vec![ - KeyParameter { - tag: Tag::PURPOSE, - value: KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), - }, - KeyParameter { - tag: Tag::EC_CURVE, - value: KeyParameterValue::EcCurve(EcCurve::CURVE_25519), - }, - ], - false, - ), - ]; - for (sec_level, params, want) in tests { - let v0 = Keymaster::new(sec_level); - let got = v0.emulation_required(¶ms, &KeyImportData::None); - assert_eq!(got, want, "emulation_required({:?})={}, want {}", params, got, want); - } - } -} diff --git a/keystore2/src/km_compat/Android.bp b/keystore2/src/km_compat/Android.bp index 806f3dcf..541788e4 100644 --- a/keystore2/src/km_compat/Android.bp +++ b/keystore2/src/km_compat/Android.bp @@ -25,10 +25,9 @@ rust_library { name: "libkeystore2_km_compat", crate_name: "keystore2_km_compat", srcs: ["lib.rs"], - defaults: [ - "keymint_use_latest_hal_aidl_rust", - ], + rustlibs: [ + "android.hardware.security.keymint-V1-rust", "android.security.compat-rust", ], shared_libs: [ @@ -42,10 +41,8 @@ rust_test { srcs: ["lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, - defaults: [ - "keymint_use_latest_hal_aidl_rust", - ], rustlibs: [ + "android.hardware.security.keymint-V1-rust", "android.security.compat-rust", ], shared_libs: [ @@ -56,17 +53,15 @@ rust_test { cc_library { name: "libkm_compat", srcs: ["km_compat.cpp"], - defaults: [ - "keymint_use_latest_hal_aidl_ndk_shared", - "keystore2_use_latest_aidl_ndk_shared", - ], shared_libs: [ "android.hardware.keymaster@3.0", "android.hardware.keymaster@4.0", "android.hardware.keymaster@4.1", - "android.hardware.security.secureclock-V1-ndk", - "android.hardware.security.sharedsecret-V1-ndk", - "android.security.compat-ndk", + "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.secureclock-V1-ndk_platform", + "android.hardware.security.sharedsecret-V1-ndk_platform", + "android.security.compat-ndk_platform", + "android.system.keystore2-V1-ndk_platform", "libbase", "libbinder_ndk", "libcrypto", @@ -82,13 +77,11 @@ cc_library { cc_library { name: "libkm_compat_service", srcs: ["km_compat_service.cpp"], - defaults: [ - "keymint_use_latest_hal_aidl_ndk_shared", - ], shared_libs: [ - "android.hardware.security.secureclock-V1-ndk", - "android.hardware.security.sharedsecret-V1-ndk", - "android.security.compat-ndk", + "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.secureclock-V1-ndk_platform", + "android.hardware.security.sharedsecret-V1-ndk_platform", + "android.security.compat-ndk_platform", "libbinder_ndk", "libcrypto", "libkm_compat", @@ -110,17 +103,15 @@ cc_test { "parameter_conversion_test.cpp", "slot_test.cpp", ], - defaults: [ - "keymint_use_latest_hal_aidl_ndk_shared", - "keystore2_use_latest_aidl_ndk_shared", - ], shared_libs: [ "android.hardware.keymaster@3.0", "android.hardware.keymaster@4.0", "android.hardware.keymaster@4.1", - "android.hardware.security.secureclock-V1-ndk", - "android.hardware.security.sharedsecret-V1-ndk", - "android.security.compat-ndk", + "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.secureclock-V1-ndk_platform", + "android.hardware.security.sharedsecret-V1-ndk_platform", + "android.security.compat-ndk_platform", + "android.system.keystore2-V1-ndk_platform", "libbase", "libbinder_ndk", "libcrypto", diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp index 6d0630b4..8d59a5a7 100644 --- a/keystore2/src/km_compat/km_compat.cpp +++ b/keystore2/src/km_compat/km_compat.cpp @@ -80,6 +80,7 @@ bool isAttestationParameter(const KMV1::KeyParameter& param) { case Tag::CERTIFICATE_SUBJECT: case Tag::CERTIFICATE_NOT_BEFORE: case Tag::CERTIFICATE_NOT_AFTER: + case Tag::INCLUDE_UNIQUE_ID: case Tag::DEVICE_UNIQUE_ATTESTATION: return true; default: @@ -126,7 +127,7 @@ bool isKeyCreationParameter(const KMV1::KeyParameter& param) { case Tag::TRUSTED_CONFIRMATION_REQUIRED: case Tag::UNLOCKED_DEVICE_REQUIRED: case Tag::CREATION_DATETIME: - case Tag::INCLUDE_UNIQUE_ID: + case Tag::UNIQUE_ID: case Tag::IDENTITY_CREDENTIAL_KEY: case Tag::STORAGE_KEY: case Tag::MAC_LENGTH: @@ -383,39 +384,29 @@ convertSharedSecretParametersToLegacy(const std::vector<SharedSecretParameters>& return ssps; } -void OperationSlotManager::setNumFreeSlots(uint8_t numFreeSlots) { +void OperationSlots::setNumFreeSlots(uint8_t numFreeSlots) { std::lock_guard<std::mutex> lock(mNumFreeSlotsMutex); mNumFreeSlots = numFreeSlots; } -std::optional<OperationSlot> -OperationSlotManager::claimSlot(std::shared_ptr<OperationSlotManager> operationSlots) { - std::lock_guard<std::mutex> lock(operationSlots->mNumFreeSlotsMutex); - if (operationSlots->mNumFreeSlots > 0) { - operationSlots->mNumFreeSlots--; - return OperationSlot(std::move(operationSlots), std::nullopt); +bool OperationSlots::claimSlot() { + std::lock_guard<std::mutex> lock(mNumFreeSlotsMutex); + if (mNumFreeSlots > 0) { + mNumFreeSlots--; + return true; } - return std::nullopt; -} - -OperationSlot -OperationSlotManager::claimReservedSlot(std::shared_ptr<OperationSlotManager> operationSlots) { - std::unique_lock<std::mutex> reservedGuard(operationSlots->mReservedSlotMutex); - return OperationSlot(std::move(operationSlots), std::move(reservedGuard)); + return false; } -OperationSlot::OperationSlot(std::shared_ptr<OperationSlotManager> slots, - std::optional<std::unique_lock<std::mutex>> reservedGuard) - : mOperationSlots(std::move(slots)), mReservedGuard(std::move(reservedGuard)) {} - -void OperationSlotManager::freeSlot() { +void OperationSlots::freeSlot() { std::lock_guard<std::mutex> lock(mNumFreeSlotsMutex); mNumFreeSlots++; } -OperationSlot::~OperationSlot() { - if (!mReservedGuard && mOperationSlots) { +void OperationSlot::freeSlot() { + if (mIsActive) { mOperationSlots->freeSlot(); + mIsActive = false; } } @@ -505,15 +496,16 @@ ScopedAStatus KeyMintDevice::importKey(const std::vector<KeyParameter>& inKeyPar auto legacyKeyGENParams = convertKeyParametersToLegacy(extractGenerationParams(inKeyParams)); auto legacyKeyFormat = convertKeyFormatToLegacy(in_inKeyFormat); KMV1::ErrorCode errorCode; - auto result = mDevice->importKey( - legacyKeyGENParams, legacyKeyFormat, in_inKeyData, - [&](V4_0_ErrorCode error, const hidl_vec<uint8_t>& keyBlob, - const V4_0_KeyCharacteristics& keyCharacteristics) { - errorCode = convert(error); - out_creationResult->keyBlob = keyBlobPrefix(keyBlob, false); - out_creationResult->keyCharacteristics = - processLegacyCharacteristics(securityLevel_, inKeyParams, keyCharacteristics); - }); + auto result = mDevice->importKey(legacyKeyGENParams, legacyKeyFormat, in_inKeyData, + [&](V4_0_ErrorCode error, const hidl_vec<uint8_t>& keyBlob, + const V4_0_KeyCharacteristics& keyCharacteristics) { + errorCode = convert(error); + out_creationResult->keyBlob = + keyBlobPrefix(keyBlob, false); + out_creationResult->keyCharacteristics = + processLegacyCharacteristics( + securityLevel_, inKeyParams, keyCharacteristics); + }); if (!result.isOk()) { LOG(ERROR) << __func__ << " transaction failed. " << result.description(); return convertErrorCode(KMV1::ErrorCode::UNKNOWN_ERROR); @@ -621,15 +613,9 @@ ScopedAStatus KeyMintDevice::begin(KeyPurpose in_inPurpose, const std::vector<KeyParameter>& in_inParams, const std::optional<HardwareAuthToken>& in_inAuthToken, BeginResult* _aidl_return) { - return beginInternal(in_inPurpose, prefixedKeyBlob, in_inParams, in_inAuthToken, - false /* useReservedSlot */, _aidl_return); -} - -ScopedAStatus KeyMintDevice::beginInternal(KeyPurpose in_inPurpose, - const std::vector<uint8_t>& prefixedKeyBlob, - const std::vector<KeyParameter>& in_inParams, - const std::optional<HardwareAuthToken>& in_inAuthToken, - bool useReservedSlot, BeginResult* _aidl_return) { + if (!mOperationSlots.claimSlot()) { + return convertErrorCode(V4_0_ErrorCode::TOO_MANY_OPERATIONS); + } const std::vector<uint8_t>& in_inKeyBlob = prefixedKeyBlobRemovePrefix(prefixedKeyBlob); if (prefixedKeyBlobIsSoftKeyMint(prefixedKeyBlob)) { @@ -637,41 +623,28 @@ ScopedAStatus KeyMintDevice::beginInternal(KeyPurpose in_inPurpose, _aidl_return); } - OperationSlot slot; - // No need to claim a slot for software device. - if (useReservedSlot) { - // There is only one reserved slot. This function blocks until - // the reserved slot becomes available. - slot = OperationSlotManager::claimReservedSlot(mOperationSlots); - } else { - if (auto opt_slot = OperationSlotManager::claimSlot(mOperationSlots)) { - slot = std::move(*opt_slot); - } else { - return convertErrorCode(V4_0_ErrorCode::TOO_MANY_OPERATIONS); - } - } - auto legacyPurpose = static_cast<::android::hardware::keymaster::V4_0::KeyPurpose>(in_inPurpose); auto legacyParams = convertKeyParametersToLegacy(in_inParams); auto legacyAuthToken = convertAuthTokenToLegacy(in_inAuthToken); KMV1::ErrorCode errorCode; - auto result = - mDevice->begin(legacyPurpose, in_inKeyBlob, legacyParams, legacyAuthToken, - [&](V4_0_ErrorCode error, const hidl_vec<V4_0_KeyParameter>& outParams, - uint64_t operationHandle) { - errorCode = convert(error); - if (error == V4_0_ErrorCode::OK) { - _aidl_return->challenge = operationHandle; - _aidl_return->params = convertKeyParametersFromLegacy(outParams); - _aidl_return->operation = ndk::SharedRefBase::make<KeyMintOperation>( - mDevice, operationHandle, std::move(slot)); - } - }); + auto result = mDevice->begin( + legacyPurpose, in_inKeyBlob, legacyParams, legacyAuthToken, + [&](V4_0_ErrorCode error, const hidl_vec<V4_0_KeyParameter>& outParams, + uint64_t operationHandle) { + errorCode = convert(error); + _aidl_return->challenge = operationHandle; + _aidl_return->params = convertKeyParametersFromLegacy(outParams); + _aidl_return->operation = ndk::SharedRefBase::make<KeyMintOperation>( + mDevice, operationHandle, &mOperationSlots, error == V4_0_ErrorCode::OK); + }); if (!result.isOk()) { LOG(ERROR) << __func__ << " transaction failed. " << result.description(); errorCode = KMV1::ErrorCode::UNKNOWN_ERROR; } + if (errorCode != KMV1::ErrorCode::OK) { + mOperationSlots.freeSlot(); + } return convertErrorCode(errorCode); } @@ -731,9 +704,8 @@ KeyMintDevice::convertStorageKeyToEphemeral(const std::vector<uint8_t>& prefixed LOG(ERROR) << __func__ << " export_key failed: " << ret.description(); return convertErrorCode(KMV1::ErrorCode::UNKNOWN_ERROR); } - if (km_error != KMV1::ErrorCode::OK) { + if (km_error != KMV1::ErrorCode::OK) LOG(ERROR) << __func__ << " export_key failed, code " << int32_t(km_error); - } return convertErrorCode(km_error); } @@ -769,19 +741,6 @@ ScopedAStatus KeyMintDevice::getKeyCharacteristics( } } -ScopedAStatus KeyMintDevice::getRootOfTrustChallenge(std::array<uint8_t, 16>* /* challenge */) { - return convertErrorCode(KMV1::ErrorCode::UNIMPLEMENTED); -} - -ScopedAStatus KeyMintDevice::getRootOfTrust(const std::array<uint8_t, 16>& /* challenge */, - std::vector<uint8_t>* /* rootOfTrust */) { - return convertErrorCode(KMV1::ErrorCode::UNIMPLEMENTED); -} - -ScopedAStatus KeyMintDevice::sendRootOfTrust(const std::vector<uint8_t>& /* rootOfTrust */) { - return convertErrorCode(KMV1::ErrorCode::UNIMPLEMENTED); -} - ScopedAStatus KeyMintOperation::updateAad(const std::vector<uint8_t>& input, const std::optional<HardwareAuthToken>& optAuthToken, const std::optional<TimeStampToken>& optTimeStampToken) { @@ -798,11 +757,7 @@ ScopedAStatus KeyMintOperation::updateAad(const std::vector<uint8_t>& input, LOG(ERROR) << __func__ << " transaction failed. " << result.description(); errorCode = KMV1::ErrorCode::UNKNOWN_ERROR; } - - // Operation slot is no longer occupied. - if (errorCode != KMV1::ErrorCode::OK) { - mOperationSlot = std::nullopt; - } + if (errorCode != KMV1::ErrorCode::OK) mOperationSlot.freeSlot(); return convertErrorCode(errorCode); } @@ -860,10 +815,7 @@ ScopedAStatus KeyMintOperation::update(const std::vector<uint8_t>& input_raw, inputPos += consumed; } - // Operation slot is no longer occupied. - if (errorCode != KMV1::ErrorCode::OK) { - mOperationSlot = std::nullopt; - } + if (errorCode != KMV1::ErrorCode::OK) mOperationSlot.freeSlot(); return convertErrorCode(errorCode); } @@ -894,19 +846,17 @@ KeyMintOperation::finish(const std::optional<std::vector<uint8_t>>& in_input, *out_output = output; }); + mOperationSlot.freeSlot(); if (!result.isOk()) { LOG(ERROR) << __func__ << " transaction failed. " << result.description(); errorCode = KMV1::ErrorCode::UNKNOWN_ERROR; } - - mOperationSlot = std::nullopt; - return convertErrorCode(errorCode); } ScopedAStatus KeyMintOperation::abort() { auto result = mDevice->abort(mOperationHandle); - mOperationSlot = std::nullopt; + mOperationSlot.freeSlot(); if (!result.isOk()) { LOG(ERROR) << __func__ << " transaction failed. " << result.description(); return convertErrorCode(KMV1::ErrorCode::UNKNOWN_ERROR); @@ -915,7 +865,7 @@ ScopedAStatus KeyMintOperation::abort() { } KeyMintOperation::~KeyMintOperation() { - if (mOperationSlot) { + if (mOperationSlot.hasSlot()) { auto error = abort(); if (!error.isOk()) { LOG(WARNING) << "Error calling abort in ~KeyMintOperation: " << error.getMessage(); @@ -1168,8 +1118,8 @@ KeyMintDevice::signCertificate(const std::vector<KeyParameter>& keyParams, kps.push_back(KMV1::makeKeyParameter(KMV1::TAG_PADDING, origPadding)); } BeginResult beginResult; - auto error = beginInternal(KeyPurpose::SIGN, prefixedKeyBlob, kps, HardwareAuthToken(), - true /* useReservedSlot */, &beginResult); + auto error = + begin(KeyPurpose::SIGN, prefixedKeyBlob, kps, HardwareAuthToken(), &beginResult); if (!error.isOk()) { errorCode = toErrorCode(error); return std::vector<uint8_t>(); @@ -1390,7 +1340,7 @@ KeymasterDevices initializeKeymasters() { CHECK(serviceManager.get()) << "Failed to get ServiceManager"; auto result = enumerateKeymasterDevices<Keymaster4>(serviceManager.get()); auto softKeymaster = result[SecurityLevel::SOFTWARE]; - if ((!result[SecurityLevel::TRUSTED_ENVIRONMENT]) && (!result[SecurityLevel::STRONGBOX])) { + if (!result[SecurityLevel::TRUSTED_ENVIRONMENT]) { result = enumerateKeymasterDevices<Keymaster3>(serviceManager.get()); } if (softKeymaster) result[SecurityLevel::SOFTWARE] = softKeymaster; @@ -1405,21 +1355,20 @@ KeymasterDevices initializeKeymasters() { } void KeyMintDevice::setNumFreeSlots(uint8_t numFreeSlots) { - mOperationSlots->setNumFreeSlots(numFreeSlots); + mOperationSlots.setNumFreeSlots(numFreeSlots); } // Constructors and helpers. KeyMintDevice::KeyMintDevice(sp<Keymaster> device, KeyMintSecurityLevel securityLevel) - : mDevice(device), mOperationSlots(std::make_shared<OperationSlotManager>()), - securityLevel_(securityLevel) { + : mDevice(device), securityLevel_(securityLevel) { if (securityLevel == KeyMintSecurityLevel::STRONGBOX) { setNumFreeSlots(3); } else { setNumFreeSlots(15); } - softKeyMintDevice_ = CreateKeyMintDevice(KeyMintSecurityLevel::SOFTWARE); + softKeyMintDevice_.reset(CreateKeyMintDevice(KeyMintSecurityLevel::SOFTWARE)); } sp<Keymaster> getDevice(KeyMintSecurityLevel securityLevel) { @@ -1442,33 +1391,14 @@ sp<Keymaster> getDevice(KeyMintSecurityLevel securityLevel) { } } -std::shared_ptr<IKeyMintDevice> getSoftwareKeymintDevice() { - static std::mutex mutex; - static std::shared_ptr<IKeyMintDevice> swDevice; - std::lock_guard<std::mutex> lock(mutex); - if (!swDevice) { - swDevice = CreateKeyMintDevice(KeyMintSecurityLevel::SOFTWARE); - } - return swDevice; -} - std::shared_ptr<KeyMintDevice> -KeyMintDevice::getWrappedKeymasterDevice(KeyMintSecurityLevel securityLevel) { +KeyMintDevice::createKeyMintDevice(KeyMintSecurityLevel securityLevel) { if (auto dev = getDevice(securityLevel)) { return ndk::SharedRefBase::make<KeyMintDevice>(std::move(dev), securityLevel); } return {}; } -std::shared_ptr<IKeyMintDevice> -KeyMintDevice::createKeyMintDevice(KeyMintSecurityLevel securityLevel) { - if (securityLevel == KeyMintSecurityLevel::SOFTWARE) { - return getSoftwareKeymintDevice(); - } else { - return getWrappedKeymasterDevice(securityLevel); - } -} - std::shared_ptr<SharedSecret> SharedSecret::createSharedSecret(KeyMintSecurityLevel securityLevel) { auto device = getDevice(securityLevel); if (!device) { diff --git a/keystore2/src/km_compat/km_compat.h b/keystore2/src/km_compat/km_compat.h index 6654c4a6..70c7b863 100644 --- a/keystore2/src/km_compat/km_compat.h +++ b/keystore2/src/km_compat/km_compat.h @@ -50,55 +50,41 @@ using ::aidl::android::security::compat::BnKeystoreCompatService; using ::android::hardware::keymaster::V4_1::support::Keymaster; using ::ndk::ScopedAStatus; -class OperationSlot; -class OperationSlotManager; -// An abstraction for a single operation slot. -// This contains logic to ensure that we do not free the slot multiple times, -// e.g., if we call abort twice on the same operation. -class OperationSlot { - friend OperationSlotManager; - +class OperationSlots { private: - std::shared_ptr<OperationSlotManager> mOperationSlots; - std::optional<std::unique_lock<std::mutex>> mReservedGuard; - - protected: - OperationSlot(std::shared_ptr<OperationSlotManager>, - std::optional<std::unique_lock<std::mutex>> reservedGuard); - OperationSlot(const OperationSlot&) = delete; - OperationSlot& operator=(const OperationSlot&) = delete; + uint8_t mNumFreeSlots; + std::mutex mNumFreeSlotsMutex; public: - OperationSlot() : mOperationSlots(nullptr), mReservedGuard(std::nullopt) {} - OperationSlot(OperationSlot&&) = default; - OperationSlot& operator=(OperationSlot&&) = default; - ~OperationSlot(); + void setNumFreeSlots(uint8_t numFreeSlots); + bool claimSlot(); + void freeSlot(); }; -class OperationSlotManager { +// An abstraction for a single operation slot. +// This contains logic to ensure that we do not free the slot multiple times, +// e.g., if we call abort twice on the same operation. +class OperationSlot { private: - uint8_t mNumFreeSlots; - std::mutex mNumFreeSlotsMutex; - std::mutex mReservedSlotMutex; + OperationSlots* mOperationSlots; + bool mIsActive; public: - void setNumFreeSlots(uint8_t numFreeSlots); - static std::optional<OperationSlot> - claimSlot(std::shared_ptr<OperationSlotManager> operationSlots); - static OperationSlot claimReservedSlot(std::shared_ptr<OperationSlotManager> operationSlots); + OperationSlot(OperationSlots* slots, bool isActive) + : mOperationSlots(slots), mIsActive(isActive) {} + void freeSlot(); + bool hasSlot() { return mIsActive; } }; class KeyMintDevice : public aidl::android::hardware::security::keymint::BnKeyMintDevice { private: ::android::sp<Keymaster> mDevice; - std::shared_ptr<OperationSlotManager> mOperationSlots; + OperationSlots mOperationSlots; public: explicit KeyMintDevice(::android::sp<Keymaster>, KeyMintSecurityLevel); - static std::shared_ptr<IKeyMintDevice> createKeyMintDevice(KeyMintSecurityLevel securityLevel); - static std::shared_ptr<KeyMintDevice> - getWrappedKeymasterDevice(KeyMintSecurityLevel securityLevel); + static std::shared_ptr<KeyMintDevice> createKeyMintDevice(KeyMintSecurityLevel securityLevel); ScopedAStatus getHardwareInfo(KeyMintHardwareInfo* _aidl_return) override; ScopedAStatus addRngEntropy(const std::vector<uint8_t>& in_data) override; @@ -121,15 +107,10 @@ class KeyMintDevice : public aidl::android::hardware::security::keymint::BnKeyMi ScopedAStatus deleteKey(const std::vector<uint8_t>& in_inKeyBlob) override; ScopedAStatus deleteAllKeys() override; ScopedAStatus destroyAttestationIds() override; - ScopedAStatus begin(KeyPurpose in_inPurpose, const std::vector<uint8_t>& in_inKeyBlob, const std::vector<KeyParameter>& in_inParams, const std::optional<HardwareAuthToken>& in_inAuthToken, BeginResult* _aidl_return) override; - ScopedAStatus beginInternal(KeyPurpose in_inPurpose, const std::vector<uint8_t>& in_inKeyBlob, - const std::vector<KeyParameter>& in_inParams, - const std::optional<HardwareAuthToken>& in_inAuthToken, - bool useReservedSlot, BeginResult* _aidl_return); ScopedAStatus deviceLocked(bool passwordOnly, const std::optional<TimeStampToken>& timestampToken) override; ScopedAStatus earlyBootEnded() override; @@ -142,11 +123,6 @@ class KeyMintDevice : public aidl::android::hardware::security::keymint::BnKeyMi const std::vector<uint8_t>& appId, const std::vector<uint8_t>& appData, std::vector<KeyCharacteristics>* keyCharacteristics) override; - ScopedAStatus getRootOfTrustChallenge(std::array<uint8_t, 16>* challenge); - ScopedAStatus getRootOfTrust(const std::array<uint8_t, 16>& challenge, - std::vector<uint8_t>* rootOfTrust); - ScopedAStatus sendRootOfTrust(const std::vector<uint8_t>& rootOfTrust); - // 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> @@ -165,8 +141,9 @@ class KeyMintDevice : public aidl::android::hardware::security::keymint::BnKeyMi class KeyMintOperation : public aidl::android::hardware::security::keymint::BnKeyMintOperation { public: - KeyMintOperation(::android::sp<Keymaster> device, uint64_t operationHandle, OperationSlot slot) - : mDevice(device), mOperationHandle(operationHandle), mOperationSlot(std::move(slot)) {} + KeyMintOperation(::android::sp<Keymaster> device, uint64_t operationHandle, + OperationSlots* slots, bool isActive) + : mDevice(device), mOperationHandle(operationHandle), mOperationSlot(slots, isActive) {} ~KeyMintOperation(); ScopedAStatus updateAad(const std::vector<uint8_t>& input, @@ -204,7 +181,7 @@ class KeyMintOperation : public aidl::android::hardware::security::keymint::BnKe std::vector<uint8_t> mUpdateBuffer; ::android::sp<Keymaster> mDevice; uint64_t mOperationHandle; - std::optional<OperationSlot> mOperationSlot; + OperationSlot mOperationSlot; }; class SharedSecret : public aidl::android::hardware::security::sharedsecret::BnSharedSecret { diff --git a/keystore2/src/km_compat/km_compat_type_conversion.h b/keystore2/src/km_compat/km_compat_type_conversion.h index 33248a40..de094775 100644 --- a/keystore2/src/km_compat/km_compat_type_conversion.h +++ b/keystore2/src/km_compat/km_compat_type_conversion.h @@ -16,9 +16,6 @@ #pragma once -#include <optional> - -#include <aidl/android/hardware/security/keymint/EcCurve.h> #include <aidl/android/hardware/security/keymint/ErrorCode.h> #include <keymasterV4_1/keymaster_tags.h> #include <keymint_support/keymint_tags.h> @@ -281,7 +278,7 @@ static KMV1::Digest convert(V4_0::Digest d) { } } -static std::optional<V4_0::EcCurve> convert(KMV1::EcCurve e) { +static V4_0::EcCurve convert(KMV1::EcCurve e) { switch (e) { case KMV1::EcCurve::P_224: return V4_0::EcCurve::P_224; @@ -291,11 +288,7 @@ static std::optional<V4_0::EcCurve> convert(KMV1::EcCurve e) { return V4_0::EcCurve::P_384; case KMV1::EcCurve::P_521: return V4_0::EcCurve::P_521; - case KMV1::EcCurve::CURVE_25519: - // KeyMaster did not support curve 25519 - return std::nullopt; } - return std::nullopt; } static KMV1::EcCurve convert(V4_0::EcCurve e) { @@ -497,9 +490,7 @@ static V4_0::KeyParameter convertKeyParameterToLegacy(const KMV1::KeyParameter& break; case KMV1::Tag::EC_CURVE: if (auto v = KMV1::authorizationValue(KMV1::TAG_EC_CURVE, kp)) { - if (auto curve = convert(v->get())) { - return V4_0::makeKeyParameter(V4_0::TAG_EC_CURVE, curve.value()); - } + return V4_0::makeKeyParameter(V4_0::TAG_EC_CURVE, convert(v->get())); } break; case KMV1::Tag::RSA_PUBLIC_EXPONENT: diff --git a/keystore2/src/km_compat/lib.rs b/keystore2/src/km_compat/lib.rs index 13f77607..56c35bfa 100644 --- a/keystore2/src/km_compat/lib.rs +++ b/keystore2/src/km_compat/lib.rs @@ -260,7 +260,7 @@ mod tests { if let Some(mut extras) = extra_params { kps.append(&mut extras); } - let result = legacy.begin(purpose, blob, &kps, None); + let result = legacy.begin(purpose, &blob, &kps, None); assert!(result.is_ok(), "{:?}", result); result.unwrap() } @@ -286,7 +286,7 @@ mod tests { let operation = begin_result.operation.unwrap(); let update_aad_result = operation.updateAad( - b"foobar".as_ref(), + &b"foobar".to_vec(), None, /* authToken */ None, /* timestampToken */ ); @@ -310,7 +310,7 @@ mod tests { let operation = begin_result.operation.unwrap(); let update_aad_result = operation.updateAad( - b"foobar".as_ref(), + &b"foobar".to_vec(), None, /* authToken */ None, /* timestampToken */ ); @@ -450,6 +450,10 @@ mod tests { ))); assert!(sec_level_enforced.iter().any(|kp| matches!( kp, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KeyParameterValue::Integer(_) } + ))); + assert!(sec_level_enforced.iter().any(|kp| matches!( + kp, KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KeyParameterValue::Integer(_) } ))); } diff --git a/keystore2/src/km_compat/slot_test.cpp b/keystore2/src/km_compat/slot_test.cpp index d7349702..43f3bc69 100644 --- a/keystore2/src/km_compat/slot_test.cpp +++ b/keystore2/src/km_compat/slot_test.cpp @@ -26,7 +26,6 @@ using ::aidl::android::hardware::security::keymint::Algorithm; using ::aidl::android::hardware::security::keymint::BlockMode; using ::aidl::android::hardware::security::keymint::Certificate; using ::aidl::android::hardware::security::keymint::Digest; -using ::aidl::android::hardware::security::keymint::EcCurve; using ::aidl::android::hardware::security::keymint::ErrorCode; using ::aidl::android::hardware::security::keymint::IKeyMintOperation; using ::aidl::android::hardware::security::keymint::KeyCharacteristics; @@ -54,25 +53,6 @@ static std::vector<uint8_t> generateAESKey(std::shared_ptr<KeyMintDevice> device return creationResult.keyBlob; } -static bool generateECSingingKey(std::shared_ptr<KeyMintDevice> device) { - uint64_t now_ms = (uint64_t)time(nullptr) * 1000; - - auto keyParams = std::vector<KeyParameter>({ - KMV1::makeKeyParameter(KMV1::TAG_ALGORITHM, Algorithm::EC), - KMV1::makeKeyParameter(KMV1::TAG_EC_CURVE, EcCurve::P_256), - KMV1::makeKeyParameter(KMV1::TAG_NO_AUTH_REQUIRED, true), - KMV1::makeKeyParameter(KMV1::TAG_DIGEST, Digest::SHA_2_256), - KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::SIGN), - KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::VERIFY), - KMV1::makeKeyParameter(KMV1::TAG_CERTIFICATE_NOT_BEFORE, now_ms - 60 * 60 * 1000), - KMV1::makeKeyParameter(KMV1::TAG_CERTIFICATE_NOT_AFTER, now_ms + 60 * 60 * 1000), - }); - KeyCreationResult creationResult; - auto status = device->generateKey(keyParams, std::nullopt /* attest_key */, &creationResult); - EXPECT_TRUE(status.isOk()) << status.getDescription(); - return status.isOk(); -} - static std::variant<BeginResult, ScopedAStatus> begin(std::shared_ptr<KeyMintDevice> device, bool valid) { auto blob = generateAESKey(device); @@ -89,57 +69,17 @@ static std::variant<BeginResult, ScopedAStatus> begin(std::shared_ptr<KeyMintDev return beginResult; } -static std::shared_ptr<KMV1::IKeyMintOperation> -generateAndBeginECDHKeyOperation(std::shared_ptr<KeyMintDevice> device) { - uint64_t now_ms = (uint64_t)time(nullptr) * 1000; - - auto keyParams = std::vector<KeyParameter>({ - KMV1::makeKeyParameter(KMV1::TAG_ALGORITHM, Algorithm::EC), - KMV1::makeKeyParameter(KMV1::TAG_EC_CURVE, EcCurve::P_256), - KMV1::makeKeyParameter(KMV1::TAG_NO_AUTH_REQUIRED, true), - KMV1::makeKeyParameter(KMV1::TAG_DIGEST, Digest::NONE), - KMV1::makeKeyParameter(KMV1::TAG_PURPOSE, KeyPurpose::AGREE_KEY), - KMV1::makeKeyParameter(KMV1::TAG_CERTIFICATE_NOT_BEFORE, now_ms - 60 * 60 * 1000), - KMV1::makeKeyParameter(KMV1::TAG_CERTIFICATE_NOT_AFTER, now_ms + 60 * 60 * 1000), - }); - KeyCreationResult creationResult; - auto status = device->generateKey(keyParams, std::nullopt /* attest_key */, &creationResult); - EXPECT_TRUE(status.isOk()) << status.getDescription(); - if (!status.isOk()) { - return {}; - } - std::vector<KeyParameter> kps; - BeginResult beginResult; - auto bstatus = device->begin(KeyPurpose::AGREE_KEY, creationResult.keyBlob, kps, - HardwareAuthToken(), &beginResult); - EXPECT_TRUE(status.isOk()) << status.getDescription(); - if (status.isOk()) { - return beginResult.operation; - } - return {}; -} - static const int NUM_SLOTS = 2; TEST(SlotTest, TestSlots) { static std::shared_ptr<KeyMintDevice> device = - KeyMintDevice::getWrappedKeymasterDevice(SecurityLevel::TRUSTED_ENVIRONMENT); - ASSERT_NE(device.get(), nullptr); - + KeyMintDevice::createKeyMintDevice(SecurityLevel::TRUSTED_ENVIRONMENT); device->setNumFreeSlots(NUM_SLOTS); // A begin() that returns a failure should not use a slot. auto result = begin(device, false); ASSERT_TRUE(std::holds_alternative<ScopedAStatus>(result)); - // Software emulated operations must not leak virtual slots. - ASSERT_TRUE(!!generateAndBeginECDHKeyOperation(device)); - - // Software emulated operations must not impact virtual slots accounting. - // As opposed to the previous call, the software operation is kept alive. - auto software_op = generateAndBeginECDHKeyOperation(device); - ASSERT_TRUE(!!software_op); - // Fill up all the slots. std::vector<std::shared_ptr<IKeyMintOperation>> operations; for (int i = 0; i < NUM_SLOTS; i++) { @@ -154,14 +94,6 @@ TEST(SlotTest, TestSlots) { ASSERT_EQ(std::get<ScopedAStatus>(result).getServiceSpecificError(), static_cast<int32_t>(ErrorCode::TOO_MANY_OPERATIONS)); - // At this point all slots are in use. We should still be able to generate keys which - // require an operation slot during generation. - ASSERT_TRUE(generateECSingingKey(device)); - - // Software emulated operations should work despite having all virtual operation slots - // depleted. - ASSERT_TRUE(generateAndBeginECDHKeyOperation(device)); - // TODO: I'm not sure how to generate a failing update call to test that. // Calling finish should free up a slot. diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs index d75bfd2c..6b16d2e0 100644 --- a/keystore2/src/legacy_blob.rs +++ b/keystore2/src/legacy_blob.rs @@ -17,8 +17,8 @@ use crate::{ error::{Error as KsError, ResponseCode}, key_parameter::{KeyParameter, KeyParameterValue}, + super_key::SuperKeyManager, utils::uid_to_android_user, - utils::AesGcm, }; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ SecurityLevel::SecurityLevel, Tag::Tag, TagType::TagType, @@ -26,7 +26,6 @@ use android_hardware_security_keymint::aidl::android::hardware::security::keymin use anyhow::{Context, Result}; use keystore2_crypto::{aes_gcm_decrypt, Password, ZVec}; use std::collections::{HashMap, HashSet}; -use std::sync::Arc; use std::{convert::TryInto, fs::File, path::Path, path::PathBuf}; use std::{ fs, @@ -88,14 +87,6 @@ pub enum Error { /// an invalid alias filename encoding. #[error("Invalid alias filename encoding.")] BadEncoding, - /// A component of the requested entry other than the KM key blob itself - /// was encrypted and no super key was provided. - #[error("Locked entry component.")] - LockedComponent, - /// The uids presented to move_keystore_entry belonged to different - /// Android users. - #[error("Cannot move keys across Android users.")] - AndroidUserMismatch, } /// The blob payload, optionally with all information required to decrypt it. @@ -105,16 +96,6 @@ pub enum BlobValue { Generic(Vec<u8>), /// A legacy key characteristics file. This has only a single list of Authorizations. Characteristics(Vec<u8>), - /// A legacy key characteristics file. This has only a single list of Authorizations. - /// Additionally, this characteristics file was encrypted with the user's super key. - EncryptedCharacteristics { - /// Initialization vector. - iv: Vec<u8>, - /// Aead tag for integrity verification. - tag: Vec<u8>, - /// Ciphertext. - data: Vec<u8>, - }, /// A key characteristics cache has both a hardware enforced and a software enforced list /// of authorizations. CharacteristicsCache(Vec<u8>), @@ -143,17 +124,6 @@ pub enum BlobValue { /// Ciphertext. data: Vec<u8>, }, - /// An encrypted blob. Includes the initialization vector, the aead tag, and the - /// ciphertext data. The key can be selected from context, i.e., the owner of the key - /// blob. This is a special case for generic encrypted blobs as opposed to key blobs. - EncryptedGeneric { - /// Initialization vector. - iv: Vec<u8>, - /// Aead tag for integrity verification. - tag: Vec<u8>, - /// Ciphertext. - data: Vec<u8>, - }, /// Holds the plaintext key blob either after unwrapping an encrypted blob or when the /// blob was stored in "plaintext" on disk. The "plaintext" of a key blob is not actual /// plaintext because all KeyMint blobs are encrypted with a device bound key. The key @@ -162,19 +132,6 @@ pub enum BlobValue { Decrypted(ZVec), } -/// Keystore used two different key characteristics file formats in the past. -/// The key characteristics cache which superseded the characteristics file. -/// The latter stored only one list of key parameters, while the former stored -/// a hardware enforced and a software enforced list. This Enum indicates which -/// type was read from the file system. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum LegacyKeyCharacteristics { - /// A characteristics cache was read. - Cache(Vec<KeyParameter>), - /// A characteristics file was read. - File(Vec<KeyParameter>), -} - /// Represents a loaded legacy key blob file. #[derive(Debug, Eq, PartialEq)] pub struct Blob { @@ -212,16 +169,6 @@ fn read_ne_i64(stream: &mut dyn Read) -> Result<i64> { } impl Blob { - /// Creates a new blob from flags and value. - pub fn new(flags: u8, value: BlobValue) -> Self { - Self { flags, value } - } - - /// Return the raw flags of this Blob. - pub fn get_flags(&self) -> u8 { - self.flags - } - /// This blob was generated with a fallback software KM device. pub fn is_fallback(&self) -> bool { self.flags & flags::FALLBACK != 0 @@ -265,14 +212,10 @@ impl LegacyBlobLoader { // version (1 Byte) // blob_type (1 Byte) // flags (1 Byte) - // info (1 Byte) Size of an info field appended to the blob. + // info (1 Byte) // initialization_vector (16 Bytes) // integrity (MD5 digest or gcm tag) (16 Bytes) // length (4 Bytes) - // - // The info field is used to store the salt for password encrypted blobs. - // The beginning of the info field can be computed from the file length - // and the info byte from the header: <file length> - <info> bytes. const COMMON_HEADER_SIZE: usize = 4 + Self::IV_SIZE + Self::GCM_TAG_LENGTH + 4; const VERSION_OFFSET: usize = 0; @@ -398,28 +341,12 @@ impl LegacyBlobLoader { let tag = &buffer[Self::AEAD_TAG_OFFSET..Self::AEAD_TAG_OFFSET + Self::GCM_TAG_LENGTH]; match (blob_type, is_encrypted, salt) { - (blob_types::GENERIC, false, _) => { + (blob_types::GENERIC, _, _) => { Ok(Blob { flags, value: BlobValue::Generic(value.to_vec()) }) } - (blob_types::GENERIC, true, _) => Ok(Blob { - flags, - value: BlobValue::EncryptedGeneric { - iv: iv.to_vec(), - tag: tag.to_vec(), - data: value.to_vec(), - }, - }), - (blob_types::KEY_CHARACTERISTICS, false, _) => { + (blob_types::KEY_CHARACTERISTICS, _, _) => { Ok(Blob { flags, value: BlobValue::Characteristics(value.to_vec()) }) } - (blob_types::KEY_CHARACTERISTICS, true, _) => Ok(Blob { - flags, - value: BlobValue::EncryptedCharacteristics { - iv: iv.to_vec(), - tag: tag.to_vec(), - data: value.to_vec(), - }, - }), (blob_types::KEY_CHARACTERISTICS_CACHE, _, _) => { Ok(Blob { flags, value: BlobValue::CharacteristicsCache(value.to_vec()) }) } @@ -489,26 +416,17 @@ impl LegacyBlobLoader { BlobValue::Encrypted { iv, tag, data } => Ok(Blob { flags: blob.flags, value: BlobValue::Decrypted( - decrypt(data, iv, tag, None, None) + decrypt(&data, &iv, &tag, None, None) .context("In new_from_stream_decrypt_with.")?, ), }), BlobValue::PwEncrypted { iv, tag, data, salt, key_size } => Ok(Blob { flags: blob.flags, value: BlobValue::Decrypted( - decrypt(data, iv, tag, Some(salt), Some(*key_size)) + decrypt(&data, &iv, &tag, Some(salt), Some(*key_size)) .context("In new_from_stream_decrypt_with.")?, ), }), - BlobValue::EncryptedGeneric { iv, tag, data } => Ok(Blob { - flags: blob.flags, - value: BlobValue::Generic( - decrypt(data, iv, tag, None, None) - .context("In new_from_stream_decrypt_with.")?[..] - .to_vec(), - ), - }), - _ => Ok(blob), } } @@ -628,91 +546,24 @@ impl LegacyBlobLoader { Ok(params) } - /// This function takes a Blob and an optional AesGcm. Plain text blob variants are - /// passed through as is. If a super key is given an attempt is made to decrypt the - /// blob thereby mapping BlobValue variants as follows: - /// BlobValue::Encrypted => BlobValue::Decrypted - /// BlobValue::EncryptedGeneric => BlobValue::Generic - /// BlobValue::EncryptedCharacteristics => BlobValue::Characteristics - /// If now super key is given or BlobValue::PwEncrypted is encountered, - /// Err(Error::LockedComponent) is returned. - fn decrypt_if_required(super_key: &Option<Arc<dyn AesGcm>>, blob: Blob) -> Result<Blob> { - match blob { - Blob { value: BlobValue::Generic(_), .. } - | Blob { value: BlobValue::Characteristics(_), .. } - | Blob { value: BlobValue::CharacteristicsCache(_), .. } - | Blob { value: BlobValue::Decrypted(_), .. } => Ok(blob), - Blob { value: BlobValue::EncryptedCharacteristics { iv, tag, data }, flags } - if super_key.is_some() => - { - Ok(Blob { - value: BlobValue::Characteristics( - super_key.as_ref().unwrap().decrypt(&data, &iv, &tag).context( - "In decrypt_if_required: Failed to decrypt EncryptedCharacteristics", - )?[..] - .to_vec(), - ), - flags, - }) - } - Blob { value: BlobValue::Encrypted { iv, tag, data }, flags } - if super_key.is_some() => - { - Ok(Blob { - value: BlobValue::Decrypted( - super_key - .as_ref() - .unwrap() - .decrypt(&data, &iv, &tag) - .context("In decrypt_if_required: Failed to decrypt Encrypted")?, - ), - flags, - }) - } - Blob { value: BlobValue::EncryptedGeneric { iv, tag, data }, flags } - if super_key.is_some() => - { - Ok(Blob { - value: BlobValue::Generic( - super_key - .as_ref() - .unwrap() - .decrypt(&data, &iv, &tag) - .context("In decrypt_if_required: Failed to decrypt Encrypted")?[..] - .to_vec(), - ), - flags, - }) - } - // This arm catches all encrypted cases where super key is not present or cannot - // decrypt the blob, the latter being BlobValue::PwEncrypted. - _ => Err(Error::LockedComponent) - .context("In decrypt_if_required: Encountered encrypted blob without super key."), - } - } - fn read_characteristics_file( &self, uid: u32, prefix: &str, alias: &str, hw_sec_level: SecurityLevel, - super_key: &Option<Arc<dyn AesGcm>>, - ) -> Result<LegacyKeyCharacteristics> { + ) -> Result<Vec<KeyParameter>> { let blob = Self::read_generic_blob(&self.make_chr_filename(uid, alias, prefix)) .context("In read_characteristics_file")?; let blob = match blob { - None => return Ok(LegacyKeyCharacteristics::Cache(Vec::new())), + None => return Ok(Vec::new()), Some(blob) => blob, }; - let blob = Self::decrypt_if_required(super_key, blob) - .context("In read_characteristics_file: Trying to decrypt blob.")?; - - let (mut stream, is_cache) = match blob.value() { - BlobValue::Characteristics(data) => (&data[..], false), - BlobValue::CharacteristicsCache(data) => (&data[..], true), + let mut stream = match blob.value() { + BlobValue::Characteristics(data) => &data[..], + BlobValue::CharacteristicsCache(data) => &data[..], _ => { return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(concat!( "In read_characteristics_file: ", @@ -738,12 +589,7 @@ impl LegacyBlobLoader { .into_iter() .map(|value| KeyParameter::new(value, SecurityLevel::KEYSTORE)); - let params: Vec<KeyParameter> = hw_list.into_iter().flatten().chain(sw_list).collect(); - if is_cache { - Ok(LegacyKeyCharacteristics::Cache(params)) - } else { - Ok(LegacyKeyCharacteristics::File(params)) - } + Ok(hw_list.into_iter().flatten().chain(sw_list).collect()) } // This is a list of known prefixes that the Keystore 1.0 SPI used to use. @@ -793,40 +639,14 @@ impl LegacyBlobLoader { Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?)) } - fn read_generic_blob_decrypt_with<F>(path: &Path, decrypt: F) -> Result<Option<Blob>> - where - F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>, - { - let mut file = match Self::with_retry_interrupted(|| File::open(path)) { - Ok(file) => file, - Err(e) => match e.kind() { - ErrorKind::NotFound => return Ok(None), - _ => return Err(e).context("In read_generic_blob_decrypt_with."), - }, - }; - - Ok(Some( - Self::new_from_stream_decrypt_with(&mut file, decrypt) - .context("In read_generic_blob_decrypt_with.")?, - )) - } - /// Read a legacy keystore entry blob. - pub fn read_legacy_keystore_entry<F>( - &self, - uid: u32, - alias: &str, - decrypt: F, - ) -> Result<Option<Vec<u8>>> - where - F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>, - { + pub fn read_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<Option<Vec<u8>>> { let path = match self.make_legacy_keystore_entry_filename(uid, alias) { Some(path) => path, None => return Ok(None), }; - let blob = Self::read_generic_blob_decrypt_with(&path, decrypt) + let blob = Self::read_generic_blob(&path) .context("In read_legacy_keystore_entry: Failed to read blob.")?; Ok(blob.and_then(|blob| match blob.value { @@ -839,23 +659,22 @@ impl LegacyBlobLoader { } /// Remove a legacy keystore entry by the name alias with owner uid. - pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<bool> { + pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<()> { let path = match self.make_legacy_keystore_entry_filename(uid, alias) { Some(path) => path, - None => return Ok(false), + None => return Ok(()), }; if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) { match e.kind() { - ErrorKind::NotFound => return Ok(false), + ErrorKind::NotFound => return Ok(()), _ => return Err(e).context("In remove_legacy_keystore_entry."), } } let user_id = uid_to_android_user(uid); self.remove_user_dir_if_empty(user_id) - .context("In remove_legacy_keystore_entry: Trying to remove empty user dir.")?; - Ok(true) + .context("In remove_legacy_keystore_entry: Trying to remove empty user dir.") } /// List all entries belonging to the given uid. @@ -1017,7 +836,7 @@ impl LegacyBlobLoader { // in are all in the printable range that don't get mangled. for prefix in Self::KNOWN_KEYSTORE_PREFIXES { if let Some(alias) = encoded_alias.strip_prefix(prefix) { - return Self::decode_alias(alias).ok(); + return Self::decode_alias(&alias).ok(); } } None @@ -1169,88 +988,6 @@ impl LegacyBlobLoader { Ok(something_was_deleted) } - /// This function moves a keystore file if it exists. It constructs the source and destination - /// file name using the make_filename function with the arguments uid, alias, and prefix. - /// The function overwrites existing destination files silently. If the source does not exist, - /// this function has no side effect and returns successfully. - fn move_keystore_file_if_exists<F>( - src_uid: u32, - dest_uid: u32, - src_alias: &str, - dest_alias: &str, - prefix: &str, - make_filename: F, - ) -> Result<()> - where - F: Fn(u32, &str, &str) -> PathBuf, - { - let src_path = make_filename(src_uid, src_alias, prefix); - let dest_path = make_filename(dest_uid, dest_alias, prefix); - match Self::with_retry_interrupted(|| fs::rename(&src_path, &dest_path)) { - Err(e) if e.kind() == ErrorKind::NotFound => Ok(()), - r => r.context("In move_keystore_file_if_exists: Trying to rename."), - } - } - - /// Moves a keystore entry from one uid to another. The uids must have the same android user - /// component. Moves across android users are not permitted. - pub fn move_keystore_entry( - &self, - src_uid: u32, - dest_uid: u32, - src_alias: &str, - dest_alias: &str, - ) -> Result<()> { - if src_uid == dest_uid { - // Nothing to do in the trivial case. - return Ok(()); - } - - if uid_to_android_user(src_uid) != uid_to_android_user(dest_uid) { - return Err(Error::AndroidUserMismatch).context("In move_keystore_entry."); - } - - let prefixes = ["USRPKEY", "USRSKEY", "USRCERT", "CACERT"]; - for prefix in prefixes { - Self::move_keystore_file_if_exists( - src_uid, - dest_uid, - src_alias, - dest_alias, - prefix, - |uid, alias, prefix| self.make_blob_filename(uid, alias, prefix), - ) - .with_context(|| { - format!( - "In move_keystore_entry: Trying to move blob file with prefix: \"{}\"", - prefix - ) - })?; - } - - let prefixes = ["USRPKEY", "USRSKEY"]; - - for prefix in prefixes { - Self::move_keystore_file_if_exists( - src_uid, - dest_uid, - src_alias, - dest_alias, - prefix, - |uid, alias, prefix| self.make_chr_filename(uid, alias, prefix), - ) - .with_context(|| { - format!( - "In move_keystore_entry: Trying to move characteristics file with \ - prefix: \"{}\"", - prefix - ) - })?; - } - - Ok(()) - } - fn remove_user_dir_if_empty(&self, user_id: u32) -> Result<()> { if self .is_empty_user(user_id) @@ -1267,66 +1004,79 @@ impl LegacyBlobLoader { &self, uid: u32, alias: &str, - super_key: &Option<Arc<dyn AesGcm>>, - ) -> Result<(Option<(Blob, LegacyKeyCharacteristics)>, Option<Vec<u8>>, Option<Vec<u8>>)> { + key_manager: Option<&SuperKeyManager>, + ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>)> { let km_blob = self.read_km_blob_file(uid, alias).context("In load_by_uid_alias.")?; let km_blob = match km_blob { Some((km_blob, prefix)) => { - let km_blob = - match km_blob { - Blob { flags: _, value: BlobValue::Decrypted(_) } - | Blob { flags: _, value: BlobValue::Encrypted { .. } } => km_blob, - _ => return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context( + let km_blob = match km_blob { + Blob { flags: _, value: BlobValue::Decrypted(_) } => km_blob, + // Unwrap the key blob if required and if we have key_manager. + Blob { flags, value: BlobValue::Encrypted { ref iv, ref tag, ref data } } => { + if let Some(key_manager) = key_manager { + let decrypted = match key_manager + .get_per_boot_key_by_user_id(uid_to_android_user(uid)) + { + Some(key) => key.aes_gcm_decrypt(data, iv, tag).context( + "In load_by_uid_alias: while trying to decrypt legacy blob.", + )?, + None => { + return Err(KsError::Rc(ResponseCode::LOCKED)).context(format!( + concat!( + "In load_by_uid_alias: ", + "User {} has not unlocked the keystore yet.", + ), + uid_to_android_user(uid) + )) + } + }; + Blob { flags, value: BlobValue::Decrypted(decrypted) } + } else { + km_blob + } + } + _ => { + return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context( "In load_by_uid_alias: Found wrong blob type in legacy key blob file.", - ), - }; + ) + } + }; let hw_sec_level = match km_blob.is_strongbox() { true => SecurityLevel::STRONGBOX, false => SecurityLevel::TRUSTED_ENVIRONMENT, }; let key_parameters = self - .read_characteristics_file(uid, &prefix, alias, hw_sec_level, super_key) + .read_characteristics_file(uid, &prefix, alias, hw_sec_level) .context("In load_by_uid_alias.")?; Some((km_blob, key_parameters)) } None => None, }; - let user_cert_blob = - Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT")) - .context("In load_by_uid_alias: While loading user cert.")?; - - let user_cert = if let Some(blob) = user_cert_blob { - let blob = Self::decrypt_if_required(super_key, blob) - .context("In load_by_uid_alias: While decrypting user cert.")?; - - if let Blob { value: BlobValue::Generic(data), .. } = blob { - Some(data) - } else { - return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In load_by_uid_alias: Found unexpected blob type in USRCERT file"); - } - } else { - None - }; - - let ca_cert_blob = Self::read_generic_blob(&self.make_blob_filename(uid, alias, "CACERT")) - .context("In load_by_uid_alias: While loading ca cert.")?; - - let ca_cert = if let Some(blob) = ca_cert_blob { - let blob = Self::decrypt_if_required(super_key, blob) - .context("In load_by_uid_alias: While decrypting ca cert.")?; + let user_cert = + match Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT")) + .context("In load_by_uid_alias: While loading user cert.")? + { + Some(Blob { value: BlobValue::Generic(data), .. }) => Some(data), + None => None, + _ => { + return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context( + "In load_by_uid_alias: Found unexpected blob type in USRCERT file", + ) + } + }; - if let Blob { value: BlobValue::Generic(data), .. } = blob { - Some(data) - } else { + let ca_cert = match Self::read_generic_blob(&self.make_blob_filename(uid, alias, "CACERT")) + .context("In load_by_uid_alias: While loading ca cert.")? + { + Some(Blob { value: BlobValue::Generic(data), .. }) => Some(data), + None => None, + _ => { return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)) - .context("In load_by_uid_alias: Found unexpected blob type in CACERT file"); + .context("In load_by_uid_alias: Found unexpected blob type in CACERT file") } - } else { - None }; Ok((km_blob, user_cert, ca_cert)) @@ -1387,318 +1137,17 @@ impl LegacyBlobLoader { } } -/// This module implements utility apis for creating legacy blob files. -#[cfg(feature = "keystore2_blob_test_utils")] -pub mod test_utils { - #![allow(dead_code)] - - /// test vectors for legacy key blobs - pub mod legacy_blob_test_vectors; - - use crate::legacy_blob::blob_types::{ - GENERIC, KEY_CHARACTERISTICS, KEY_CHARACTERISTICS_CACHE, KM_BLOB, SUPER_KEY, - SUPER_KEY_AES256, - }; - use crate::legacy_blob::*; - use anyhow::{anyhow, Result}; - use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt}; - use std::convert::TryInto; - use std::fs::OpenOptions; - use std::io::Write; - - /// This function takes a blob and synchronizes the encrypted/super encrypted flags - /// with the blob type for the pairs Generic/EncryptedGeneric, - /// Characteristics/EncryptedCharacteristics and Encrypted/Decrypted. - /// E.g. if a non encrypted enum variant is encountered with flags::SUPER_ENCRYPTED - /// or flags::ENCRYPTED is set, the payload is encrypted and the corresponding - /// encrypted variant is returned, and vice versa. All other variants remain untouched - /// even if flags and BlobValue variant are inconsistent. - pub fn prepare_blob(blob: Blob, key: &[u8]) -> Result<Blob> { - match blob { - Blob { value: BlobValue::Generic(data), flags } if blob.is_encrypted() => { - let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap(); - Ok(Blob { value: BlobValue::EncryptedGeneric { data: ciphertext, iv, tag }, flags }) - } - Blob { value: BlobValue::Characteristics(data), flags } if blob.is_encrypted() => { - let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap(); - Ok(Blob { - value: BlobValue::EncryptedCharacteristics { data: ciphertext, iv, tag }, - flags, - }) - } - Blob { value: BlobValue::Decrypted(data), flags } if blob.is_encrypted() => { - let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap(); - Ok(Blob { value: BlobValue::Encrypted { data: ciphertext, iv, tag }, flags }) - } - Blob { value: BlobValue::EncryptedGeneric { data, iv, tag }, flags } - if !blob.is_encrypted() => - { - let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap(); - Ok(Blob { value: BlobValue::Generic(plaintext[..].to_vec()), flags }) - } - Blob { value: BlobValue::EncryptedCharacteristics { data, iv, tag }, flags } - if !blob.is_encrypted() => - { - let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap(); - Ok(Blob { value: BlobValue::Characteristics(plaintext[..].to_vec()), flags }) - } - Blob { value: BlobValue::Encrypted { data, iv, tag }, flags } - if !blob.is_encrypted() => - { - let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap(); - Ok(Blob { value: BlobValue::Decrypted(plaintext), flags }) - } - _ => Ok(blob), - } - } - - /// Legacy blob header structure. - pub struct LegacyBlobHeader { - version: u8, - blob_type: u8, - flags: u8, - info: u8, - iv: [u8; 12], - tag: [u8; 16], - blob_size: u32, - } - - /// This function takes a Blob and writes it to out as a legacy blob file - /// version 3. Note that the flags field and the values field may be - /// inconsistent and could be sanitized by this function. It is intentionally - /// not done to enable tests to construct malformed blobs. - pub fn write_legacy_blob(out: &mut dyn Write, blob: Blob) -> Result<usize> { - let (header, data, salt) = match blob { - Blob { value: BlobValue::Generic(data), flags } => ( - LegacyBlobHeader { - version: 3, - blob_type: GENERIC, - flags, - info: 0, - iv: [0u8; 12], - tag: [0u8; 16], - blob_size: data.len() as u32, - }, - data, - None, - ), - Blob { value: BlobValue::Characteristics(data), flags } => ( - LegacyBlobHeader { - version: 3, - blob_type: KEY_CHARACTERISTICS, - flags, - info: 0, - iv: [0u8; 12], - tag: [0u8; 16], - blob_size: data.len() as u32, - }, - data, - None, - ), - Blob { value: BlobValue::CharacteristicsCache(data), flags } => ( - LegacyBlobHeader { - version: 3, - blob_type: KEY_CHARACTERISTICS_CACHE, - flags, - info: 0, - iv: [0u8; 12], - tag: [0u8; 16], - blob_size: data.len() as u32, - }, - data, - None, - ), - Blob { value: BlobValue::PwEncrypted { iv, tag, data, salt, key_size }, flags } => ( - LegacyBlobHeader { - version: 3, - blob_type: if key_size == keystore2_crypto::AES_128_KEY_LENGTH { - SUPER_KEY - } else { - SUPER_KEY_AES256 - }, - flags, - info: 0, - iv: iv.try_into().unwrap(), - tag: tag[..].try_into().unwrap(), - blob_size: data.len() as u32, - }, - data, - Some(salt), - ), - Blob { value: BlobValue::Encrypted { iv, tag, data }, flags } => ( - LegacyBlobHeader { - version: 3, - blob_type: KM_BLOB, - flags, - info: 0, - iv: iv.try_into().unwrap(), - tag: tag[..].try_into().unwrap(), - blob_size: data.len() as u32, - }, - data, - None, - ), - Blob { value: BlobValue::EncryptedGeneric { iv, tag, data }, flags } => ( - LegacyBlobHeader { - version: 3, - blob_type: GENERIC, - flags, - info: 0, - iv: iv.try_into().unwrap(), - tag: tag[..].try_into().unwrap(), - blob_size: data.len() as u32, - }, - data, - None, - ), - Blob { value: BlobValue::EncryptedCharacteristics { iv, tag, data }, flags } => ( - LegacyBlobHeader { - version: 3, - blob_type: KEY_CHARACTERISTICS, - flags, - info: 0, - iv: iv.try_into().unwrap(), - tag: tag[..].try_into().unwrap(), - blob_size: data.len() as u32, - }, - data, - None, - ), - Blob { value: BlobValue::Decrypted(data), flags } => ( - LegacyBlobHeader { - version: 3, - blob_type: KM_BLOB, - flags, - info: 0, - iv: [0u8; 12], - tag: [0u8; 16], - blob_size: data.len() as u32, - }, - data[..].to_vec(), - None, - ), - }; - write_legacy_blob_helper(out, &header, &data, salt.as_deref()) - } - - /// This function takes LegacyBlobHeader, blob payload and writes it to out as a legacy blob file - /// version 3. - pub fn write_legacy_blob_helper( - out: &mut dyn Write, - header: &LegacyBlobHeader, - data: &[u8], - info: Option<&[u8]>, - ) -> Result<usize> { - if 1 != out.write(&[header.version])? { - return Err(anyhow!("Unexpected size while writing version.")); - } - if 1 != out.write(&[header.blob_type])? { - return Err(anyhow!("Unexpected size while writing blob_type.")); - } - if 1 != out.write(&[header.flags])? { - return Err(anyhow!("Unexpected size while writing flags.")); - } - if 1 != out.write(&[header.info])? { - return Err(anyhow!("Unexpected size while writing info.")); - } - if 12 != out.write(&header.iv)? { - return Err(anyhow!("Unexpected size while writing iv.")); - } - if 4 != out.write(&[0u8; 4])? { - return Err(anyhow!("Unexpected size while writing last 4 bytes of iv.")); - } - if 16 != out.write(&header.tag)? { - return Err(anyhow!("Unexpected size while writing tag.")); - } - if 4 != out.write(&header.blob_size.to_be_bytes())? { - return Err(anyhow!("Unexpected size while writing blob size.")); - } - if data.len() != out.write(data)? { - return Err(anyhow!("Unexpected size while writing blob.")); - } - if let Some(info) = info { - if info.len() != out.write(info)? { - return Err(anyhow!("Unexpected size while writing inof.")); - } - } - Ok(40 + data.len() + info.map(|v| v.len()).unwrap_or(0)) - } - - /// Create encrypted characteristics file using given key. - pub fn make_encrypted_characteristics_file<P: AsRef<Path>>( - path: P, - key: &[u8], - data: &[u8], - ) -> Result<()> { - let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap(); - let blob = - Blob { value: BlobValue::Characteristics(data.to_vec()), flags: flags::ENCRYPTED }; - let blob = prepare_blob(blob, key).unwrap(); - write_legacy_blob(&mut file, blob).unwrap(); - Ok(()) - } - - /// Create encrypted user certificate file using given key. - pub fn make_encrypted_usr_cert_file<P: AsRef<Path>>( - path: P, - key: &[u8], - data: &[u8], - ) -> Result<()> { - let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap(); - let blob = Blob { value: BlobValue::Generic(data.to_vec()), flags: flags::ENCRYPTED }; - let blob = prepare_blob(blob, key).unwrap(); - write_legacy_blob(&mut file, blob).unwrap(); - Ok(()) - } - - /// Create encrypted CA certificate file using given key. - pub fn make_encrypted_ca_cert_file<P: AsRef<Path>>( - path: P, - key: &[u8], - data: &[u8], - ) -> Result<()> { - let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap(); - let blob = Blob { value: BlobValue::Generic(data.to_vec()), flags: flags::ENCRYPTED }; - let blob = prepare_blob(blob, key).unwrap(); - write_legacy_blob(&mut file, blob).unwrap(); - Ok(()) - } - - /// Create encrypted user key file using given key. - pub fn make_encrypted_key_file<P: AsRef<Path>>(path: P, key: &[u8], data: &[u8]) -> Result<()> { - let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap(); - let blob = Blob { - value: BlobValue::Decrypted(ZVec::try_from(data).unwrap()), - flags: flags::ENCRYPTED, - }; - let blob = prepare_blob(blob, key).unwrap(); - write_legacy_blob(&mut file, blob).unwrap(); - Ok(()) - } - - /// Create user or ca cert blob file. - pub fn make_cert_blob_file<P: AsRef<Path>>(path: P, data: &[u8]) -> Result<()> { - let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap(); - let blob = Blob { value: BlobValue::Generic(data.to_vec()), flags: 0 }; - let blob = prepare_blob(blob, &[]).unwrap(); - write_legacy_blob(&mut file, blob).unwrap(); - Ok(()) - } -} - #[cfg(test)] mod test { - #![allow(dead_code)] use super::*; - use crate::legacy_blob::test_utils::legacy_blob_test_vectors::*; - use crate::legacy_blob::test_utils::*; - use anyhow::{anyhow, Result}; + use anyhow::anyhow; use keystore2_crypto::aes_gcm_decrypt; - use keystore2_test_utils::TempDir; use rand::Rng; - use std::convert::TryInto; - use std::ops::Deref; use std::string::FromUtf8Error; + mod legacy_blob_test_vectors; + use crate::error; + use crate::legacy_blob::test::legacy_blob_test_vectors::*; + use keystore2_test_utils::TempDir; #[test] fn decode_encode_alias_test() { @@ -1754,8 +1203,7 @@ mod test { fn read_golden_key_blob_test() -> anyhow::Result<()> { let blob = LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*BLOB, |_, _, _, _, _| { Err(anyhow!("should not be called")) - }) - .unwrap(); + })?; assert!(!blob.is_encrypted()); assert!(!blob.is_fallback()); assert!(!blob.is_strongbox()); @@ -1765,8 +1213,7 @@ mod test { let blob = LegacyBlobLoader::new_from_stream_decrypt_with( &mut &*REAL_LEGACY_BLOB, |_, _, _, _, _| Err(anyhow!("should not be called")), - ) - .unwrap(); + )?; assert!(!blob.is_encrypted()); assert!(!blob.is_fallback()); assert!(!blob.is_strongbox()); @@ -1854,75 +1301,62 @@ mod test { #[test] fn test_legacy_blobs() -> anyhow::Result<()> { - let temp_dir = TempDir::new("legacy_blob_test").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); + let temp_dir = TempDir::new("legacy_blob_test")?; + std::fs::create_dir(&*temp_dir.build().push("user_0"))?; - std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); + std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY)?; std::fs::write( &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), USRPKEY_AUTHBOUND, - ) - .unwrap(); + )?; std::fs::write( &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), USRPKEY_AUTHBOUND_CHR, - ) - .unwrap(); + )?; std::fs::write( &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), USRCERT_AUTHBOUND, - ) - .unwrap(); + )?; std::fs::write( &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), CACERT_AUTHBOUND, - ) - .unwrap(); + )?; std::fs::write( &*temp_dir.build().push("user_0").push("10223_USRPKEY_non_authbound"), USRPKEY_NON_AUTHBOUND, - ) - .unwrap(); + )?; std::fs::write( &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_non_authbound"), USRPKEY_NON_AUTHBOUND_CHR, - ) - .unwrap(); + )?; std::fs::write( &*temp_dir.build().push("user_0").push("10223_USRCERT_non_authbound"), USRCERT_NON_AUTHBOUND, - ) - .unwrap(); + )?; std::fs::write( &*temp_dir.build().push("user_0").push("10223_CACERT_non_authbound"), CACERT_NON_AUTHBOUND, - ) - .unwrap(); + )?; + let key_manager: SuperKeyManager = Default::default(); + let mut db = crate::database::KeystoreDB::new(temp_dir.path(), None)?; let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) = - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? - { - assert_eq!(flags, 4); - assert_eq!( - value, - BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - ); - assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND); - assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND); - } else { - panic!(""); - } + assert_eq!( + legacy_blob_loader + .load_by_uid_alias(10223, "authbound", Some(&key_manager)) + .unwrap_err() + .root_cause() + .downcast_ref::<error::Error>(), + Some(&error::Error::Rc(ResponseCode::LOCKED)) + ); + + key_manager.unlock_user_key(&mut db, 0, &(PASSWORD.into()), &legacy_blob_loader)?; if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) = - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? + legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))? { assert_eq!(flags, 4); //assert_eq!(value, BlobValue::Encrypted(..)); @@ -1932,7 +1366,7 @@ mod test { panic!(""); } if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) = - legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)? + legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))? { assert_eq!(flags, 0); assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?)); @@ -1949,11 +1383,11 @@ mod test { assert_eq!( (None, None, None), - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? + legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))? ); assert_eq!( (None, None, None), - legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)? + legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))? ); // The database should not be empty due to the super key. @@ -1972,319 +1406,9 @@ mod test { Ok(()) } - struct TestKey(ZVec); - - impl crate::utils::AesGcmKey for TestKey { - fn key(&self) -> &[u8] { - &self.0 - } - } - - impl Deref for TestKey { - type Target = [u8]; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[test] - fn test_with_encrypted_characteristics() -> anyhow::Result<()> { - let temp_dir = TempDir::new("test_with_encrypted_characteristics").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); - let super_key = - Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); - - std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); - - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), - USRPKEY_AUTHBOUND, - ) - .unwrap(); - make_encrypted_characteristics_file( - &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), - &super_key, - KEY_PARAMETERS, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), - USRCERT_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), - CACERT_AUTHBOUND, - ) - .unwrap(); - - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert_eq!( - legacy_blob_loader - .load_by_uid_alias(10223, "authbound", &None) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>(), - Some(&Error::LockedComponent) - ); - - assert_eq!( - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(), - ( - Some(( - Blob { - flags: 4, - value: BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - }, - structured_test_params() - )), - Some(LOADED_CERT_AUTHBOUND.to_vec()), - Some(LOADED_CACERT_AUTHBOUND.to_vec()) - ) - ); - - legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed."); - - assert_eq!( - (None, None, None), - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap() - ); - - // The database should not be empty due to the super key. - assert!(!legacy_blob_loader.is_empty().unwrap()); - assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); - - // The database should be considered empty for user 1. - assert!(legacy_blob_loader.is_empty_user(1).unwrap()); - - legacy_blob_loader.remove_super_key(0); - - // Now it should be empty. - assert!(legacy_blob_loader.is_empty_user(0).unwrap()); - assert!(legacy_blob_loader.is_empty().unwrap()); - - Ok(()) - } - - #[test] - fn test_with_encrypted_certificates() -> anyhow::Result<()> { - let temp_dir = TempDir::new("test_with_encrypted_certificates").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); - let super_key = - Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); - - std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); - - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), - USRPKEY_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), - USRPKEY_AUTHBOUND_CHR, - ) - .unwrap(); - make_encrypted_usr_cert_file( - &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), - &super_key, - LOADED_CERT_AUTHBOUND, - ) - .unwrap(); - make_encrypted_ca_cert_file( - &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), - &super_key, - LOADED_CACERT_AUTHBOUND, - ) - .unwrap(); - - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert_eq!( - legacy_blob_loader - .load_by_uid_alias(10223, "authbound", &None) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>(), - Some(&Error::LockedComponent) - ); - - assert_eq!( - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(), - ( - Some(( - Blob { - flags: 4, - value: BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - }, - structured_test_params_cache() - )), - Some(LOADED_CERT_AUTHBOUND.to_vec()), - Some(LOADED_CACERT_AUTHBOUND.to_vec()) - ) - ); - - legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed."); - - assert_eq!( - (None, None, None), - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap() - ); - - // The database should not be empty due to the super key. - assert!(!legacy_blob_loader.is_empty().unwrap()); - assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); - - // The database should be considered empty for user 1. - assert!(legacy_blob_loader.is_empty_user(1).unwrap()); - - legacy_blob_loader.remove_super_key(0); - - // Now it should be empty. - assert!(legacy_blob_loader.is_empty_user(0).unwrap()); - assert!(legacy_blob_loader.is_empty().unwrap()); - - Ok(()) - } - - #[test] - fn test_in_place_key_migration() -> anyhow::Result<()> { - let temp_dir = TempDir::new("test_in_place_key_migration").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); - let super_key = - Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); - - std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); - - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), - USRPKEY_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), - USRPKEY_AUTHBOUND_CHR, - ) - .unwrap(); - make_encrypted_usr_cert_file( - &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), - &super_key, - LOADED_CERT_AUTHBOUND, - ) - .unwrap(); - make_encrypted_ca_cert_file( - &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), - &super_key, - LOADED_CACERT_AUTHBOUND, - ) - .unwrap(); - - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert_eq!( - legacy_blob_loader - .load_by_uid_alias(10223, "authbound", &None) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>(), - Some(&Error::LockedComponent) - ); - - let super_key: Option<Arc<dyn AesGcm>> = Some(super_key); - - assert_eq!( - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(), - ( - Some(( - Blob { - flags: 4, - value: BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - }, - structured_test_params_cache() - )), - Some(LOADED_CERT_AUTHBOUND.to_vec()), - Some(LOADED_CACERT_AUTHBOUND.to_vec()) - ) - ); - - legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap(); - - assert_eq!( - legacy_blob_loader - .load_by_uid_alias(10224, "boundauth", &None) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>(), - Some(&Error::LockedComponent) - ); - - assert_eq!( - legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(), - ( - Some(( - Blob { - flags: 4, - value: BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - }, - structured_test_params_cache() - )), - Some(LOADED_CERT_AUTHBOUND.to_vec()), - Some(LOADED_CACERT_AUTHBOUND.to_vec()) - ) - ); - - legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed."); - - assert_eq!( - (None, None, None), - legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap() - ); - - // The database should not be empty due to the super key. - assert!(!legacy_blob_loader.is_empty().unwrap()); - assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); - - // The database should be considered empty for user 1. - assert!(legacy_blob_loader.is_empty_user(1).unwrap()); - - legacy_blob_loader.remove_super_key(0); - - // Now it should be empty. - assert!(legacy_blob_loader.is_empty_user(0).unwrap()); - assert!(legacy_blob_loader.is_empty().unwrap()); - - Ok(()) - } - #[test] fn list_non_existing_user() -> Result<()> { - let temp_dir = TempDir::new("list_non_existing_user").unwrap(); + let temp_dir = TempDir::new("list_non_existing_user")?; let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); assert!(legacy_blob_loader.list_user(20)?.is_empty()); @@ -2294,66 +1418,11 @@ mod test { #[test] fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> { - let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap(); + let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user")?; let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty()); Ok(()) } - - #[test] - fn test_move_keystore_entry() { - let temp_dir = TempDir::new("test_move_keystore_entry").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - const SOME_CONTENT: &[u8] = b"some content"; - const ANOTHER_CONTENT: &[u8] = b"another content"; - const SOME_FILENAME: &str = "some_file"; - const ANOTHER_FILENAME: &str = "another_file"; - - std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT) - .unwrap(); - - std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT) - .unwrap(); - - // Non existent source id silently ignored. - assert!(LegacyBlobLoader::move_keystore_file_if_exists( - 1, - 2, - "non_existent", - ANOTHER_FILENAME, - "ignored", - |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf() - ) - .is_ok()); - - // Content of another_file has not changed. - let another_content = - std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap(); - assert_eq!(&another_content, ANOTHER_CONTENT); - - // Check that some_file still exists. - assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists()); - // Existing target files are silently overwritten. - - assert!(LegacyBlobLoader::move_keystore_file_if_exists( - 1, - 2, - SOME_FILENAME, - ANOTHER_FILENAME, - "ignored", - |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf() - ) - .is_ok()); - - // Content of another_file is now "some content". - let another_content = - std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap(); - assert_eq!(&another_content, SOME_CONTENT); - - // Check that some_file no longer exists. - assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists()); - } } diff --git a/keystore2/src/legacy_blob/test_utils/legacy_blob_test_vectors.rs b/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs index 3eecee08..14bd40ca 100644 --- a/keystore2/src/legacy_blob/test_utils/legacy_blob_test_vectors.rs +++ b/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs @@ -12,15 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::key_parameter::{KeyParameter, KeyParameterValue}; -use crate::legacy_blob::LegacyKeyCharacteristics; -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, - HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin, - KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, -}; - -/// Holds Blob structure. pub static BLOB: &[u8] = &[ 3, // version 1, // type @@ -31,109 +22,6 @@ pub static BLOB: &[u8] = &[ 0, 0, 0, 4, // length in big endian 0xde, 0xed, 0xbe, 0xef, // payload ]; - -/// Creates LegacyKeyCharacteristics with security level KEYSTORE. -pub fn structured_test_params() -> LegacyKeyCharacteristics { - LegacyKeyCharacteristics::File(vec![ - KeyParameter::new(KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), SecurityLevel::KEYSTORE), - KeyParameter::new( - KeyParameterValue::KeyPurpose(KeyPurpose::VERIFY), - SecurityLevel::KEYSTORE, - ), - KeyParameter::new(KeyParameterValue::Digest(Digest::SHA_2_256), SecurityLevel::KEYSTORE), - KeyParameter::new( - KeyParameterValue::UserSecureID(2100322049669824240), - SecurityLevel::KEYSTORE, - ), - KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::EC), SecurityLevel::KEYSTORE), - KeyParameter::new(KeyParameterValue::KeySize(256), SecurityLevel::KEYSTORE), - KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::KEYSTORE), - KeyParameter::new( - KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::FINGERPRINT), - SecurityLevel::KEYSTORE, - ), - KeyParameter::new( - KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED), - SecurityLevel::KEYSTORE, - ), - KeyParameter::new(KeyParameterValue::OSVersion(110000), SecurityLevel::KEYSTORE), - KeyParameter::new(KeyParameterValue::OSPatchLevel(202101), SecurityLevel::KEYSTORE), - KeyParameter::new(KeyParameterValue::BootPatchLevel(20210105), SecurityLevel::KEYSTORE), - KeyParameter::new(KeyParameterValue::VendorPatchLevel(20210105), SecurityLevel::KEYSTORE), - ]) -} - -/// Creates LegacyKeyCharacteristics with security level TRUSTED_ENVIRONMENT. -pub fn structured_test_params_cache() -> LegacyKeyCharacteristics { - LegacyKeyCharacteristics::Cache(vec![ - KeyParameter::new( - KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::KeyPurpose(KeyPurpose::VERIFY), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::Digest(Digest::SHA_2_256), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::UserSecureID(2100322049669824240), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::Algorithm(Algorithm::EC), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::KeySize(256), SecurityLevel::TRUSTED_ENVIRONMENT), - KeyParameter::new( - KeyParameterValue::EcCurve(EcCurve::P_256), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::FINGERPRINT), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::OSVersion(110000), SecurityLevel::TRUSTED_ENVIRONMENT), - KeyParameter::new( - KeyParameterValue::OSPatchLevel(202101), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::BootPatchLevel(20210105), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::VendorPatchLevel(20210105), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::CreationDateTime(1607149002000), - SecurityLevel::KEYSTORE, - ), - KeyParameter::new(KeyParameterValue::UserID(0), SecurityLevel::KEYSTORE), - ]) -} - -/// One encoded list of key parameters. -pub static KEY_PARAMETERS: &[u8] = &[ - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x20, - 0x04, 0x00, 0x00, 0x00, 0xf6, 0x01, 0x00, 0xa0, 0xf0, 0x7e, 0x7d, 0xb4, 0xc6, 0xd7, 0x25, 0x1d, - 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x30, 0x00, 0x01, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x2d, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, - 0xf8, 0x01, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0xbe, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, - 0xc1, 0x02, 0x00, 0x30, 0xb0, 0xad, 0x01, 0x00, 0xc2, 0x02, 0x00, 0x30, 0x75, 0x15, 0x03, 0x00, - 0xcf, 0x02, 0x00, 0x30, 0xb9, 0x61, 0x34, 0x01, 0xce, 0x02, 0x00, 0x30, 0xb9, 0x61, 0x34, 0x01, - 0x30, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, -]; - -/// Real legacy blob. pub static REAL_LEGACY_BLOB: &[u8] = &[ 0x03, 0x04, 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, @@ -165,7 +53,6 @@ pub static REAL_LEGACY_BLOB: &[u8] = &[ 0xda, 0x40, 0x2b, 0x75, 0xd0, 0xd2, 0x81, 0x7f, 0xe2, 0x2b, 0xef, 0x64, ]; -/// Real legacy blob payload. pub static REAL_LEGACY_BLOB_PAYLOAD: &[u8] = &[ 0x6c, 0x01, 0x00, 0x00, 0x00, 0x32, 0x00, 0x25, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x80, 0x00, 0x43, 0x00, 0x20, 0x85, 0x42, 0x9e, 0xe9, 0x34, 0x85, 0x2a, 0x00, @@ -195,13 +82,11 @@ pub static REAL_LEGACY_BLOB_PAYLOAD: &[u8] = &[ 0xe2, 0x2b, 0xef, 0x64, ]; -/// AES key blob. pub static AES_KEY: &[u8] = &[ 0x48, 0xe4, 0xb5, 0xff, 0xcd, 0x9c, 0x41, 0x1e, 0x20, 0x41, 0xf2, 0x65, 0xa0, 0x4f, 0xf6, 0x57, 0xc6, 0x58, 0xca, 0xbf, 0x28, 0xa3, 0x01, 0x98, 0x01, 0x76, 0x10, 0xc0, 0x30, 0x4e, 0x35, 0x6e, ]; -/// AES-GCM encrypted blob. pub static AES_GCM_ENCRYPTED_BLOB: &[u8] = &[ 0x03, 0x04, 0x04, 0x00, 0xbd, 0xdb, 0x8d, 0x69, 0x72, 0x56, 0xf0, 0xf5, 0xa4, 0x02, 0x88, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x50, 0xd9, 0x97, 0x95, 0x37, 0x6e, 0x28, 0x6a, 0x28, 0x9d, 0x51, 0xb9, @@ -234,7 +119,6 @@ pub static AES_GCM_ENCRYPTED_BLOB: &[u8] = &[ 0x2e, 0x0c, 0xc7, 0xbf, 0x29, 0x1e, 0x31, 0xdc, 0x0e, 0x85, 0x96, 0x7b, ]; -/// Decrypted payload. pub static DECRYPTED_PAYLOAD: &[u8] = &[ 0x7c, 0x01, 0x00, 0x00, 0x00, 0x32, 0x00, 0x25, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x80, 0x00, 0x43, 0x00, 0x20, 0xa4, 0xee, 0xdc, 0x1f, 0x9e, 0xba, 0x42, 0xd6, @@ -265,7 +149,6 @@ pub static DECRYPTED_PAYLOAD: &[u8] = &[ 0xf6, 0x0b, 0x81, 0x07, ]; -/// Password blob. pub static PASSWORD: &[u8] = &[ 0x42, 0x39, 0x30, 0x37, 0x44, 0x37, 0x32, 0x37, 0x39, 0x39, 0x43, 0x42, 0x39, 0x41, 0x42, 0x30, 0x34, 0x31, 0x30, 0x38, 0x46, 0x44, 0x33, 0x45, 0x39, 0x42, 0x32, 0x38, 0x36, 0x35, 0x41, 0x36, @@ -273,7 +156,6 @@ pub static PASSWORD: &[u8] = &[ 0x32, 0x45, 0x31, 0x35, 0x43, 0x43, 0x46, 0x32, 0x39, 0x36, 0x33, 0x34, 0x31, 0x32, 0x41, 0x39, ]; -/// Super key blob. pub static SUPERKEY: &[u8] = &[ 0x03, 0x07, 0x01, 0x10, 0x9a, 0x81, 0x56, 0x7d, 0xf5, 0x86, 0x7c, 0x62, 0xd7, 0xf9, 0x26, 0x06, 0x00, 0x00, 0x00, 0x00, 0xde, 0x2a, 0xcb, 0xac, 0x98, 0x57, 0x2b, 0xe5, 0x57, 0x18, 0x78, 0x57, @@ -282,29 +164,6 @@ pub static SUPERKEY: &[u8] = &[ 0x76, 0x04, 0x2a, 0x48, 0xd1, 0xa7, 0x59, 0xd1, 0x04, 0x5b, 0xb4, 0x8a, 0x09, 0x22, 0x13, 0x0c, 0x94, 0xb6, 0x67, 0x7b, 0x39, 0x85, 0x28, 0x11, ]; - -/// Super key IV. -pub static SUPERKEY_IV: &[u8] = &[ - 0x9a, 0x81, 0x56, 0x7d, 0xf5, 0x86, 0x7c, 0x62, 0xd7, 0xf9, 0x26, 0x06, 0x00, 0x00, 0x00, 0x00, -]; - -/// Super key tag. -pub static SUPERKEY_TAG: &[u8] = &[ - 0xde, 0x2a, 0xcb, 0xac, 0x98, 0x57, 0x2b, 0xe5, 0x57, 0x18, 0x78, 0x57, 0x6e, 0x10, 0x09, 0x84, -]; - -/// Super key salt. -pub static SUPERKEY_SALT: &[u8] = &[ - 0x04, 0x5b, 0xb4, 0x8a, 0x09, 0x22, 0x13, 0x0c, 0x94, 0xb6, 0x67, 0x7b, 0x39, 0x85, 0x28, 0x11, -]; - -/// Super key payload. -pub static SUPERKEY_PAYLOAD: &[u8] = &[ - 0xac, 0x6d, 0x13, 0xe6, 0xad, 0x2c, 0x89, 0x53, 0x1a, 0x99, 0xa5, 0x6c, 0x88, 0xe9, 0xeb, 0x5c, - 0xef, 0x68, 0x5e, 0x5b, 0x53, 0xa8, 0xe7, 0xa2, 0x76, 0x04, 0x2a, 0x48, 0xd1, 0xa7, 0x59, 0xd1, -]; - -/// user key blob. pub static USRPKEY_AUTHBOUND: &[u8] = &[ 0x03, 0x04, 0x04, 0x00, 0x1c, 0x34, 0x87, 0x6f, 0xc8, 0x35, 0x0d, 0x34, 0x88, 0x59, 0xbc, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x62, 0xe3, 0x38, 0x2d, 0xd0, 0x58, 0x40, 0xc1, 0xb0, 0xf2, 0x4a, 0xdd, @@ -344,56 +203,6 @@ pub static USRPKEY_AUTHBOUND: &[u8] = &[ 0xaf, 0x17, 0x2f, 0x21, 0x07, 0xea, 0x61, 0xff, 0x73, 0x08, 0x50, 0xb2, 0x19, 0xe8, 0x23, 0x1b, 0x83, 0x42, 0xdd, 0x4e, 0x6d, ]; - -/// Authbound IV. -pub static USRPKEY_AUTHBOUND_IV: &[u8] = &[ - 0x1c, 0x34, 0x87, 0x6f, 0xc8, 0x35, 0x0d, 0x34, 0x88, 0x59, 0xbc, 0xf5, 0x00, 0x00, 0x00, 0x00, -]; - -/// Authbond IV Tag. -pub static USRPKEY_AUTHBOUND_TAG: &[u8] = &[ - 0x62, 0xe3, 0x38, 0x2d, 0xd0, 0x58, 0x40, 0xc1, 0xb0, 0xf2, 0x4a, 0xdd, 0xf7, 0x81, 0x67, 0x0b, -]; - -/// Encrypted use key payload. -pub static USRPKEY_AUTHBOUND_ENC_PAYLOAD: &[u8] = &[ - 0x05, 0xb2, 0x5a, 0x1d, 0x1b, 0x25, 0x19, 0x48, 0xbf, 0x76, 0x0b, 0x37, 0x8c, 0x60, 0x52, 0xea, - 0x30, 0x2a, 0x2c, 0x89, 0x99, 0x95, 0x57, 0x5c, 0xec, 0x62, 0x3c, 0x08, 0x1a, 0xc6, 0x65, 0xf9, - 0xad, 0x24, 0x99, 0xf0, 0x5c, 0x44, 0xa0, 0xea, 0x9a, 0x60, 0xa2, 0xef, 0xf5, 0x27, 0x50, 0xba, - 0x9c, 0xef, 0xa6, 0x08, 0x88, 0x4b, 0x0f, 0xfe, 0x5d, 0x41, 0xac, 0xba, 0xef, 0x9d, 0xa4, 0xb7, - 0x72, 0xd3, 0xc8, 0x11, 0x92, 0x06, 0xf6, 0x26, 0xdf, 0x90, 0xe2, 0x66, 0x89, 0xf3, 0x85, 0x16, - 0x4a, 0xdf, 0x7f, 0xac, 0x94, 0x4a, 0x1c, 0xce, 0x18, 0xee, 0xf4, 0x1f, 0x8e, 0xd6, 0xaf, 0xfd, - 0x1d, 0xe5, 0x80, 0x4a, 0x6b, 0xbf, 0x91, 0xe2, 0x36, 0x1d, 0xb3, 0x53, 0x12, 0xfd, 0xc9, 0x0b, - 0xa6, 0x69, 0x00, 0x45, 0xcb, 0x4c, 0x40, 0x6b, 0x70, 0xcb, 0xd2, 0xa0, 0x44, 0x0b, 0x4b, 0xec, - 0xd6, 0x4f, 0x6f, 0x64, 0x37, 0xa7, 0xc7, 0x25, 0x54, 0xf4, 0xac, 0x6b, 0x34, 0x53, 0xea, 0x4e, - 0x56, 0x49, 0xba, 0xf4, 0x1e, 0xc6, 0x52, 0x8f, 0xf4, 0x85, 0xe7, 0xb5, 0xaf, 0x49, 0x68, 0xb3, - 0xb8, 0x7d, 0x63, 0xfc, 0x6e, 0x83, 0xa0, 0xf3, 0x91, 0x04, 0x80, 0xfd, 0xc5, 0x54, 0x7e, 0x92, - 0x1a, 0x87, 0x2c, 0x6e, 0xa6, 0x29, 0xb9, 0x1e, 0x3f, 0xef, 0x30, 0x12, 0x7b, 0x2f, 0xa2, 0x16, - 0x61, 0x8a, 0xcf, 0x14, 0x2d, 0x62, 0x98, 0x15, 0xae, 0x3b, 0xe6, 0x08, 0x1e, 0xb1, 0xf1, 0x21, - 0xb0, 0x50, 0xc0, 0x4b, 0x81, 0x71, 0x29, 0xe7, 0x86, 0xbf, 0x29, 0xe1, 0xeb, 0xfe, 0xbc, 0x11, - 0x3c, 0xc6, 0x15, 0x47, 0x9b, 0x41, 0x84, 0x61, 0x33, 0xbf, 0xca, 0xfe, 0x24, 0x92, 0x9e, 0x70, - 0x26, 0x36, 0x46, 0xca, 0xfe, 0xd3, 0x5a, 0x1d, 0x9e, 0x30, 0x19, 0xbd, 0x26, 0x49, 0xb4, 0x90, - 0x0c, 0x8d, 0xa2, 0x28, 0xa6, 0x24, 0x62, 0x6b, 0xe2, 0xfa, 0xe0, 0x53, 0xaa, 0x01, 0xeb, 0xaa, - 0x41, 0x2b, 0xcb, 0xb1, 0x08, 0x66, 0x9d, 0x21, 0x2d, 0x2a, 0x47, 0x44, 0xee, 0xd5, 0x06, 0xe3, - 0x4a, 0xb9, 0x3f, 0xcd, 0x78, 0x67, 0x89, 0x5b, 0xf7, 0x51, 0xc0, 0xc4, 0xa9, 0x68, 0xee, 0x44, - 0x9c, 0x47, 0xa4, 0xbd, 0x6f, 0x7b, 0xdd, 0x64, 0xa8, 0xc7, 0x1e, 0x77, 0x1d, 0x68, 0x87, 0xaa, - 0xae, 0x3c, 0xfc, 0x58, 0xb6, 0x3c, 0xcf, 0x58, 0xd0, 0x10, 0xaa, 0xef, 0xf0, 0x98, 0x67, 0x14, - 0x29, 0x4d, 0x40, 0x8b, 0xe5, 0xb1, 0xdf, 0x7f, 0x40, 0xb1, 0xd8, 0xea, 0x6c, 0xa8, 0xf7, 0x64, - 0xed, 0x02, 0x8d, 0xe7, 0x93, 0xfe, 0x79, 0x9a, 0x88, 0x62, 0x4f, 0xd0, 0x8a, 0x80, 0x36, 0x42, - 0x0a, 0xf1, 0xa2, 0x0e, 0x30, 0x39, 0xbd, 0x26, 0x1d, 0xd4, 0xf1, 0xc8, 0x6e, 0xdd, 0xc5, 0x41, - 0x29, 0xd8, 0xc1, 0x9e, 0x24, 0xf0, 0x25, 0x07, 0x05, 0x06, 0xc5, 0x08, 0xe3, 0x02, 0x2b, 0xe1, - 0x40, 0xc5, 0x67, 0xd2, 0x82, 0x96, 0x20, 0x80, 0xcf, 0x87, 0x3a, 0xc6, 0xb0, 0xbe, 0xcc, 0xbb, - 0x5a, 0x01, 0xab, 0xdd, 0x00, 0xc7, 0x0e, 0x7b, 0x02, 0x35, 0x27, 0xf4, 0x70, 0xfe, 0xd1, 0x19, - 0x6a, 0x64, 0x23, 0x9d, 0xba, 0xe9, 0x1d, 0x76, 0x90, 0xfe, 0x7f, 0xd6, 0xb5, 0xa0, 0xe7, 0xb9, - 0xf3, 0x56, 0x82, 0x8e, 0x57, 0x35, 0xf2, 0x69, 0xce, 0x52, 0xac, 0xc2, 0xf6, 0x5e, 0xb6, 0x54, - 0x95, 0x83, 0x3b, 0x9f, 0x48, 0xbb, 0x04, 0x06, 0xac, 0x55, 0xa9, 0xb9, 0xa3, 0xe7, 0x89, 0x6e, - 0x5c, 0x3a, 0x08, 0x67, 0x00, 0x8f, 0x1e, 0x26, 0x1b, 0x4d, 0x8a, 0xa6, 0x17, 0xa0, 0xa6, 0x18, - 0xe6, 0x31, 0x43, 0x15, 0xb8, 0x7f, 0x9e, 0xf5, 0x78, 0x58, 0x98, 0xb1, 0x8c, 0xf5, 0x22, 0x42, - 0x33, 0xc0, 0x42, 0x72, 0x4f, 0xce, 0x9f, 0x31, 0xaf, 0x17, 0x2f, 0x21, 0x07, 0xea, 0x61, 0xff, - 0x73, 0x08, 0x50, 0xb2, 0x19, 0xe8, 0x23, 0x1b, 0x83, 0x42, 0xdd, 0x4e, 0x6d, -]; - -/// User key characterstics blob. pub static USRPKEY_AUTHBOUND_CHR: &[u8] = &[ 0x03, 0x06, 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, @@ -409,8 +218,6 @@ pub static USRPKEY_AUTHBOUND_CHR: &[u8] = &[ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x00, 0x60, 0x10, 0x9d, 0x8b, 0x31, 0x76, 0x01, 0x00, 0x00, 0xf5, 0x01, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, ]; - -/// User certificate blob. pub static USRCERT_AUTHBOUND: &[u8] = &[ 0x03, 0x01, 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, @@ -457,8 +264,6 @@ pub static USRCERT_AUTHBOUND: &[u8] = &[ 0x39, 0x58, 0xe9, 0x89, 0x1a, 0x14, 0x41, 0x8d, 0xe0, 0xdc, 0x3d, 0x88, 0xf4, 0x2c, 0x7c, 0xda, 0xa1, 0x84, 0xfa, 0x7f, 0xf9, 0x07, 0x97, 0xfb, 0xb5, 0xb7, 0x28, 0x28, 0x00, 0x7c, 0xa7, ]; - -/// CA certificate blob. pub static CACERT_AUTHBOUND: &[u8] = &[ 0x03, 0x01, 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, @@ -647,7 +452,6 @@ pub static CACERT_AUTHBOUND: &[u8] = &[ 0xab, 0xae, 0x24, 0xe2, 0x44, 0x35, 0x16, 0x8d, 0x55, 0x3c, 0xe4, ]; -/// User non-authbond-key blob. pub static USRPKEY_NON_AUTHBOUND: &[u8] = &[ 0x03, 0x04, 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, @@ -687,8 +491,6 @@ pub static USRPKEY_NON_AUTHBOUND: &[u8] = &[ 0x46, 0xf0, 0xee, 0x50, 0x73, 0x6a, 0x7b, 0xa3, 0xe9, 0xb1, 0x08, 0x81, 0x00, 0xdf, 0x0e, 0xc9, 0xc3, 0x2c, 0x13, 0x64, 0xa1, ]; - -/// User non-authbond-key characteristics blob. pub static USRPKEY_NON_AUTHBOUND_CHR: &[u8] = &[ 0x03, 0x06, 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, @@ -704,7 +506,6 @@ pub static USRPKEY_NON_AUTHBOUND_CHR: &[u8] = &[ 0x60, 0x60, 0x60, 0x8c, 0x31, 0x76, 0x01, 0x00, 0x00, 0xf5, 0x01, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, ]; -/// User non-authbond-key certificate blob. pub static USRCERT_NON_AUTHBOUND: &[u8] = &[ 0x03, 0x01, 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, @@ -752,7 +553,6 @@ pub static USRCERT_NON_AUTHBOUND: &[u8] = &[ 0xd8, 0xd5, 0xd1, 0x64, 0x4c, 0x05, 0xdd, 0x13, 0x0e, 0xa4, 0xf3, 0x38, 0xbf, 0x18, 0xd5, ]; -/// User non-authbond-key ca-certs blob. pub static CACERT_NON_AUTHBOUND: &[u8] = &[ 0x03, 0x01, 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, @@ -941,7 +741,6 @@ pub static CACERT_NON_AUTHBOUND: &[u8] = &[ 0xab, 0xae, 0x24, 0xe2, 0x44, 0x35, 0x16, 0x8d, 0x55, 0x3c, 0xe4, ]; -/// User decrypted authbond-key blob. pub static _DECRYPTED_USRPKEY_AUTHBOUND: &[u8] = &[ 0x44, 0x4b, 0x4d, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc6, 0x15, 0x3a, 0x08, 0x1e, 0x43, 0xba, 0x7a, 0x0f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, @@ -979,7 +778,6 @@ pub static _DECRYPTED_USRPKEY_AUTHBOUND: &[u8] = &[ 0x60, 0x5e, 0xcd, 0xce, 0x3a, 0xd8, 0x09, 0xeb, 0x9d, 0x40, 0xdb, 0x58, 0x53, ]; -/// User loaded authbond certs blob. pub static LOADED_CERT_AUTHBOUND: &[u8] = &[ 0x30, 0x82, 0x02, 0x93, 0x30, 0x82, 0x02, 0x3A, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x30, 0x29, 0x31, 0x19, @@ -1024,8 +822,6 @@ pub static LOADED_CERT_AUTHBOUND: &[u8] = &[ 0xE0, 0xDC, 0x3D, 0x88, 0xF4, 0x2C, 0x7C, 0xDA, 0xA1, 0x84, 0xFA, 0x7F, 0xF9, 0x07, 0x97, 0xFB, 0xB5, 0xB7, 0x28, 0x28, 0x00, 0x7C, 0xA7, ]; - -/// User loaded authbond ca-certs blob. pub static LOADED_CACERT_AUTHBOUND: &[u8] = &[ 0x30, 0x82, 0x02, 0x26, 0x30, 0x82, 0x01, 0xAB, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x0A, 0x05, 0x84, 0x20, 0x26, 0x90, 0x76, 0x23, 0x58, 0x71, 0x77, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, @@ -1212,7 +1008,6 @@ pub static LOADED_CACERT_AUTHBOUND: &[u8] = &[ 0x55, 0x3C, 0xE4, ]; -/// User loaded non-authbond user key blob. pub static LOADED_USRPKEY_NON_AUTHBOUND: &[u8] = &[ 0x44, 0x4b, 0x4d, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x8a, 0xc1, 0x08, 0x13, 0x7c, 0x47, 0xba, 0x09, 0x0e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, @@ -1250,7 +1045,6 @@ pub static LOADED_USRPKEY_NON_AUTHBOUND: &[u8] = &[ 0xe9, 0xb1, 0x08, 0x81, 0x00, 0xdf, 0x0e, 0xc9, 0xc3, 0x2c, 0x13, 0x64, 0xa1, ]; -/// User loaded non-authbond certificate blob. pub static LOADED_CERT_NON_AUTHBOUND: &[u8] = &[ 0x30, 0x82, 0x02, 0x93, 0x30, 0x82, 0x02, 0x39, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x29, 0x31, 0x19, @@ -1296,7 +1090,6 @@ pub static LOADED_CERT_NON_AUTHBOUND: &[u8] = &[ 0x0e, 0xa4, 0xf3, 0x38, 0xbf, 0x18, 0xd5, ]; -/// User loaded non-authbond ca-certificates blob. pub static LOADED_CACERT_NON_AUTHBOUND: &[u8] = &[ 0x30, 0x82, 0x02, 0x26, 0x30, 0x82, 0x01, 0xab, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x0a, 0x05, 0x84, 0x20, 0x26, 0x90, 0x76, 0x23, 0x58, 0x71, 0x77, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, diff --git a/keystore2/src/legacy_importer.rs b/keystore2/src/legacy_migrator.rs index 93e17358..f92fd459 100644 --- a/keystore2/src/legacy_importer.rs +++ b/keystore2/src/legacy_migrator.rs @@ -14,19 +14,18 @@ //! This module acts as a bridge between the legacy key database and the keystore2 database. -use crate::database::{ - BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, EncryptedBy, KeyMetaData, - KeyMetaEntry, KeyType, KeystoreDB, Uuid, KEYSTORE_UUID, -}; -use crate::error::{map_km_error, Error}; -use crate::key_parameter::{KeyParameter, KeyParameterValue}; -use crate::legacy_blob::{self, Blob, BlobValue, LegacyKeyCharacteristics}; -use crate::super_key::USER_SUPER_KEY; -use crate::utils::{ - key_characteristics_to_internal, uid_to_android_user, upgrade_keyblob_if_required_with, - watchdog as wd, AesGcm, -}; +use crate::key_parameter::KeyParameterValue; +use crate::legacy_blob::BlobValue; +use crate::utils::{uid_to_android_user, watchdog as wd}; use crate::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader}; +use crate::{database::KeyType, error::Error}; +use crate::{ + database::{ + BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, EncryptedBy, KeyMetaData, + KeyMetaEntry, KeystoreDB, Uuid, KEYSTORE_UUID, + }, + super_key::USER_SUPER_KEY, +}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, @@ -39,8 +38,8 @@ use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; -/// Represents LegacyImporter. -pub struct LegacyImporter { +/// Represents LegacyMigrator. +pub struct LegacyMigrator { async_task: Arc<AsyncTask>, initializer: Mutex< Option< @@ -52,19 +51,19 @@ pub struct LegacyImporter { >, >, /// This atomic is used for cheap interior mutability. It is intended to prevent - /// expensive calls into the legacy importer when the legacy database is empty. + /// expensive calls into the legacy migrator when the legacy database is empty. /// When transitioning from READY to EMPTY, spurious calls may occur for a brief period /// of time. This is tolerable in favor of the common case. state: AtomicU8, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct RecentImport { +struct RecentMigration { uid: u32, alias: String, } -impl RecentImport { +impl RecentMigration { fn new(uid: u32, alias: String) -> Self { Self { uid, alias } } @@ -75,15 +74,15 @@ enum BulkDeleteRequest { User(u32), } -struct LegacyImporterState { - recently_imported: HashSet<RecentImport>, - recently_imported_super_key: HashSet<u32>, +struct LegacyMigratorState { + recently_migrated: HashSet<RecentMigration>, + recently_migrated_super_key: HashSet<u32>, legacy_loader: Arc<LegacyBlobLoader>, sec_level_to_km_uuid: HashMap<SecurityLevel, Uuid>, db: KeystoreDB, } -impl LegacyImporter { +impl LegacyMigrator { const WIFI_NAMESPACE: i64 = 102; const AID_WIFI: u32 = 1010; @@ -91,7 +90,7 @@ impl LegacyImporter { const STATE_READY: u8 = 1; const STATE_EMPTY: u8 = 2; - /// Constructs a new LegacyImporter using the given AsyncTask object as import + /// Constructs a new LegacyMigrator using the given AsyncTask object as migration /// worker. pub fn new(async_task: Arc<AsyncTask>) -> Self { Self { @@ -101,7 +100,7 @@ impl LegacyImporter { } } - /// The legacy importer must be initialized deferred, because keystore starts very early. + /// The legacy migrator 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 /// uses to connect to the database. @@ -126,11 +125,11 @@ impl LegacyImporter { Ok(()) } - /// This function is called by the import requestor to check if it is worth - /// making an import request. It also transitions the state from UNINITIALIZED + /// This function is called by the migration requestor to check if it is worth + /// making a migration request. It also transitions the state from UNINITIALIZED /// to READY or EMPTY on first use. The deferred initialization is necessary, because /// Keystore 2.0 runs early during boot, where data may not yet be mounted. - /// Returns Ok(STATE_READY) if an import request is worth undertaking and + /// Returns Ok(STATE_READY) if a migration request is worth undertaking and /// Ok(STATE_EMPTY) if the database is empty. An error is returned if the loader /// was not initialized and cannot be initialized. fn check_state(&self) -> Result<u8> { @@ -158,9 +157,9 @@ impl LegacyImporter { } self.async_task.queue_hi(move |shelf| { - shelf.get_or_put_with(|| LegacyImporterState { - recently_imported: Default::default(), - recently_imported_super_key: Default::default(), + shelf.get_or_put_with(|| LegacyMigratorState { + recently_migrated: Default::default(), + recently_migrated_super_key: Default::default(), legacy_loader, sec_level_to_km_uuid, db, @@ -190,14 +189,14 @@ impl LegacyImporter { ); } (Self::STATE_READY, _) => return Ok(Self::STATE_READY), - (s, _) => panic!("Unknown legacy importer state. {} ", s), + (s, _) => panic!("Unknown legacy migrator state. {} ", s), } } } /// List all aliases for uid in the legacy database. pub fn list_uid(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> { - let _wp = wd::watch_millis("LegacyImporter::list_uid", 500); + let _wp = wd::watch_millis("LegacyMigrator::list_uid", 500); let uid = match (domain, namespace) { (Domain::APP, namespace) => namespace as u32, @@ -218,44 +217,44 @@ impl LegacyImporter { ) } - /// Sends the given closure to the importer thread for execution after calling check_state. + /// Sends the given closure to the migrator thread for execution after calling check_state. /// Returns None if the database was empty and the request was not executed. - /// Otherwise returns Some with the result produced by the import request. + /// Otherwise returns Some with the result produced by the migration request. /// The loader state may transition to STATE_EMPTY during the execution of this function. fn do_serialized<F, T: Send + 'static>(&self, f: F) -> Option<Result<T>> where - F: FnOnce(&mut LegacyImporterState) -> Result<T> + Send + 'static, + F: FnOnce(&mut LegacyMigratorState) -> Result<T> + Send + 'static, { // Short circuit if the database is empty or not initialized (error case). match self.check_state().context("In do_serialized: Checking state.") { - Ok(LegacyImporter::STATE_EMPTY) => return None, - Ok(LegacyImporter::STATE_READY) => {} + Ok(LegacyMigrator::STATE_EMPTY) => return None, + Ok(LegacyMigrator::STATE_READY) => {} Err(e) => return Some(Err(e)), - Ok(s) => panic!("Unknown legacy importer state. {} ", s), + Ok(s) => panic!("Unknown legacy migrator state. {} ", s), } // We have established that there may be a key in the legacy database. - // Now we schedule an import request. + // Now we schedule a migration request. let (sender, receiver) = channel(); self.async_task.queue_hi(move |shelf| { - // Get the importer state from the shelf. - // There may not be a state. This can happen if this import request was scheduled + // Get the migrator state from the shelf. + // There may not be a state. This can happen if this migration request was scheduled // before a previous request established that the legacy database was empty // and removed the state from the shelf. Since we know now that the database // is empty, we can return None here. - let (new_state, result) = if let Some(legacy_importer_state) = - shelf.get_downcast_mut::<LegacyImporterState>() + let (new_state, result) = if let Some(legacy_migrator_state) = + shelf.get_downcast_mut::<LegacyMigratorState>() { - let result = f(legacy_importer_state); - (legacy_importer_state.check_empty(), Some(result)) + let result = f(legacy_migrator_state); + (legacy_migrator_state.check_empty(), Some(result)) } else { (Self::STATE_EMPTY, None) }; - // If the import request determined that the database is now empty, we discard + // If the migration request determined that the database is now empty, we discard // the state from the shelf to free up the resources we won't need any longer. if result.is_some() && new_state == Self::STATE_EMPTY { - shelf.remove_downcast_ref::<LegacyImporterState>(); + shelf.remove_downcast_ref::<LegacyMigratorState>(); } // Send the result to the requester. @@ -272,7 +271,7 @@ impl LegacyImporter { }; // We can only transition to EMPTY but never back. - // The importer never creates any legacy blobs. + // The migrator never creates any legacy blobs. if new_state == Self::STATE_EMPTY { self.state.store(Self::STATE_EMPTY, Ordering::Relaxed) } @@ -281,20 +280,19 @@ impl LegacyImporter { } /// Runs the key_accessor function and returns its result. If it returns an error and the - /// root cause was KEY_NOT_FOUND, tries to import a key with the given parameters from + /// root cause was KEY_NOT_FOUND, tries to migrate a key with the given parameters from /// the legacy database to the new database and runs the key_accessor function again if - /// the import request was successful. - pub fn with_try_import<F, T>( + /// the migration request was successful. + pub fn with_try_migrate<F, T>( &self, key: &KeyDescriptor, caller_uid: u32, - super_key: Option<Arc<dyn AesGcm + Send + Sync>>, key_accessor: F, ) -> Result<T> where F: Fn() -> Result<T>, { - let _wp = wd::watch_millis("LegacyImporter::with_try_import", 500); + let _wp = wd::watch_millis("LegacyMigrator::with_try_migrate", 500); // Access the key and return on success. match key_accessor() { @@ -306,7 +304,7 @@ impl LegacyImporter { } // Filter inputs. We can only load legacy app domain keys and some special rules due - // to which we import keys transparently to an SELINUX domain. + // to which we migrate keys transparently to an SELINUX domain. let uid = match key { KeyDescriptor { domain: Domain::APP, alias: Some(_), .. } => caller_uid, KeyDescriptor { domain: Domain::SELINUX, nspace, alias: Some(_), .. } => { @@ -325,14 +323,12 @@ impl LegacyImporter { }; let key_clone = key.clone(); - let result = self.do_serialized(move |importer_state| { - let super_key = super_key.map(|sk| -> Arc<dyn AesGcm> { sk }); - importer_state.check_and_import(uid, key_clone, super_key) - }); + let result = self + .do_serialized(move |migrator_state| migrator_state.check_and_migrate(uid, key_clone)); if let Some(result) = result { result?; - // After successful import try again. + // After successful migration try again. key_accessor() } else { Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context("Legacy database is empty.") @@ -340,8 +336,8 @@ impl LegacyImporter { } /// Calls key_accessor and returns the result on success. In the case of a KEY_NOT_FOUND error - /// this function makes an import request and on success retries the key_accessor. - pub fn with_try_import_super_key<F, T>( + /// this function makes a migration request and on success retries the key_accessor. + pub fn with_try_migrate_super_key<F, T>( &self, user_id: u32, pw: &Password, @@ -350,31 +346,31 @@ impl LegacyImporter { where F: FnMut() -> Result<Option<T>>, { - let _wp = wd::watch_millis("LegacyImporter::with_try_import_super_key", 500); + let _wp = wd::watch_millis("LegacyMigrator::with_try_migrate_super_key", 500); match key_accessor() { Ok(Some(result)) => return Ok(Some(result)), Ok(None) => {} Err(e) => return Err(e), } - let pw = pw.try_clone().context("In with_try_import_super_key: Cloning password.")?; - let result = self.do_serialized(move |importer_state| { - importer_state.check_and_import_super_key(user_id, &pw) + let pw = pw.try_clone().context("In with_try_migrate_super_key: Cloning password.")?; + let result = self.do_serialized(move |migrator_state| { + migrator_state.check_and_migrate_super_key(user_id, &pw) }); if let Some(result) = result { result?; - // After successful import try again. + // After successful migration try again. key_accessor() } else { Ok(None) } } - /// Deletes all keys belonging to the given namespace, importing them into the database + /// Deletes all keys belonging to the given namespace, migrating them into the database /// for subsequent garbage collection if necessary. pub fn bulk_delete_uid(&self, domain: Domain, nspace: i64) -> Result<()> { - let _wp = wd::watch_millis("LegacyImporter::bulk_delete_uid", 500); + let _wp = wd::watch_millis("LegacyMigrator::bulk_delete_uid", 500); let uid = match (domain, nspace) { (Domain::APP, nspace) => nspace as u32, @@ -383,24 +379,24 @@ impl LegacyImporter { _ => return Ok(()), }; - let result = self.do_serialized(move |importer_state| { - importer_state.bulk_delete(BulkDeleteRequest::Uid(uid), false) + let result = self.do_serialized(move |migrator_state| { + migrator_state.bulk_delete(BulkDeleteRequest::Uid(uid), false) }); result.unwrap_or(Ok(())) } - /// Deletes all keys belonging to the given android user, importing them into the database + /// Deletes all keys belonging to the given android user, migrating them into the database /// for subsequent garbage collection if necessary. pub fn bulk_delete_user( &self, user_id: u32, keep_non_super_encrypted_keys: bool, ) -> Result<()> { - let _wp = wd::watch_millis("LegacyImporter::bulk_delete_user", 500); + let _wp = wd::watch_millis("LegacyMigrator::bulk_delete_user", 500); - let result = self.do_serialized(move |importer_state| { - importer_state + let result = self.do_serialized(move |migrator_state| { + migrator_state .bulk_delete(BulkDeleteRequest::User(user_id), keep_non_super_encrypted_keys) }); @@ -410,12 +406,12 @@ impl LegacyImporter { /// Queries the legacy database for the presence of a super key for the given user. pub fn has_super_key(&self, user_id: u32) -> Result<bool> { let result = - self.do_serialized(move |importer_state| importer_state.has_super_key(user_id)); + self.do_serialized(move |migrator_state| migrator_state.has_super_key(user_id)); result.unwrap_or(Ok(false)) } } -impl LegacyImporterState { +impl LegacyMigratorState { fn get_km_uuid(&self, is_strongbox: bool) -> Result<Uuid> { let sec_level = if is_strongbox { SecurityLevel::STRONGBOX @@ -434,174 +430,17 @@ impl LegacyImporterState { .context("In list_uid: Trying to list legacy entries.") } - /// Checks if the key can potentially be unlocked. And deletes the key entry otherwise. - /// If the super_key has already been imported, the super key database id is returned. - fn get_super_key_id_check_unlockable_or_delete( - &mut self, - uid: u32, - alias: &str, - ) -> Result<i64> { - let user_id = uid_to_android_user(uid); - - match self - .db - .load_super_key(&USER_SUPER_KEY, user_id) - .context("In get_super_key_id_check_unlockable_or_delete: Failed to load super key")? - { - Some((_, entry)) => Ok(entry.id()), - None => { - // This might be the first time we access the super key, - // and it may not have been imported. We cannot import - // the legacy super_key key now, because we need to reencrypt - // it which we cannot do if we are not unlocked, which we are - // not because otherwise the key would have been imported. - // We can check though if the key exists. If it does, - // we can return Locked. Otherwise, we can delete the - // key and return NotFound, because the key will never - // be unlocked again. - if self.legacy_loader.has_super_key(user_id) { - Err(Error::Rc(ResponseCode::LOCKED)).context( - "In get_super_key_id_check_unlockable_or_delete: \ - Cannot import super key of this key while user is locked.", - ) - } else { - self.legacy_loader.remove_keystore_entry(uid, alias).context( - "In get_super_key_id_check_unlockable_or_delete: \ - Trying to remove obsolete key.", - )?; - Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In get_super_key_id_check_unlockable_or_delete: Obsolete key.") - } - } - } - } - - fn characteristics_file_to_cache( - &mut self, - km_blob_params: Option<(Blob, LegacyKeyCharacteristics)>, - super_key: &Option<Arc<dyn AesGcm>>, - uid: u32, - alias: &str, - ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<(LegacyBlob<'static>, BlobMetaData)>)> - { - let (km_blob, params) = match km_blob_params { - Some((km_blob, LegacyKeyCharacteristics::File(params))) => (km_blob, params), - Some((km_blob, LegacyKeyCharacteristics::Cache(params))) => { - return Ok((Some((km_blob, params)), None)) - } - None => return Ok((None, None)), - }; - - let km_uuid = self - .get_km_uuid(km_blob.is_strongbox()) - .context("In characteristics_file_to_cache: Trying to get KM UUID")?; - - let blob = match (&km_blob.value(), super_key.as_ref()) { - (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => { - let blob = super_key - .decrypt(data, iv, tag) - .context("In characteristics_file_to_cache: Decryption failed.")?; - LegacyBlob::ZVec(blob) - } - (BlobValue::Encrypted { .. }, None) => { - return Err(Error::Rc(ResponseCode::LOCKED)).context( - "In characteristics_file_to_cache: Oh uh, so close. \ - This ancient key cannot be imported unless the user is unlocked.", - ); - } - (BlobValue::Decrypted(data), _) => LegacyBlob::Ref(data), - _ => { - return Err(Error::sys()) - .context("In characteristics_file_to_cache: Unexpected blob type.") - } - }; - - let (km_params, upgraded_blob) = get_key_characteristics_without_app_data(&km_uuid, &*blob) - .context( - "In characteristics_file_to_cache: Failed to get key characteristics from device.", - )?; - - let flags = km_blob.get_flags(); - - let (current_blob, superseded_blob) = if let Some(upgraded_blob) = upgraded_blob { - match (km_blob.take_value(), super_key.as_ref()) { - (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => { - let super_key_id = - self.get_super_key_id_check_unlockable_or_delete(uid, alias).context( - "In characteristics_file_to_cache: \ - How is there a super key but no super key id?", - )?; - - let mut superseded_metadata = BlobMetaData::new(); - superseded_metadata.add(BlobMetaEntry::Iv(iv.to_vec())); - superseded_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec())); - superseded_metadata - .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id))); - superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid)); - let superseded_blob = (LegacyBlob::Vec(data), superseded_metadata); - - let (data, iv, tag) = super_key.encrypt(&upgraded_blob).context( - "In characteristics_file_to_cache: \ - Failed to encrypt upgraded key blob.", - )?; - ( - Blob::new(flags, BlobValue::Encrypted { data, iv, tag }), - Some(superseded_blob), - ) - } - (BlobValue::Encrypted { .. }, None) => { - return Err(Error::sys()).context( - "In characteristics_file_to_cache: This should not be reachable. \ - The blob could not have been decrypted above.", - ); - } - (BlobValue::Decrypted(data), _) => { - let mut superseded_metadata = BlobMetaData::new(); - superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid)); - let superseded_blob = (LegacyBlob::ZVec(data), superseded_metadata); - ( - Blob::new( - flags, - BlobValue::Decrypted(upgraded_blob.try_into().context( - "In characteristics_file_to_cache: \ - Failed to convert upgraded blob to ZVec.", - )?), - ), - Some(superseded_blob), - ) - } - _ => { - return Err(Error::sys()).context( - "In characteristics_file_to_cache: This should not be reachable. \ - Any other variant should have resulted in a different error.", - ) - } - } - } else { - (km_blob, None) - }; - - let params = - augment_legacy_characteristics_file_with_key_characteristics(km_params, params); - Ok((Some((current_blob, params)), superseded_blob)) - } - - /// This is a key import request that must run in the importer thread. This must + /// This is a key migration request that must run in the migrator thread. This must /// be passed to do_serialized. - fn check_and_import( - &mut self, - uid: u32, - mut key: KeyDescriptor, - super_key: Option<Arc<dyn AesGcm>>, - ) -> Result<()> { + fn check_and_migrate(&mut self, uid: u32, mut key: KeyDescriptor) -> Result<()> { let alias = key.alias.clone().ok_or_else(|| { - anyhow::anyhow!(Error::sys()).context( - "In check_and_import: Must be Some because \ - our caller must not have called us otherwise.", - ) + anyhow::anyhow!(Error::sys()).context(concat!( + "In check_and_migrate: Must be Some because ", + "our caller must not have called us otherwise." + )) })?; - if self.recently_imported.contains(&RecentImport::new(uid, alias.clone())) { + if self.recently_migrated.contains(&RecentMigration::new(uid, alias.clone())) { return Ok(()); } @@ -612,42 +451,49 @@ impl LegacyImporterState { // If the key is not found in the cache, try to load from the legacy database. let (km_blob_params, user_cert, ca_cert) = self .legacy_loader - .load_by_uid_alias(uid, &alias, &super_key) - .map_err(|e| { - if e.root_cause().downcast_ref::<legacy_blob::Error>() - == Some(&legacy_blob::Error::LockedComponent) - { - // There is no chance to succeed at this point. We just check if there is - // a super key so that this entry might be unlockable in the future. - // If not the entry will be deleted and KEY_NOT_FOUND is returned. - // If a super key id was returned we still have to return LOCKED but the key - // may be imported when the user unlocks the device. - self.get_super_key_id_check_unlockable_or_delete(uid, &alias) - .and_then::<i64, _>(|_| { - Err(Error::Rc(ResponseCode::LOCKED)) - .context("Super key present but locked.") - }) - .unwrap_err() - } else { - e - } - }) - .context("In check_and_import: Trying to load legacy blob.")?; - - let (km_blob_params, superseded_blob) = self - .characteristics_file_to_cache(km_blob_params, &super_key, uid, &alias) - .context("In check_and_import: Trying to update legacy charateristics.")?; - + .load_by_uid_alias(uid, &alias, None) + .context("In check_and_migrate: Trying to load legacy blob.")?; let result = match km_blob_params { Some((km_blob, params)) => { let is_strongbox = km_blob.is_strongbox(); - let (blob, mut blob_metadata) = match km_blob.take_value() { BlobValue::Encrypted { iv, tag, data } => { // Get super key id for user id. - let super_key_id = self - .get_super_key_id_check_unlockable_or_delete(uid, &alias) - .context("In check_and_import: Failed to get super key id.")?; + let user_id = uid_to_android_user(uid as u32); + + let super_key_id = match self + .db + .load_super_key(&USER_SUPER_KEY, user_id) + .context("In check_and_migrate: Failed to load super key")? + { + Some((_, entry)) => entry.id(), + None => { + // This might be the first time we access the super key, + // and it may not have been migrated. We cannot import + // the legacy super_key key now, because we need to reencrypt + // it which we cannot do if we are not unlocked, which we are + // not because otherwise the key would have been migrated. + // We can check though if the key exists. If it does, + // we can return Locked. Otherwise, we can delete the + // key and return NotFound, because the key will never + // be unlocked again. + if self.legacy_loader.has_super_key(user_id) { + return Err(Error::Rc(ResponseCode::LOCKED)).context(concat!( + "In check_and_migrate: Cannot migrate super key of this ", + "key while user is locked." + )); + } else { + self.legacy_loader.remove_keystore_entry(uid, &alias).context( + concat!( + "In check_and_migrate: ", + "Trying to remove obsolete key." + ), + )?; + return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) + .context("In check_and_migrate: Obsolete key."); + } + } + }; let mut blob_metadata = BlobMetaData::new(); blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec())); @@ -659,79 +505,74 @@ impl LegacyImporterState { BlobValue::Decrypted(data) => (LegacyBlob::ZVec(data), BlobMetaData::new()), _ => { return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In check_and_import: Legacy key has unexpected type.") + .context("In check_and_migrate: Legacy key has unexpected type.") } }; let km_uuid = self .get_km_uuid(is_strongbox) - .context("In check_and_import: Trying to get KM UUID")?; + .context("In check_and_migrate: Trying to get KM UUID")?; blob_metadata.add(BlobMetaEntry::KmUuid(km_uuid)); let mut metadata = KeyMetaData::new(); let creation_date = DateTime::now() - .context("In check_and_import: Trying to make creation time.")?; + .context("In check_and_migrate: Trying to make creation time.")?; metadata.add(KeyMetaEntry::CreationDate(creation_date)); - let blob_info = BlobInfo::new_with_superseded( - &blob, - &blob_metadata, - superseded_blob.as_ref().map(|(b, m)| (&**b, m)), - ); // Store legacy key in the database. self.db .store_new_key( &key, KeyType::Client, ¶ms, - &blob_info, + &(&blob, &blob_metadata), &CertificateInfo::new(user_cert, ca_cert), &metadata, &km_uuid, ) - .context("In check_and_import.")?; + .context("In check_and_migrate.")?; Ok(()) } None => { if let Some(ca_cert) = ca_cert { self.db .store_new_certificate(&key, KeyType::Client, &ca_cert, &KEYSTORE_UUID) - .context("In check_and_import: Failed to insert new certificate.")?; + .context("In check_and_migrate: Failed to insert new certificate.")?; Ok(()) } else { Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In check_and_import: Legacy key not found.") + .context("In check_and_migrate: Legacy key not found.") } } }; match result { Ok(()) => { - // Add the key to the imported_keys list. - self.recently_imported.insert(RecentImport::new(uid, alias.clone())); + // Add the key to the migrated_keys list. + self.recently_migrated.insert(RecentMigration::new(uid, alias.clone())); // Delete legacy key from the file system self.legacy_loader .remove_keystore_entry(uid, &alias) - .context("In check_and_import: Trying to remove imported key.")?; + .context("In check_and_migrate: Trying to remove migrated key.")?; Ok(()) } Err(e) => Err(e), } } - fn check_and_import_super_key(&mut self, user_id: u32, pw: &Password) -> Result<()> { - if self.recently_imported_super_key.contains(&user_id) { + fn check_and_migrate_super_key(&mut self, user_id: u32, pw: &Password) -> Result<()> { + if self.recently_migrated_super_key.contains(&user_id) { return Ok(()); } if let Some(super_key) = self .legacy_loader - .load_super_key(user_id, pw) - .context("In check_and_import_super_key: Trying to load legacy super key.")? + .load_super_key(user_id, &pw) + .context("In check_and_migrate_super_key: Trying to load legacy super key.")? { let (blob, blob_metadata) = crate::super_key::SuperKeyManager::encrypt_with_password(&super_key, pw) - .context("In check_and_import_super_key: Trying to encrypt super key.")?; + .context("In check_and_migrate_super_key: Trying to encrypt super key.")?; self.db .store_super_key( @@ -742,20 +583,20 @@ impl LegacyImporterState { &KeyMetaData::new(), ) .context(concat!( - "In check_and_import_super_key: ", + "In check_and_migrate_super_key: ", "Trying to insert legacy super_key into the database." ))?; self.legacy_loader.remove_super_key(user_id); - self.recently_imported_super_key.insert(user_id); + self.recently_migrated_super_key.insert(user_id); Ok(()) } else { Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - .context("In check_and_import_super_key: No key found do import.") + .context("In check_and_migrate_super_key: No key found do migrate.") } } - /// Key importer request to be run by do_serialized. - /// See LegacyImporter::bulk_delete_uid and LegacyImporter::bulk_delete_user. + /// Key migrator request to be run by do_serialized. + /// See LegacyMigrator::bulk_delete_uid and LegacyMigrator::bulk_delete_user. fn bulk_delete( &mut self, bulk_delete_request: BulkDeleteRequest, @@ -789,21 +630,18 @@ impl LegacyImporterState { for (uid, alias) in aliases .into_iter() - .flat_map(|(uid, aliases)| aliases.into_iter().map(move |alias| (uid, alias))) + .map(|(uid, aliases)| aliases.into_iter().map(move |alias| (uid, alias))) + .flatten() { let (km_blob_params, _, _) = self .legacy_loader - .load_by_uid_alias(uid, &alias, &None) + .load_by_uid_alias(uid, &alias, None) .context("In bulk_delete: Trying to load legacy blob.")?; // Determine if the key needs special handling to be deleted. let (need_gc, is_super_encrypted) = km_blob_params .as_ref() .map(|(blob, params)| { - let params = match params { - LegacyKeyCharacteristics::Cache(params) - | LegacyKeyCharacteristics::File(params) => params, - }; ( params.iter().any(|kp| { KeyParameterValue::RollbackResistance == *kp.key_parameter_value() @@ -857,87 +695,37 @@ impl LegacyImporterState { self.legacy_loader .remove_keystore_entry(uid, &alias) - .context("In bulk_delete: Trying to remove imported key.")?; + .context("In bulk_delete: Trying to remove migrated key.")?; } Ok(()) } fn has_super_key(&mut self, user_id: u32) -> Result<bool> { - Ok(self.recently_imported_super_key.contains(&user_id) + Ok(self.recently_migrated_super_key.contains(&user_id) || self.legacy_loader.has_super_key(user_id)) } fn check_empty(&self) -> u8 { if self.legacy_loader.is_empty().unwrap_or(false) { - LegacyImporter::STATE_EMPTY + LegacyMigrator::STATE_EMPTY } else { - LegacyImporter::STATE_READY + LegacyMigrator::STATE_READY } } } -enum LegacyBlob<'a> { +enum LegacyBlob { Vec(Vec<u8>), ZVec(ZVec), - Ref(&'a [u8]), } -impl Deref for LegacyBlob<'_> { +impl Deref for LegacyBlob { type Target = [u8]; fn deref(&self) -> &Self::Target { match self { - Self::Vec(v) => v, - Self::ZVec(v) => v, - Self::Ref(v) => v, - } - } -} - -/// This function takes two KeyParameter lists. The first is assumed to have been retrieved from the -/// KM back end using km_dev.getKeyCharacteristics. The second is assumed to have been retrieved -/// from a legacy key characteristics file (not cache) as used in Android P and older. The function -/// augments the former with entries from the latter only if no equivalent entry is present ignoring. -/// the security level of enforcement. All entries in the latter are assumed to have security level -/// KEYSTORE. -fn augment_legacy_characteristics_file_with_key_characteristics<T>( - mut from_km: Vec<KeyParameter>, - legacy: T, -) -> Vec<KeyParameter> -where - T: IntoIterator<Item = KeyParameter>, -{ - for legacy_kp in legacy.into_iter() { - if !from_km - .iter() - .any(|km_kp| km_kp.key_parameter_value() == legacy_kp.key_parameter_value()) - { - from_km.push(legacy_kp); + Self::Vec(v) => &v, + Self::ZVec(v) => &v, } } - from_km -} - -/// Attempts to retrieve the key characteristics for the given blob from the KM back end with the -/// given UUID. It may upgrade the key blob in the process. In that case the upgraded blob is -/// returned as the second tuple member. -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) - .with_context(|| format!("In foo: Trying to get km device for id {:?}", uuid))?; - - let (characteristics, upgraded_blob) = upgrade_keyblob_if_required_with( - &*km_dev, - blob, - &[], - |blob| { - let _wd = wd::watch_millis("In foo: Calling GetKeyCharacteristics.", 500); - map_km_error(km_dev.getKeyCharacteristics(blob, &[], &[])) - }, - |_| Ok(()), - ) - .context("In foo.")?; - Ok((key_characteristics_to_internal(characteristics), upgraded_blob)) } diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs index 4a23843f..8b629b10 100644 --- a/keystore2/src/lib.rs +++ b/keystore2/src/lib.rs @@ -29,7 +29,7 @@ pub mod id_rotation; /// Internal Representation of Key Parameter and convenience functions. pub mod key_parameter; pub mod legacy_blob; -pub mod legacy_importer; +pub mod legacy_migrator; pub mod maintenance; pub mod metrics; pub mod metrics_store; @@ -40,12 +40,12 @@ pub mod remote_provisioning; pub mod security_level; pub mod service; pub mod shared_secret_negotiation; +pub mod try_insert; pub mod utils; mod attestation_key_utils; mod audit_log; mod gc; -mod km_compat; mod super_key; #[cfg(feature = "watchdog")] diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs index 1fca5d96..3180e5df 100644 --- a/keystore2/src/maintenance.rs +++ b/keystore2/src/maintenance.rs @@ -19,15 +19,12 @@ use crate::error::map_km_error; use crate::error::map_or_log_err; use crate::error::Error; use crate::globals::get_keymint_device; -use crate::globals::{DB, LEGACY_IMPORTER, SUPER_KEY}; +use crate::globals::{DB, LEGACY_MIGRATOR, SUPER_KEY}; 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, -}; -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel, -}; +use crate::super_key::UserState; +use crate::utils::{check_key_permission, check_keystore_permission, watchdog as wd}; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::IKeyMintDevice::IKeyMintDevice; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel; use android_security_maintenance::aidl::android::security::maintenance::{ IKeystoreMaintenance::{BnKeystoreMaintenance, IKeystoreMaintenance}, UserState::UserState as AidlUserState, @@ -69,25 +66,24 @@ impl Maintenance { } fn on_user_password_changed(user_id: i32, password: Option<Password>) -> Result<()> { - // Check permission. Function should return if this failed. Therefore having '?' at the end - // is very important. - check_keystore_permission(KeystorePerm::ChangePassword) + //Check permission. Function should return if this failed. Therefore having '?' at the end + //is very important. + check_keystore_permission(KeystorePerm::change_password()) .context("In on_user_password_changed.")?; - let mut skm = SUPER_KEY.write().unwrap(); - 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) + SUPER_KEY.unlock_screen_lock_bound_key(&mut db.borrow_mut(), user_id as u32, pw) }) .context("In on_user_password_changed: unlock_screen_lock_bound_key failed")?; } match DB .with(|db| { - skm.reset_or_init_user_and_get_user_state( + UserState::get_with_password_changed( &mut db.borrow_mut(), - &LEGACY_IMPORTER, + &LEGACY_MIGRATOR, + &SUPER_KEY, user_id as u32, password.as_ref(), ) @@ -109,12 +105,12 @@ impl Maintenance { fn add_or_remove_user(&self, user_id: i32) -> Result<()> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. - check_keystore_permission(KeystorePerm::ChangeUser).context("In add_or_remove_user.")?; - + check_keystore_permission(KeystorePerm::change_user()).context("In add_or_remove_user.")?; DB.with(|db| { - SUPER_KEY.write().unwrap().reset_user( + UserState::reset_user( &mut db.borrow_mut(), - &LEGACY_IMPORTER, + &SUPER_KEY, + &LEGACY_MIGRATOR, user_id as u32, false, ) @@ -127,9 +123,9 @@ impl Maintenance { 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.")?; + check_keystore_permission(KeystorePerm::clear_uid()).context("In clear_namespace.")?; - LEGACY_IMPORTER + LEGACY_MIGRATOR .bulk_delete_uid(domain, nspace) .context("In clear_namespace: Trying to delete legacy keys.")?; DB.with(|db| db.borrow_mut().unbind_keys_for_namespace(domain, nspace)) @@ -142,14 +138,10 @@ impl Maintenance { 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.")?; + check_keystore_permission(KeystorePerm::get_state()).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, - ) + UserState::get(&mut db.borrow_mut(), &LEGACY_MIGRATOR, &SUPER_KEY, user_id as u32) }) .context("In get_state. Trying to get UserState.")?; @@ -162,10 +154,13 @@ impl Maintenance { fn call_with_watchdog<F>(sec_level: SecurityLevel, name: &'static str, op: &F) -> Result<()> where - F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>, + F: Fn(Strong<dyn IKeyMintDevice>) -> binder::public_api::Result<()>, { - let (km_dev, _, _) = get_keymint_device(&sec_level) + let (dev, _, _) = get_keymint_device(&sec_level) .context("In call_with_watchdog: getting keymint device")?; + let km_dev: Strong<dyn IKeyMintDevice> = dev + .get_interface() + .context("In call_with_watchdog: getting keymint device interface")?; let _wp = wd::watch_millis_with("In call_with_watchdog", 500, move || { format!("Seclevel: {:?} Op: {}", sec_level, name) @@ -176,7 +171,7 @@ impl Maintenance { fn call_on_all_security_levels<F>(name: &'static str, op: F) -> Result<()> where - F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>, + F: Fn(Strong<dyn IKeyMintDevice>) -> binder::public_api::Result<()>, { let sec_levels = [ (SecurityLevel::TRUSTED_ENVIRONMENT, "TRUSTED_ENVIRONMENT"), @@ -202,13 +197,11 @@ impl Maintenance { } fn early_boot_ended() -> Result<()> { - check_keystore_permission(KeystorePerm::EarlyBootEnded) + check_keystore_permission(KeystorePerm::early_boot_ended()) .context("In early_boot_ended. Checking permission")?; log::info!("In early_boot_ended."); - if let Err(e) = - DB.with(|db| SuperKeyManager::set_up_boot_level_cache(&SUPER_KEY, &mut db.borrow_mut())) - { + if let Err(e) = DB.with(|db| SUPER_KEY.set_up_boot_level_cache(&mut db.borrow_mut())) { log::error!("SUPER_KEY.set_up_boot_level_cache failed:\n{:?}\n:(", e); } Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded()) @@ -216,66 +209,53 @@ impl Maintenance { fn on_device_off_body() -> Result<()> { // Security critical permission check. This statement must return on fail. - check_keystore_permission(KeystorePerm::ReportOffBody).context("In on_device_off_body.")?; + check_keystore_permission(KeystorePerm::report_off_body()) + .context("In on_device_off_body.")?; 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(); - - match source.domain { - Domain::SELINUX | Domain::KEY_ID | Domain::APP => (), - _ => { - return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context( - "In migrate_key_namespace: \ - Source domain must be one of APP, SELINUX, or KEY_ID.", - ) - } - }; - - match destination.domain { - Domain::SELINUX | Domain::APP => (), - _ => { - return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context( - "In migrate_key_namespace: \ - Destination domain must be one of APP or SELINUX.", - ) - } - }; - - 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 caller_uid = ThreadState::get_calling_uid(); DB.with(|db| { - let (key_id_guard, _) = LEGACY_IMPORTER - .with_try_import(source, calling_uid, super_key, || { - db.borrow_mut().load_key_entry( - source, - KeyType::Client, - KeyEntryLoadBits::NONE, - calling_uid, - |k, av| { - check_key_permission(KeyPerm::Use, k, &av)?; - check_key_permission(KeyPerm::Delete, k, &av)?; - check_key_permission(KeyPerm::Grant, k, &av) - }, - ) - }) - .context("In migrate_key_namespace: Failed to load key blob.")?; - { - db.borrow_mut().migrate_key_namespace(key_id_guard, destination, calling_uid, |k| { - check_key_permission(KeyPerm::Rebind, k, &None) - }) - } + let key_id_guard = match source.domain { + Domain::APP | Domain::SELINUX | Domain::KEY_ID => { + let (key_id_guard, _) = LEGACY_MIGRATOR + .with_try_migrate(&source, caller_uid, || { + db.borrow_mut().load_key_entry( + &source, + KeyType::Client, + KeyEntryLoadBits::NONE, + caller_uid, + |k, av| { + check_key_permission(KeyPerm::use_(), k, &av)?; + check_key_permission(KeyPerm::delete(), k, &av)?; + check_key_permission(KeyPerm::grant(), k, &av) + }, + ) + }) + .context("In migrate_key_namespace: Failed to load key blob.")?; + key_id_guard + } + _ => { + return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(concat!( + "In migrate_key_namespace: ", + "Source domain must be one of APP, SELINUX, or KEY_ID." + )) + } + }; + + db.borrow_mut().migrate_key_namespace(key_id_guard, destination, caller_uid, |k| { + check_key_permission(KeyPerm::rebind(), k, &None) + }) }) } fn delete_all_keys() -> Result<()> { // Security critical permission check. This statement must return on fail. - check_keystore_permission(KeystorePerm::DeleteAllKeys) + check_keystore_permission(KeystorePerm::delete_all_keys()) .context("In delete_all_keys. Checking permission")?; log::info!("In delete_all_keys."); diff --git a/keystore2/src/metrics.rs b/keystore2/src/metrics.rs index 3d8d6d3f..42295b7c 100644 --- a/keystore2/src/metrics.rs +++ b/keystore2/src/metrics.rs @@ -41,7 +41,7 @@ impl Metrics { fn pull_metrics(&self, atom_id: AtomID) -> Result<Vec<KeystoreAtom>> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. - check_keystore_permission(KeystorePerm::PullMetrics).context("In pull_metrics.")?; + check_keystore_permission(KeystorePerm::pull_metrics()).context("In pull_metrics.")?; METRICS_STORE.get_atoms(atom_id) } } diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs index 62a7d135..32067b95 100644 --- a/keystore2/src/metrics_store.rs +++ b/keystore2/src/metrics_store.rs @@ -17,7 +17,7 @@ //! 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::get_error_code; use crate::globals::DB; use crate::key_parameter::KeyParameterValue as KsKeyParamValue; use crate::operation::Outcome; @@ -44,10 +44,9 @@ use android_security_metrics::aidl::android::security::metrics::{ RkpPoolStats::RkpPoolStats, SecurityLevel::SecurityLevel as MetricsSecurityLevel, Storage::Storage as MetricsStorage, }; -use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode; use anyhow::{Context, Result}; +use keystore2_system_property::{write, PropertyWatcher, PropertyWatcherError}; 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}; @@ -288,7 +287,6 @@ fn process_key_creation_event_stats<U>( EcCurve::P_256 => MetricsEcCurve::P_256, EcCurve::P_384 => MetricsEcCurve::P_384, EcCurve::P_521 => MetricsEcCurve::P_521, - EcCurve::CURVE_25519 => MetricsEcCurve::CURVE_25519, _ => MetricsEcCurve::EC_CURVE_UNSPECIFIED, } } @@ -562,14 +560,10 @@ fn pull_storage_stats() -> Result<Vec<KeystoreAtom>> { 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("In pull_attestation_pool_stats: Failed to compute expired by system time.")? .duration_since(UNIX_EPOCH) - .context("In pull_attestation_pool_stats: Failed to compute expired by duration.")? - .as_millis() as i64; + .unwrap_or_else(|_| Duration::new(0, 0)) + .as_secs() as i64; let result = get_pool_status(expired_by, *sec_level); @@ -599,9 +593,8 @@ fn pull_attestation_pool_stats() -> Result<Vec<KeystoreAtom>> { } /// 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 { rkpError: rkp_error, security_level: process_security_level(*sec_level) }); +pub fn log_rkp_error_stats(rkp_error: MetricsRkpError) { + let rkp_error_stats = KeystoreAtomPayload::RkpErrorStats(RkpErrorStats { rkpError: rkp_error }); METRICS_STORE.insert_atom(AtomID::RKP_ERROR_STATS, rkp_error_stats); } @@ -633,9 +626,7 @@ pub fn update_keystore_crash_sysprop() { } }; - if let Err(e) = - rustutils::system_properties::write(KEYSTORE_CRASH_COUNT_PROPERTY, &new_count.to_string()) - { + if let Err(e) = write(KEYSTORE_CRASH_COUNT_PROPERTY, &new_count.to_string()) { log::error!( concat!( "In update_keystore_crash_sysprop:: ", @@ -648,11 +639,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("In read_keystore_crash_count: Failed read property.")? - .context("In read_keystore_crash_count: Property not set.")? - .parse::<i32>() - .map_err(std::convert::Into::into) + let mut prop_reader = PropertyWatcher::new("keystore.crash_count").context(concat!( + "In read_keystore_crash_count: Failed to create reader a PropertyWatcher." + ))?; + prop_reader + .read(|_n, v| v.parse::<i32>().map_err(std::convert::Into::into)) + .context("In read_keystore_crash_count: Failed to read the existing system 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 5da3b326..71718642 100644 --- a/keystore2/src/operation.rs +++ b/keystore2/src/operation.rs @@ -128,12 +128,12 @@ use crate::enforcements::AuthInfo; use crate::error::{map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, ResponseCode}; use crate::metrics_store::log_key_operation_event_stats; -use crate::utils::watchdog as wd; +use crate::utils::{watchdog as wd, Asp}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ IKeyMintOperation::IKeyMintOperation, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, }; -use android_hardware_security_keymint::binder::{BinderFeatures, Strong}; +use android_hardware_security_keymint::binder::BinderFeatures; use android_system_keystore2::aidl::android::system::keystore2::{ IKeystoreOperation::BnKeystoreOperation, IKeystoreOperation::IKeystoreOperation, }; @@ -170,7 +170,7 @@ pub enum Outcome { pub struct Operation { // The index of this operation in the OperationDb. index: usize, - km_op: Strong<dyn IKeyMintOperation>, + km_op: Asp, last_usage: Mutex<Instant>, outcome: Mutex<Outcome>, owner: u32, // Uid of the operation's owner. @@ -222,7 +222,7 @@ impl Operation { ) -> Self { Self { index, - km_op, + km_op: Asp::new(km_op.as_binder()), last_usage: Mutex::new(Instant::now()), outcome: Mutex::new(Outcome::Unknown), owner, @@ -282,10 +282,19 @@ impl Operation { } *locked_outcome = Outcome::Pruned; + let km_op: binder::public_api::Strong<dyn IKeyMintOperation> = + match self.km_op.get_interface() { + Ok(km_op) => km_op, + Err(e) => { + log::error!("In prune: Failed to get KeyMintOperation interface.\n {:?}", e); + return Err(Error::sys()); + } + }; + let _wp = wd::watch_millis("In Operation::prune: calling abort()", 500); // 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()) { + if let Err(e) = map_km_error(km_op.abort()) { log::error!("In prune: KeyMint::abort failed with {:?}.", e); } @@ -353,6 +362,9 @@ impl Operation { Self::check_input_length(aad_input).context("In update_aad")?; self.touch(); + let km_op: binder::public_api::Strong<dyn IKeyMintOperation> = + self.km_op.get_interface().context("In update: Failed to get KeyMintOperation.")?; + let (hat, tst) = self .auth_info .lock() @@ -362,7 +374,7 @@ impl Operation { self.update_outcome(&mut *outcome, { let _wp = wd::watch_millis("Operation::update_aad: calling updateAad", 500); - map_km_error(self.km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref())) + map_km_error(km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref())) }) .context("In update_aad: KeyMint::update failed.")?; @@ -376,6 +388,9 @@ impl Operation { Self::check_input_length(input).context("In update")?; self.touch(); + let km_op: binder::public_api::Strong<dyn IKeyMintOperation> = + self.km_op.get_interface().context("In update: Failed to get KeyMintOperation.")?; + let (hat, tst) = self .auth_info .lock() @@ -386,7 +401,7 @@ impl Operation { let output = self .update_outcome(&mut *outcome, { let _wp = wd::watch_millis("Operation::update: calling update", 500); - map_km_error(self.km_op.update(input, hat.as_ref(), tst.as_ref())) + map_km_error(km_op.update(input, hat.as_ref(), tst.as_ref())) }) .context("In update: KeyMint::update failed.")?; @@ -406,6 +421,9 @@ impl Operation { } self.touch(); + let km_op: binder::public_api::Strong<dyn IKeyMintOperation> = + self.km_op.get_interface().context("In finish: Failed to get KeyMintOperation.")?; + let (hat, tst, confirmation_token) = self .auth_info .lock() @@ -416,7 +434,7 @@ impl Operation { let output = self .update_outcome(&mut *outcome, { let _wp = wd::watch_millis("Operation::finish: calling finish", 500); - map_km_error(self.km_op.finish( + map_km_error(km_op.finish( input, signature, hat.as_ref(), @@ -444,10 +462,12 @@ impl Operation { fn abort(&self, outcome: Outcome) -> Result<()> { let mut locked_outcome = self.check_active().context("In abort")?; *locked_outcome = outcome; + let km_op: binder::public_api::Strong<dyn IKeyMintOperation> = + self.km_op.get_interface().context("In abort: Failed to get KeyMintOperation.")?; { let _wp = wd::watch_millis("Operation::abort: calling abort", 500); - map_km_error(self.km_op.abort()).context("In abort: KeyMint::abort failed.") + map_km_error(km_op.abort()).context("In abort: KeyMint::abort failed.") } } } @@ -493,7 +513,7 @@ impl OperationDb { /// owner uid and returns a new Operation wrapped in a `std::sync::Arc`. pub fn create_operation( &self, - km_op: binder::Strong<dyn IKeyMintOperation>, + km_op: binder::public_api::Strong<dyn IKeyMintOperation>, owner: u32, auth_info: AuthInfo, forced: bool, @@ -771,7 +791,9 @@ impl KeystoreOperation { /// BnKeystoreOperation proxy object. It also enables /// `BinderFeatures::set_requesting_sid` on the new interface, because /// we need it for checking Keystore permissions. - pub fn new_native_binder(operation: Arc<Operation>) -> binder::Strong<dyn IKeystoreOperation> { + pub fn new_native_binder( + operation: Arc<Operation>, + ) -> binder::public_api::Strong<dyn IKeystoreOperation> { BnKeystoreOperation::new_binder( Self { operation: Mutex::new(Some(operation)) }, BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, @@ -819,7 +841,7 @@ impl KeystoreOperation { impl binder::Interface for KeystoreOperation {} impl IKeystoreOperation for KeystoreOperation { - fn updateAad(&self, aad_input: &[u8]) -> binder::Result<()> { + fn updateAad(&self, aad_input: &[u8]) -> binder::public_api::Result<()> { let _wp = wd::watch_millis("IKeystoreOperation::updateAad", 500); map_or_log_err( self.with_locked_operation( @@ -830,7 +852,7 @@ impl IKeystoreOperation for KeystoreOperation { ) } - fn update(&self, input: &[u8]) -> binder::Result<Option<Vec<u8>>> { + fn update(&self, input: &[u8]) -> binder::public_api::Result<Option<Vec<u8>>> { let _wp = wd::watch_millis("IKeystoreOperation::update", 500); map_or_log_err( self.with_locked_operation( @@ -844,7 +866,7 @@ impl IKeystoreOperation for KeystoreOperation { &self, input: Option<&[u8]>, signature: Option<&[u8]>, - ) -> binder::Result<Option<Vec<u8>>> { + ) -> binder::public_api::Result<Option<Vec<u8>>> { let _wp = wd::watch_millis("IKeystoreOperation::finish", 500); map_or_log_err( self.with_locked_operation( @@ -855,7 +877,7 @@ impl IKeystoreOperation for KeystoreOperation { ) } - fn abort(&self) -> binder::Result<()> { + fn abort(&self) -> binder::public_api::Result<()> { let _wp = wd::watch_millis("IKeystoreOperation::abort", 500); map_err_with( self.with_locked_operation( diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs index 22509c46..4add8992 100644 --- a/keystore2/src/permission.rs +++ b/keystore2/src/permission.rs @@ -18,18 +18,23 @@ //! It also provides KeystorePerm and KeyPerm as convenience wrappers for the SELinux permission //! defined by keystore2 and keystore2_key respectively. -use crate::error::Error as KsError; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, }; -use anyhow::Context as AnyhowContext; -use keystore2_selinux as selinux; -use lazy_static::lazy_static; -use selinux::{implement_class, Backend, ClassPermission}; + use std::cmp::PartialEq; use std::convert::From; use std::ffi::CStr; +use crate::error::Error as KsError; +use keystore2_selinux as selinux; + +use anyhow::Context as AnyhowContext; + +use selinux::Backend; + +use lazy_static::lazy_static; + // Replace getcon with a mock in the test situation #[cfg(not(test))] use selinux::getcon; @@ -47,107 +52,273 @@ fn lookup_keystore2_key_context(namespace: i64) -> anyhow::Result<selinux::Conte KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string()) } -implement_class!( +/// ## Background +/// +/// AIDL enums are represented as constants of the form: +/// ``` +/// mod EnumName { +/// pub type EnumName = i32; +/// pub const Variant1: EnumName = <value1>; +/// pub const Variant2: EnumName = <value2>; +/// ... +/// } +///``` +/// This macro wraps the enum in a new type, e.g., `MyPerm` and maps each variant to an SELinux +/// permission while providing the following interface: +/// * From<EnumName> and Into<EnumName> are implemented. Where the implementation of From maps +/// any variant not specified to the default. +/// * Every variant has a constructor with a name corresponding to its lower case SELinux string +/// representation. +/// * `MyPerm.to_selinux(&self)` returns the SELinux string representation of the +/// represented permission. +/// +/// ## Special behavior +/// If the keyword `use` appears as an selinux name `use_` is used as identifier for the +/// constructor function (e.g. `MePerm::use_()`) but the string returned by `to_selinux` will +/// still be `"use"`. +/// +/// ## Example +/// ``` +/// +/// implement_permission!( +/// /// MyPerm documentation. +/// #[derive(Clone, Copy, Debug, PartialEq)] +/// MyPerm from EnumName with default (None, none) {} +/// Variant1, selinux name: variant1; +/// Variant2, selinux name: variant1; +/// } +/// ); +/// ``` +macro_rules! implement_permission_aidl { + // This rule provides the public interface of the macro. And starts the preprocessing + // recursion (see below). + ($(#[$m:meta])* $name:ident from $aidl_name:ident with default ($($def:tt)*) + { $($element:tt)* }) + => { + implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($def)*), [], + $($element)*); + }; + + // The following three rules recurse through the elements of the form + // `<enum variant>, selinux name: <selinux_name>;` + // preprocessing the input. + + // The first rule terminates the recursion and passes the processed arguments to the final + // rule that spills out the implementation. + (@replace_use $($m:meta)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*], ) => { + implement_permission_aidl!(@end $($m)*, $name, $aidl_name, ($($def)*) { $($out)* } ); + }; + + // The second rule is triggered if the selinux name of an element is literally `use`. + // It produces the tuple `<enum variant>, use_, use;` + // and appends it to the out list. + (@replace_use $($m:meta)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*], + $e_name:ident, selinux name: use; $($element:tt)*) + => { + implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($def)*), + [$($out)* $e_name, use_, use;], $($element)*); + }; + + // The third rule is the default rule which replaces every input tuple with + // `<enum variant>, <selinux_name>, <selinux_name>;` + // and appends the result to the out list. + (@replace_use $($m:meta)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*], + $e_name:ident, selinux name: $e_str:ident; $($element:tt)*) + => { + implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($def)*), + [$($out)* $e_name, $e_str, $e_str;], $($element)*); + }; + + (@end $($m:meta)*, $name:ident, $aidl_name:ident, + ($def_name:ident, $def_selinux_name:ident) { + $($element_name:ident, $element_identifier:ident, + $selinux_name:ident;)* + }) + => + { + $(#[$m])* + pub struct $name(pub $aidl_name); + + impl From<$aidl_name> for $name { + fn from (p: $aidl_name) -> Self { + match p { + $aidl_name::$def_name => Self($aidl_name::$def_name), + $($aidl_name::$element_name => Self($aidl_name::$element_name),)* + _ => Self($aidl_name::$def_name), + } + } + } + + impl From<$name> for $aidl_name { + fn from(p: $name) -> $aidl_name { + p.0 + } + } + + impl $name { + /// Returns a string representation of the permission as required by + /// `selinux::check_access`. + pub fn to_selinux(&self) -> &'static str { + match self { + Self($aidl_name::$def_name) => stringify!($def_selinux_name), + $(Self($aidl_name::$element_name) => stringify!($selinux_name),)* + _ => stringify!($def_selinux_name), + } + } + + /// Creates an instance representing a permission with the same name. + pub const fn $def_selinux_name() -> Self { Self($aidl_name::$def_name) } + $( + /// Creates an instance representing a permission with the same name. + pub const fn $element_identifier() -> Self { Self($aidl_name::$element_name) } + )* + } + }; +} + +implement_permission_aidl!( /// KeyPerm provides a convenient abstraction from the SELinux class `keystore2_key`. /// At the same time it maps `KeyPermissions` from the Keystore 2.0 AIDL Grant interface to - /// the SELinux permissions. - #[repr(i32)] - #[selinux(class_name = keystore2_key)] - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum KeyPerm { - /// Checked when convert_storage_key_to_ephemeral is called. - #[selinux(name = convert_storage_key_to_ephemeral)] - ConvertStorageKeyToEphemeral = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0, - /// Checked when the caller tries do delete a key. - #[selinux(name = delete)] - Delete = KeyPermission::DELETE.0, - /// Checked when the caller tries to use a unique id. - #[selinux(name = gen_unique_id)] - GenUniqueId = KeyPermission::GEN_UNIQUE_ID.0, - /// Checked when the caller tries to load a key. - #[selinux(name = get_info)] - GetInfo = KeyPermission::GET_INFO.0, - /// Checked when the caller attempts to grant a key to another uid. - /// Also used for gating key migration attempts. - #[selinux(name = grant)] - Grant = KeyPermission::GRANT.0, - /// Checked when the caller attempts to use Domain::BLOB. - #[selinux(name = manage_blob)] - ManageBlob = KeyPermission::MANAGE_BLOB.0, - /// Checked when the caller tries to create a key which implies rebinding - /// an alias to the new key. - #[selinux(name = rebind)] - Rebind = KeyPermission::REBIND.0, - /// Checked when the caller attempts to create a forced operation. - #[selinux(name = req_forced_op)] - ReqForcedOp = KeyPermission::REQ_FORCED_OP.0, - /// Checked when the caller attempts to update public key artifacts. - #[selinux(name = update)] - Update = KeyPermission::UPDATE.0, - /// Checked when the caller attempts to use a private or public key. - #[selinux(name = use)] - Use = KeyPermission::USE.0, - /// Checked when the caller attempts to use device ids for attestation. - #[selinux(name = use_dev_id)] - UseDevId = KeyPermission::USE_DEV_ID.0, + /// the SELinux permissions. With the implement_permission macro, we conveniently + /// provide mappings between the wire type bit field values, the rust enum and the SELinux + /// string representation. + /// + /// ## Example + /// + /// In this access check `KeyPerm::get_info().to_selinux()` would return the SELinux representation + /// "info". + /// ``` + /// selinux::check_access(source_context, target_context, "keystore2_key", + /// KeyPerm::get_info().to_selinux()); + /// ``` + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + KeyPerm from KeyPermission with default (NONE, none) { + CONVERT_STORAGE_KEY_TO_EPHEMERAL, selinux name: convert_storage_key_to_ephemeral; + DELETE, selinux name: delete; + GEN_UNIQUE_ID, selinux name: gen_unique_id; + GET_INFO, selinux name: get_info; + GRANT, selinux name: grant; + MANAGE_BLOB, selinux name: manage_blob; + REBIND, selinux name: rebind; + REQ_FORCED_OP, selinux name: req_forced_op; + UPDATE, selinux name: update; + USE, selinux name: use; + USE_DEV_ID, selinux name: use_dev_id; } ); -implement_class!( +/// This macro implements an enum with values mapped to SELinux permission names. +/// The below example wraps the enum MyPermission in the tuple struct `MyPerm` and implements +/// * From<i32> and Into<i32> are implemented. Where the implementation of From maps +/// any variant not specified to the default. +/// * Every variant has a constructor with a name corresponding to its lower case SELinux string +/// representation. +/// * `MyPerm.to_selinux(&self)` returns the SELinux string representation of the +/// represented permission. +/// +/// ## Example +/// ``` +/// implement_permission!( +/// /// MyPerm documentation. +/// #[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// MyPerm with default (None = 0, none) { +/// Foo = 1, selinux name: foo; +/// Bar = 2, selinux name: bar; +/// } +/// ); +/// ``` +macro_rules! implement_permission { + // This rule provides the public interface of the macro. And starts the preprocessing + // recursion (see below). + ($(#[$m:meta])* $name:ident with default + ($def_name:ident = $def_val:expr, $def_selinux_name:ident) + { + $($(#[$element_meta:meta])* + $element_name:ident = $element_val:expr, selinux name: $selinux_name:ident;)* + }) + => { + $(#[$m])* + pub enum $name { + /// The default variant of an enum. + $def_name = $def_val, + $( + $(#[$element_meta])* + $element_name = $element_val, + )* + } + + impl From<i32> for $name { + fn from (p: i32) -> Self { + match p { + $def_val => Self::$def_name, + $($element_val => Self::$element_name,)* + _ => Self::$def_name, + } + } + } + + impl From<$name> for i32 { + fn from(p: $name) -> i32 { + p as i32 + } + } + + impl $name { + /// Returns a string representation of the permission as required by + /// `selinux::check_access`. + pub fn to_selinux(&self) -> &'static str { + match self { + Self::$def_name => stringify!($def_selinux_name), + $(Self::$element_name => stringify!($selinux_name),)* + } + } + + /// Creates an instance representing a permission with the same name. + pub const fn $def_selinux_name() -> Self { Self::$def_name } + $( + /// Creates an instance representing a permission with the same name. + pub const fn $selinux_name() -> Self { Self::$element_name } + )* + } + }; +} + +implement_permission!( /// KeystorePerm provides a convenient abstraction from the SELinux class `keystore2`. /// Using the implement_permission macro we get the same features as `KeyPerm`. - #[selinux(class_name = keystore2)] #[derive(Clone, Copy, Debug, PartialEq)] - pub enum KeystorePerm { + KeystorePerm with default (None = 0, none) { /// Checked when a new auth token is installed. - #[selinux(name = add_auth)] - AddAuth, + AddAuth = 1, selinux name: add_auth; /// Checked when an app is uninstalled or wiped. - #[selinux(name = clear_ns)] - ClearNs, + ClearNs = 2, selinux name: clear_ns; /// Checked when the user state is queried from Keystore 2.0. - #[selinux(name = get_state)] - GetState, + GetState = 4, selinux name: get_state; /// 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)] - List, + List = 8, selinux name: list; /// Checked when Keystore 2.0 gets locked. - #[selinux(name = lock)] - Lock, + Lock = 0x10, selinux name: lock; /// Checked when Keystore 2.0 shall be reset. - #[selinux(name = reset)] - Reset, + Reset = 0x20, selinux name: reset; /// Checked when Keystore 2.0 shall be unlocked. - #[selinux(name = unlock)] - Unlock, + Unlock = 0x40, selinux name: unlock; /// Checked when user is added or removed. - #[selinux(name = change_user)] - ChangeUser, + ChangeUser = 0x80, selinux name: change_user; /// Checked when password of the user is changed. - #[selinux(name = change_password)] - ChangePassword, + ChangePassword = 0x100, selinux name: change_password; /// Checked when a UID is cleared. - #[selinux(name = clear_uid)] - ClearUID, + ClearUID = 0x200, selinux name: clear_uid; /// Checked when Credstore calls IKeystoreAuthorization to obtain auth tokens. - #[selinux(name = get_auth_token)] - GetAuthToken, + GetAuthToken = 0x400, selinux name: get_auth_token; /// Checked when earlyBootEnded() is called. - #[selinux(name = early_boot_ended)] - EarlyBootEnded, + EarlyBootEnded = 0x800, selinux name: early_boot_ended; /// Checked when IKeystoreMaintenance::onDeviceOffBody is called. - #[selinux(name = report_off_body)] - ReportOffBody, - /// Checked when IkeystoreMetrics::pullMetrics is called. - #[selinux(name = pull_metrics)] - PullMetrics, + ReportOffBody = 0x1000, selinux name: report_off_body; + /// Checked when IkeystoreMetrics::pullMetris is called. + PullMetrics = 0x2000, selinux name: pull_metrics; /// Checked when IKeystoreMaintenance::deleteAllKeys is called. - #[selinux(name = delete_all_keys)] - DeleteAllKeys, - /// Checked on calls to IRemotelyProvisionedKeyPool::getAttestationKey - #[selinux(name = get_attestation_key)] - GetAttestationKey, + DeleteAllKeys = 0x4000, selinux name: delete_all_keys; } ); @@ -161,17 +332,17 @@ implement_class!( /// /// ## Example /// ``` -/// let perms1 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob, KeyPerm::Grant]; -/// let perms2 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob]; +/// let perms1 = key_perm_set![KeyPerm::use_(), KeyPerm::manage_blob(), KeyPerm::grant()]; +/// let perms2 = key_perm_set![KeyPerm::use_(), KeyPerm::manage_blob()]; /// /// assert!(perms1.includes(perms2)) /// assert!(!perms2.includes(perms1)) /// /// let i = perms1.into_iter(); /// // iteration in ascending order of the permission's numeric representation. -/// assert_eq(Some(KeyPerm::ManageBlob), i.next()); -/// assert_eq(Some(KeyPerm::Grant), i.next()); -/// assert_eq(Some(KeyPerm::Use), i.next()); +/// assert_eq(Some(KeyPerm::manage_blob()), i.next()); +/// assert_eq(Some(KeyPerm::grant()), i.next()); +/// assert_eq(Some(KeyPerm::use_()), i.next()); /// assert_eq(None, i.next()); /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] @@ -202,7 +373,7 @@ mod perm { let p = self.vec.0 & (1 << self.pos); self.pos += 1; if p != 0 { - return Some(KeyPerm::from(p)); + return Some(KeyPerm::from(KeyPermission(p))); } } } @@ -211,7 +382,7 @@ mod perm { impl From<KeyPerm> for KeyPermSet { fn from(p: KeyPerm) -> Self { - Self(p as i32) + Self((p.0).0 as i32) } } @@ -246,7 +417,7 @@ impl KeyPermSet { macro_rules! key_perm_set { () => { KeyPermSet(0) }; ($head:expr $(, $tail:expr)* $(,)?) => { - KeyPermSet($head as i32 $(| $tail as i32)*) + KeyPermSet(($head.0).0 $(| ($tail.0).0)*) }; } @@ -259,14 +430,14 @@ impl IntoIterator for KeyPermSet { } } -/// Uses `selinux::check_permission` to check if the given caller context `caller_cxt` may access +/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` may access /// the given permision `perm` of the `keystore2` security class. pub fn check_keystore_permission(caller_ctx: &CStr, perm: KeystorePerm) -> anyhow::Result<()> { let target_context = getcon().context("check_keystore_permission: getcon failed.")?; - selinux::check_permission(caller_ctx, &target_context, perm) + selinux::check_access(caller_ctx, &target_context, "keystore2", perm.to_selinux()) } -/// Uses `selinux::check_permission` to check if the given caller context `caller_cxt` has +/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` has /// all the permissions indicated in `access_vec` for the target domain indicated by the key /// descriptor `key` in the security class `keystore2_key`. /// @@ -291,24 +462,27 @@ pub fn check_grant_permission( _ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)), }; - selinux::check_permission(caller_ctx, &target_context, KeyPerm::Grant) + selinux::check_access(caller_ctx, &target_context, "keystore2_key", "grant") .context("Grant permission is required when granting.")?; - if access_vec.includes(KeyPerm::Grant) { + if access_vec.includes(KeyPerm::grant()) { return Err(selinux::Error::perm()).context("Grant permission cannot be granted."); } for p in access_vec.into_iter() { - selinux::check_permission(caller_ctx, &target_context, p).context(format!( - "check_grant_permission: check_permission failed. \ - The caller may have tried to grant a permission that they don't possess. {:?}", - p - ))? + selinux::check_access(caller_ctx, &target_context, "keystore2_key", p.to_selinux()) + .context(format!( + concat!( + "check_grant_permission: check_access failed. ", + "The caller may have tried to grant a permission that they don't possess. {:?}" + ), + p + ))? } Ok(()) } -/// Uses `selinux::check_permission` to check if the given caller context `caller_cxt` +/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` /// has the permissions indicated by `perm` for the target domain indicated by the key /// descriptor `key` in the security class `keystore2_key`. /// @@ -318,7 +492,7 @@ pub fn check_grant_permission( /// backend, and the result is used as target context. /// * `Domain::BLOB` Same as SELinux but the "manage_blob" permission is always checked additionally /// to the one supplied in `perm`. -/// * `Domain::GRANT` Does not use selinux::check_permission. Instead the `access_vector` +/// * `Domain::GRANT` Does not use selinux::check_access. Instead the `access_vector` /// parameter is queried for permission, which must be supplied in this case. /// /// ## Return values. @@ -362,7 +536,7 @@ pub fn check_key_permission( match access_vector { Some(_) => { return Err(selinux::Error::perm()) - .context(format!("\"{}\" not granted", perm.name())); + .context(format!("\"{}\" not granted", perm.to_selinux())); } None => { // If DOMAIN_GRANT was selected an access vector must be supplied. @@ -383,7 +557,12 @@ pub fn check_key_permission( .context("Domain::BLOB: Failed to lookup namespace.")?; // If DOMAIN_KEY_BLOB was specified, we check for the "manage_blob" // permission in addition to the requested permission. - selinux::check_permission(caller_ctx, &tctx, KeyPerm::ManageBlob)?; + selinux::check_access( + caller_ctx, + &tctx, + "keystore2_key", + KeyPerm::manage_blob().to_selinux(), + )?; tctx } @@ -393,7 +572,7 @@ pub fn check_key_permission( } }; - selinux::check_permission(caller_ctx, &target_context, perm) + selinux::check_access(caller_ctx, &target_context, "keystore2_key", perm.to_selinux()) } #[cfg(test)] @@ -404,49 +583,49 @@ mod tests { use keystore2_selinux::*; const ALL_PERMS: KeyPermSet = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::UseDevId, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - KeyPerm::Grant, - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - KeyPerm::ConvertStorageKeyToEphemeral, + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::use_dev_id(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + KeyPerm::grant(), + KeyPerm::get_info(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + KeyPerm::convert_storage_key_to_ephemeral(), ]; const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![ - KeyPerm::Delete, - KeyPerm::UseDevId, - // No KeyPerm::Grant - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, + KeyPerm::delete(), + KeyPerm::use_dev_id(), + // No KeyPerm::grant() + KeyPerm::get_info(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), ]; const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::UseDevId, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - // No KeyPerm::Grant - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - KeyPerm::ConvertStorageKeyToEphemeral, + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::use_dev_id(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + // No KeyPerm::grant() + KeyPerm::get_info(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + KeyPerm::convert_storage_key_to_ephemeral(), ]; const UNPRIV_PERMS: KeyPermSet = key_perm_set![ - KeyPerm::Delete, - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, + KeyPerm::delete(), + KeyPerm::get_info(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), ]; /// The su_key namespace as defined in su.te and keystore_key_contexts of the @@ -493,26 +672,28 @@ mod tests { #[test] fn check_keystore_permission_test() -> Result<()> { let system_server_ctx = Context::new("u:r:system_server:s0")?; - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::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()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangeUser).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::add_auth()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::clear_ns()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::get_state()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::lock()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::reset()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::unlock()).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::change_user()).is_ok()); + assert!( + check_keystore_permission(&system_server_ctx, KeystorePerm::change_password()).is_ok() + ); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::clear_uid()).is_ok()); let shell_ctx = Context::new("u:r:shell:s0")?; - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs)); - assert!(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)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Unlock)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangeUser)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::add_auth())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_ns())); + assert!(check_keystore_permission(&shell_ctx, KeystorePerm::get_state()).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())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::unlock())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_user())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_password())); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_uid())); Ok(()) } @@ -527,7 +708,7 @@ mod tests { // attempts to grant the grant permission must always fail even when privileged. assert_perm_failed!(check_grant_permission( &system_server_ctx, - KeyPerm::Grant.into(), + KeyPerm::grant().into(), &key )); // unprivileged grant attempts always fail. shell does not have the grant permission. @@ -547,7 +728,7 @@ mod tests { if is_su { assert!(check_grant_permission(&sctx, NOT_GRANT_PERMS, &key).is_ok()); // attempts to grant the grant permission must always fail even when privileged. - assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::Grant.into(), &key)); + assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::grant().into(), &key)); } else { // unprivileged grant attempts always fail. shell does not have the grant permission. assert_perm_failed!(check_grant_permission(&sctx, UNPRIV_PERMS, &key)); @@ -562,7 +743,7 @@ mod tests { assert_perm_failed!(check_key_permission( 0, &selinux::Context::new("ignored").unwrap(), - KeyPerm::Grant, + KeyPerm::grant(), &key, &Some(UNPRIV_PERMS) )); @@ -570,7 +751,7 @@ mod tests { check_key_permission( 0, &selinux::Context::new("ignored").unwrap(), - KeyPerm::Use, + KeyPerm::use_(), &key, &Some(ALL_PERMS), ) @@ -584,31 +765,61 @@ mod tests { let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None }; - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Use, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Delete, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::GetInfo, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Rebind, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Update, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Grant, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::UseDevId, &key, &None).is_ok()); - assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &key, &None).is_ok()); - - assert!(check_key_permission(0, &shell_ctx, KeyPerm::Use, &key, &None).is_ok()); - assert!(check_key_permission(0, &shell_ctx, KeyPerm::Delete, &key, &None).is_ok()); - assert!(check_key_permission(0, &shell_ctx, KeyPerm::GetInfo, &key, &None).is_ok()); - assert!(check_key_permission(0, &shell_ctx, KeyPerm::Rebind, &key, &None).is_ok()); - assert!(check_key_permission(0, &shell_ctx, KeyPerm::Update, &key, &None).is_ok()); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::Grant, &key, &None)); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ReqForcedOp, &key, &None)); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None)); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None)); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &key, &None)); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::use_(), &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::delete(), &key, &None).is_ok()); + assert!( + check_key_permission(0, &system_server_ctx, KeyPerm::get_info(), &key, &None).is_ok() + ); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::rebind(), &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::update(), &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::grant(), &key, &None).is_ok()); + assert!( + check_key_permission(0, &system_server_ctx, KeyPerm::use_dev_id(), &key, &None).is_ok() + ); + assert!( + check_key_permission(0, &gmscore_app, KeyPerm::gen_unique_id(), &key, &None).is_ok() + ); + + assert!(check_key_permission(0, &shell_ctx, KeyPerm::use_(), &key, &None).is_ok()); + assert!(check_key_permission(0, &shell_ctx, KeyPerm::delete(), &key, &None).is_ok()); + assert!(check_key_permission(0, &shell_ctx, KeyPerm::get_info(), &key, &None).is_ok()); + assert!(check_key_permission(0, &shell_ctx, KeyPerm::rebind(), &key, &None).is_ok()); + assert!(check_key_permission(0, &shell_ctx, KeyPerm::update(), &key, &None).is_ok()); + assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::grant(), &key, &None)); + assert_perm_failed!(check_key_permission( + 0, + &shell_ctx, + KeyPerm::req_forced_op(), + &key, + &None + )); + assert_perm_failed!(check_key_permission( + 0, + &shell_ctx, + KeyPerm::manage_blob(), + &key, + &None + )); + assert_perm_failed!(check_key_permission( + 0, + &shell_ctx, + KeyPerm::use_dev_id(), + &key, + &None + )); + assert_perm_failed!(check_key_permission( + 0, + &shell_ctx, + KeyPerm::gen_unique_id(), + &key, + &None + )); // Also make sure that the permission fails if the caller is not the owner. assert_perm_failed!(check_key_permission( 1, // the owner is 0 &system_server_ctx, - KeyPerm::Use, + KeyPerm::use_(), &key, &None )); @@ -616,18 +827,18 @@ mod tests { assert!(check_key_permission( 1, &system_server_ctx, - KeyPerm::Use, + KeyPerm::use_(), &key, - &Some(key_perm_set![KeyPerm::Use]) + &Some(key_perm_set![KeyPerm::use_()]) ) .is_ok()); // But fail if the grant did not cover the requested permission. assert_perm_failed!(check_key_permission( 1, &system_server_ctx, - KeyPerm::Use, + KeyPerm::use_(), &key, - &Some(key_perm_set![KeyPerm::GetInfo]) + &Some(key_perm_set![KeyPerm::get_info()]) )); Ok(()) @@ -643,24 +854,46 @@ mod tests { blob: None, }; - assert!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::Delete, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::GetInfo, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::Rebind, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::Update, &key, &None).is_ok()); - if is_su { - assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::delete(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::get_info(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::rebind(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::update(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::grant(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::manage_blob(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::use_dev_id(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::gen_unique_id(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::req_forced_op(), &key, &None).is_ok()); } else { - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None)); - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None)); - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None)); - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None)); - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None)); + assert!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::delete(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::get_info(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::rebind(), &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::update(), &key, &None).is_ok()); + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::grant(), &key, &None)); + assert_perm_failed!(check_key_permission( + 0, + &sctx, + KeyPerm::req_forced_op(), + &key, + &None + )); + assert_perm_failed!(check_key_permission( + 0, + &sctx, + KeyPerm::manage_blob(), + &key, + &None + )); + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::use_dev_id(), &key, &None)); + assert_perm_failed!(check_key_permission( + 0, + &sctx, + KeyPerm::gen_unique_id(), + &key, + &None + )); } Ok(()) } @@ -676,9 +909,9 @@ mod tests { }; if is_su { - check_key_permission(0, &sctx, KeyPerm::Use, &key, &None) + check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None) } else { - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None)); + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None)); Ok(()) } } @@ -692,7 +925,7 @@ mod tests { check_key_permission( 0, &selinux::Context::new("ignored").unwrap(), - KeyPerm::Use, + KeyPerm::use_(), &key, &None ) @@ -707,45 +940,45 @@ mod tests { #[test] fn key_perm_set_all_test() { let v = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::UseDevId, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - KeyPerm::Grant, - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use // Test if the macro accepts missing comma at the end of the list. + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::use_dev_id(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + KeyPerm::grant(), + KeyPerm::get_info(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_() // Test if the macro accepts missing comma at the end of the list. ]; let mut i = v.into_iter(); - assert_eq!(i.next().unwrap().name(), "delete"); - assert_eq!(i.next().unwrap().name(), "gen_unique_id"); - assert_eq!(i.next().unwrap().name(), "get_info"); - assert_eq!(i.next().unwrap().name(), "grant"); - assert_eq!(i.next().unwrap().name(), "manage_blob"); - assert_eq!(i.next().unwrap().name(), "rebind"); - assert_eq!(i.next().unwrap().name(), "req_forced_op"); - assert_eq!(i.next().unwrap().name(), "update"); - assert_eq!(i.next().unwrap().name(), "use"); - assert_eq!(i.next().unwrap().name(), "use_dev_id"); + assert_eq!(i.next().unwrap().to_selinux(), "delete"); + assert_eq!(i.next().unwrap().to_selinux(), "gen_unique_id"); + assert_eq!(i.next().unwrap().to_selinux(), "get_info"); + assert_eq!(i.next().unwrap().to_selinux(), "grant"); + assert_eq!(i.next().unwrap().to_selinux(), "manage_blob"); + assert_eq!(i.next().unwrap().to_selinux(), "rebind"); + assert_eq!(i.next().unwrap().to_selinux(), "req_forced_op"); + assert_eq!(i.next().unwrap().to_selinux(), "update"); + assert_eq!(i.next().unwrap().to_selinux(), "use"); + assert_eq!(i.next().unwrap().to_selinux(), "use_dev_id"); assert_eq!(None, i.next()); } #[test] fn key_perm_set_sparse_test() { let v = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - KeyPerm::Update, - KeyPerm::Use, // Test if macro accepts the comma at the end of the list. + KeyPerm::manage_blob(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + KeyPerm::update(), + KeyPerm::use_(), // Test if macro accepts the comma at the end of the list. ]; let mut i = v.into_iter(); - assert_eq!(i.next().unwrap().name(), "gen_unique_id"); - assert_eq!(i.next().unwrap().name(), "manage_blob"); - assert_eq!(i.next().unwrap().name(), "req_forced_op"); - assert_eq!(i.next().unwrap().name(), "update"); - assert_eq!(i.next().unwrap().name(), "use"); + assert_eq!(i.next().unwrap().to_selinux(), "gen_unique_id"); + assert_eq!(i.next().unwrap().to_selinux(), "manage_blob"); + assert_eq!(i.next().unwrap().to_selinux(), "req_forced_op"); + assert_eq!(i.next().unwrap().to_selinux(), "update"); + assert_eq!(i.next().unwrap().to_selinux(), "use"); assert_eq!(None, i.next()); } #[test] @@ -757,23 +990,23 @@ mod tests { #[test] fn key_perm_set_include_subset_test() { let v1 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::UseDevId, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - KeyPerm::Grant, - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::use_dev_id(), + KeyPerm::req_forced_op(), + KeyPerm::gen_unique_id(), + KeyPerm::grant(), + KeyPerm::get_info(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), ]; let v2 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), ]; assert!(v1.includes(v2)); assert!(!v2.includes(v1)); @@ -781,18 +1014,18 @@ mod tests { #[test] fn key_perm_set_include_equal_test() { let v1 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), ]; let v2 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), ]; assert!(v1.includes(v2)); assert!(v2.includes(v1)); @@ -800,29 +1033,33 @@ mod tests { #[test] fn key_perm_set_include_overlap_test() { let v1 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::Grant, // only in v1 - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::grant(), // only in v1 + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), ]; let v2 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::ReqForcedOp, // only in v2 - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, + KeyPerm::manage_blob(), + KeyPerm::delete(), + KeyPerm::req_forced_op(), // only in v2 + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), ]; assert!(!v1.includes(v2)); assert!(!v2.includes(v1)); } #[test] fn key_perm_set_include_no_overlap_test() { - let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,]; - let v2 = - key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,]; + let v1 = key_perm_set![KeyPerm::manage_blob(), KeyPerm::delete(), KeyPerm::grant(),]; + let v2 = key_perm_set![ + KeyPerm::req_forced_op(), + KeyPerm::rebind(), + KeyPerm::update(), + KeyPerm::use_(), + ]; assert!(!v1.includes(v2)); assert!(!v2.includes(v1)); } diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs index 4ce9dceb..cd549151 100644 --- a/keystore2/src/raw_device.rs +++ b/keystore2/src/raw_device.rs @@ -16,9 +16,8 @@ use crate::{ database::{ - BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, - KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB, - SubComponentType, Uuid, + BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits, + KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB, SubComponentType, Uuid, }, error::{map_km_error, Error, ErrorCode}, globals::get_keymint_device, @@ -60,16 +59,14 @@ impl KeyMintDevice { pub const KEY_MASTER_V4_1: i32 = 41; /// Version number of KeyMintDevice@V1 pub const KEY_MINT_V1: i32 = 100; - /// Version number of KeyMintDevice@V2 - pub const KEY_MINT_V2: i32 = 200; /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`] pub fn get(security_level: SecurityLevel) -> Result<KeyMintDevice> { - let (km_dev, hw_info, km_uuid) = get_keymint_device(&security_level) + let (asp, hw_info, km_uuid) = get_keymint_device(&security_level) .context("In KeyMintDevice::get: get_keymint_device failed")?; Ok(KeyMintDevice { - km_dev, + km_dev: asp.get_interface()?, km_uuid, version: hw_info.versionNumber, security_level: hw_info.securityLevel, @@ -123,10 +120,10 @@ impl KeyMintDevice { blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid)); db.store_new_key( - key_desc, + &key_desc, key_type, &key_parameters, - &BlobInfo::new(&creation_result.keyBlob, &blob_metadata), + &(&creation_result.keyBlob, &blob_metadata), &CertificateInfo::new(None, None), &key_metadata, &self.km_uuid, @@ -151,7 +148,7 @@ impl KeyMintDevice { key_desc: &KeyDescriptor, key_type: KeyType, ) -> Result<(KeyIdGuard, KeyEntry)> { - db.load_key_entry(key_desc, key_type, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| Ok(())) + db.load_key_entry(&key_desc, key_type, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| Ok(())) .context("In lookup_from_desc: load_key_entry failed.") } @@ -231,8 +228,8 @@ impl KeyMintDevice { }; } - self.create_and_store_key(db, key_desc, key_type, |km_dev| { - km_dev.generateKey(params, None) + self.create_and_store_key(db, &key_desc, key_type, |km_dev| { + km_dev.generateKey(¶ms, None) }) .context("In lookup_or_generate_key: generate_and_store_key failed")?; Self::lookup_from_desc(db, key_desc, key_type) diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs index ea2698f0..212bf399 100644 --- a/keystore2/src/remote_provisioning.rs +++ b/keystore2/src/remote_provisioning.rs @@ -30,27 +30,21 @@ use android_hardware_security_keymint::aidl::android::hardware::security::keymin }; use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{ AttestationPoolStatus::AttestationPoolStatus, IRemoteProvisioning::BnRemoteProvisioning, - IRemoteProvisioning::IRemoteProvisioning, - IRemotelyProvisionedKeyPool::BnRemotelyProvisionedKeyPool, - IRemotelyProvisionedKeyPool::IRemotelyProvisionedKeyPool, ImplInfo::ImplInfo, - RemotelyProvisionedKey::RemotelyProvisionedKey, + IRemoteProvisioning::IRemoteProvisioning, ImplInfo::ImplInfo, }; use android_security_remoteprovisioning::binder::{BinderFeatures, Strong}; 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::database::{CertificateChain, 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::metrics_store::log_rkp_error_stats; -use crate::permission::KeystorePerm; -use crate::utils::{check_keystore_permission, watchdog as wd}; +use crate::utils::{watchdog as wd, Asp}; 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, @@ -62,43 +56,17 @@ pub struct RemProvState { 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) } } - /// Returns the uuid for the KM instance attached to this RemProvState struct. - pub fn get_uuid(&self) -> Uuid { - self.km_uuid - } - - fn is_rkp_only(&self) -> bool { - let default_value = false; - - let property_name = match self.security_level { - SecurityLevel::STRONGBOX => "remote_provisioning.strongbox.rkp_only", - SecurityLevel::TRUSTED_ENVIRONMENT => "remote_provisioning.tee.rkp_only", - _ => return default_value, - }; - - rustutils::system_properties::read_bool(property_name, default_value) - .unwrap_or(default_value) - } - /// Checks if remote provisioning is enabled and partially caches the result. On a hybrid system /// remote provisioning can flip from being disabled to enabled depending on responses from the /// server, so unfortunately caching the presence or absence of the HAL is not enough to fully /// make decisions about the state of remote provisioning during runtime. fn check_rem_prov_enabled(&self, db: &mut KeystoreDB) -> Result<bool> { - if self.is_rkp_only() { - return Ok(true); - } if !self.is_hal_present.load(Ordering::Relaxed) || get_remotely_provisioned_component(&self.security_level).is_err() { @@ -115,6 +83,70 @@ impl RemProvState { Ok(pool_status.total != 0) } + /// 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` + fn get_rem_prov_attest_key( + &self, + key: &KeyDescriptor, + caller_uid: u32, + db: &mut KeystoreDB, + ) -> Result<Option<CertificateChain>> { + match key.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. + self.get_rem_prov_attest_key_helper(key, caller_uid, db) + .context("In get_rem_prov_attest_key: Failed to get a key")? + .map_or_else( + || self.get_rem_prov_attest_key_helper(key, caller_uid, db), + |v| Ok(Some(v)), + ) + .context(concat!( + "In get_rem_prov_attest_key: Failed to get a key after", + "attempting to assign one." + ))? + .map_or_else( + || { + Err(Error::sys()).context(concat!( + "In get_rem_prov_attest_key: Attempted to assign a ", + "key and failed silently. Something is very wrong." + )) + }, + |cert_chain| Ok(Some(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( + &self, + key: &KeyDescriptor, + caller_uid: u32, + db: &mut KeystoreDB, + ) -> Result<Option<CertificateChain>> { + let cert_chain = db + .retrieve_attestation_key_and_cert_chain(key.domain, caller_uid as i64, &self.km_uuid) + .context("In get_rem_prov_attest_key_helper: Failed to retrieve a key + cert chain")?; + match cert_chain { + Some(cert_chain) => Ok(Some(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(key.domain, caller_uid as i64, &self.km_uuid) + .context("In get_rem_prov_attest_key_helper: Failed to assign a key")?; + Ok(None) + } + } + } + fn is_asymmetric_key(&self, params: &[KeyParameter]) -> bool { params.iter().any(|kp| { matches!( @@ -142,7 +174,7 @@ impl RemProvState { caller_uid: u32, params: &[KeyParameter], db: &mut KeystoreDB, - ) -> Result<Option<(KeyIdGuard, AttestationKey, Certificate)>> { + ) -> Result<Option<(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 @@ -150,22 +182,20 @@ impl RemProvState { // and therefore will not be attested. Ok(None) } else { - match get_rem_prov_attest_key(key.domain, caller_uid, db, &self.km_uuid) { + match self.get_rem_prov_attest_key(&key, caller_uid, db) { Err(e) => { log::error!( - "In get_remote_provisioning_key_and_certs: Error occurred: {:?}", + concat!( + "In get_remote_provisioning_key_and_certs: Failed to get ", + "attestation key. {:?}" + ), e ); - if self.is_rkp_only() { - return Err(e); - } - log_rkp_error_stats(MetricsRkpError::FALL_BACK_DURING_HYBRID, - &self.security_level); + log_rkp_error_stats(MetricsRkpError::FALL_BACK_DURING_HYBRID); Ok(None) } Ok(v) => match v { - Some((guard, cert_chain)) => Ok(Some(( - guard, + Some(cert_chain) => Ok(Some(( AttestationKey { keyBlob: cert_chain.private_key.to_vec(), attestKeyParams: vec![], @@ -188,7 +218,7 @@ impl RemProvState { /// Implementation of the IRemoteProvisioning service. #[derive(Default)] pub struct RemoteProvisioningService { - device_by_sec_level: HashMap<SecurityLevel, Strong<dyn IRemotelyProvisionedComponent>>, + device_by_sec_level: HashMap<SecurityLevel, Asp>, curve_by_sec_level: HashMap<SecurityLevel, i32>, } @@ -196,9 +226,9 @@ impl RemoteProvisioningService { fn get_dev_by_sec_level( &self, sec_level: &SecurityLevel, - ) -> Result<&dyn IRemotelyProvisionedComponent> { + ) -> Result<Strong<dyn IRemotelyProvisionedComponent>> { if let Some(dev) = self.device_by_sec_level.get(sec_level) { - Ok(dev.as_ref()) + dev.get_interface().context("In get_dev_by_sec_level.") } else { Err(error::Error::sys()).context(concat!( "In get_dev_by_sec_level: Remote instance for requested security level", @@ -212,17 +242,21 @@ impl RemoteProvisioningService { let mut result: Self = Default::default(); let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT) .context("In new_native_binder: Failed to get TEE Remote Provisioner instance.")?; + let rkp_tee_dev: Strong<dyn IRemotelyProvisionedComponent> = dev.get_interface()?; result.curve_by_sec_level.insert( SecurityLevel::TRUSTED_ENVIRONMENT, - dev.getHardwareInfo() + rkp_tee_dev + .getHardwareInfo() .context("In new_native_binder: 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) { + let rkp_sb_dev: Strong<dyn IRemotelyProvisionedComponent> = dev.get_interface()?; result.curve_by_sec_level.insert( SecurityLevel::STRONGBOX, - dev.getHardwareInfo() + rkp_sb_dev + .getHardwareInfo() .context("In new_native_binder: Failed to get hardware info for StrongBox.")? .supportedEekCurve, ); @@ -231,27 +265,6 @@ impl RemoteProvisioningService { 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( - "In extract_payload_from_cose_mac: COSE_Mac0 returned from IRPC cannot be parsed", - )?; - if cose_mac0.len() != COSE_MAC0_LEN { - return Err(error::Error::sys()).context(format!( - "In extract_payload_from_cose_mac: 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("In extract_payload_from_cose_mac: COSE_Mac0 payload is malformed.")?), - _ => Err(error::Error::sys()).context( - "In extract_payload_from_cose_mac: 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, @@ -281,7 +294,7 @@ impl RemoteProvisioningService { .map(|key| MacedPublicKey { macedKey: key.to_vec() }) .collect()) })?; - let mac = map_rem_prov_error(dev.generateCertificateRequest( + let mut mac = map_rem_prov_error(dev.generateCertificateRequest( test_mode, &keys_to_sign, eek, @@ -290,16 +303,30 @@ impl RemoteProvisioningService { protected_data, )) .context("In generate_csr: Failed to generate csr")?; - let mut mac_and_keys: Vec<Value> = vec![Value::from(mac)]; + // TODO(b/180392379): Replace this manual CBOR generation with the cbor-serde crate as well. + // This generates an array consisting of the mac and the public key Maps. + // Just generate the actual MacedPublicKeys structure when the crate is + // available. + let mut cose_mac_0: Vec<u8> = vec![ + (0b100_00000 | (keys_to_sign.len() + 1)) as u8, + 0b010_11000, // mac + (mac.len() as u8), + ]; + cose_mac_0.append(&mut mac); + // If this is a test mode key, there is an extra 6 bytes added as an additional entry in + // the COSE_Key struct to denote that. + let test_mode_entry_shift = if test_mode { 0 } else { 6 }; + let byte_dist_mac0_payload = 8; + let cose_key_size = 83 - test_mode_entry_shift; for maced_public_key in keys_to_sign { - mac_and_keys.push( - Self::extract_payload_from_cose_mac(&maced_public_key.macedKey) - .context("In generate_csr: Failed to get the payload from the COSE_Mac0")?, - ) + if maced_public_key.macedKey.len() > cose_key_size + byte_dist_mac0_payload { + cose_mac_0.extend_from_slice( + &maced_public_key.macedKey + [byte_dist_mac0_payload..cose_key_size + byte_dist_mac0_payload], + ); + } } - let cbor_array: Value = Value::Array(mac_and_keys); - serde_cbor::to_vec(&cbor_array) - .context("In generate_csr: Failed to serialize the mac and keys array") + Ok(cose_mac_0) } /// Provisions a certificate chain for a key whose CSR was included in generate_csr. The @@ -309,106 +336,52 @@ impl RemoteProvisioningService { /// 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( - "In parse_cose_mac0_for_coords: COSE_Mac0 returned from IRPC cannot be parsed", - )?; - if cose_mac0.len() != COSE_MAC0_LEN { - return Err(error::Error::sys()).context(format!( - "In parse_cose_mac0_for_coords: 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("In parse_cose_mac0_for_coords: COSE_Key is malformed.")?, - _ => Err(error::Error::sys()) - .context("In parse_cose_mac0_for_coords: 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( - "In parse_cose_mac0_for_coords: \ - 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(format!( - "In parse_cose_mac0_for_coords: COSE_Key X-coordinate is not the right length. \ - Expected: 32; Actual: {}", - x_coord.len() - )) - } - _ => { - return Err(error::Error::sys()) - .context("In parse_cose_mac0_for_coords: 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(format!( - "In parse_cose_mac0_for_coords: COSE_Key Y-coordinate is not the right length. \ - Expected: 32; Actual: {}", - y_coord.len() - )) - } - _ => { - return Err(error::Error::sys()) - .context("In parse_cose_mac0_for_coords: COSE_Key Y-coordinate is not a bstr") - } - } - Ok(raw_key) + DB.with::<_, Result<()>>(|db| { + let mut db = db.borrow_mut(); + 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, + ) + }) } /// 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<()> { + pub fn generate_key_pair(&self, 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(format!( - "In generate_key_pair: Failed to get device for security level {:?}", - sec_level - ))?; + let dev = self.get_dev_by_sec_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("In generate_key_pair: Failed to generated ECDSA keypair.")?; - let raw_key = Self::parse_cose_mac0_for_coords(&maced_key.macedKey) - .context("In generate_key_pair: Failed to parse raw key")?; - db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid) - .context("In generate_key_pair: Failed to insert attestation key entry") + // TODO(b/180392379): This is a brittle hack that relies on the consistent formatting of + // the returned CBOR blob in order to extract the public key. + let data = &maced_key.macedKey; + if data.len() < 85 { + return Err(error::Error::sys()).context(concat!( + "In generate_key_pair: CBOR blob returned from", + "RemotelyProvisionedComponent is definitely malformatted or empty." + )); + } + let mut raw_key: Vec<u8> = vec![0; 64]; + raw_key[0..32].clone_from_slice(&data[18..18 + 32]); + raw_key[32..64].clone_from_slice(&data[53..53 + 32]); + DB.with::<_, Result<()>>(|db| { + let mut db = db.borrow_mut(); + db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid) + }) } /// Checks the security level of each available IRemotelyProvisionedComponent hal and returns @@ -447,70 +420,6 @@ pub fn get_pool_status(expired_by: i64, sec_level: SecurityLevel) -> Result<Atte }) } -/// 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` -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(concat!( - "In get_rem_prov_attest_key: Failed to get a key after", - "attempting to assign one." - ))? - .map_or_else( - || { - Err(Error::sys()).context(concat!( - "In get_rem_prov_attest_key: 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, - 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("In get_rem_prov_attest_key_helper: 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("In get_rem_prov_attest_key_helper: Failed to assign a key")?; - Ok(None) - } - } -} - impl binder::Interface for RemoteProvisioningService {} // Implementation of IRemoteProvisioning. See AIDL spec at @@ -520,7 +429,7 @@ impl IRemoteProvisioning for RemoteProvisioningService { &self, expired_by: i64, sec_level: SecurityLevel, - ) -> binder::Result<AttestationPoolStatus> { + ) -> binder::public_api::Result<AttestationPoolStatus> { let _wp = wd::watch_millis("IRemoteProvisioning::getPoolStatus", 500); map_or_log_err(get_pool_status(expired_by, sec_level), Ok) } @@ -534,7 +443,7 @@ impl IRemoteProvisioning for RemoteProvisioningService { sec_level: SecurityLevel, protected_data: &mut ProtectedData, device_info: &mut DeviceInfo, - ) -> binder::Result<Vec<u8>> { + ) -> binder::public_api::Result<Vec<u8>> { let _wp = wd::watch_millis("IRemoteProvisioning::generateCsr", 500); map_or_log_err( self.generate_csr( @@ -557,516 +466,30 @@ impl IRemoteProvisioning for RemoteProvisioningService { certs: &[u8], expiration_date: i64, sec_level: SecurityLevel, - ) -> binder::Result<()> { + ) -> binder::public_api::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, - ) - }) + map_or_log_err( + self.provision_cert_chain(public_key, batch_cert, certs, expiration_date, sec_level), + Ok, + ) } - fn generateKeyPair(&self, is_test_mode: bool, sec_level: SecurityLevel) -> binder::Result<()> { + fn generateKeyPair( + &self, + is_test_mode: bool, + sec_level: SecurityLevel, + ) -> binder::public_api::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, - ) - }) + map_or_log_err(self.generate_key_pair(is_test_mode, sec_level), Ok) } - fn getImplementationInfo(&self) -> binder::Result<Vec<ImplInfo>> { + fn getImplementationInfo(&self) -> binder::public_api::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> { + fn deleteAllKeys(&self) -> binder::public_api::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` 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("In get_attestation_key")?; - 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)) - .context("In get_attestation_key: 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("In new_native_binder: 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_keymint::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()) - } - } - - // 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) - ); - } - - #[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) - ); - } - - #[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() - ); - } -} diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs index 28de1ec8..5cb3afcd 100644 --- a/keystore2/src/security_level.rs +++ b/keystore2/src/security_level.rs @@ -18,18 +18,17 @@ use crate::attestation_key_utils::{get_attest_key_info, AttestationKeyInfo}; 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::database::{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::globals::{DB, ENFORCEMENTS, LEGACY_MIGRATOR, SUPER_KEY}; use crate::key_parameter::KeyParameter as KsKeyParam; use crate::key_parameter::KeyParameterValue as KsKeyParamValue; use crate::metrics_store::log_key_creation_event_stats; use crate::remote_provisioning::RemProvState; use crate::super_key::{KeyBlob, SuperKeyManager}; use crate::utils::{ - check_device_attestation_permissions, check_key_permission, - check_unique_id_attestation_permissions, is_device_id_attestation_tag, - key_characteristics_to_internal, uid_to_android_user, watchdog as wd, + check_device_attestation_permissions, check_key_permission, is_device_id_attestation_tag, + key_characteristics_to_internal, uid_to_android_user, watchdog as wd, Asp, }; use crate::{ database::{ @@ -55,16 +54,14 @@ use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, EphemeralStorageKeyResponse::EphemeralStorageKeyResponse, IKeystoreOperation::IKeystoreOperation, IKeystoreSecurityLevel::BnKeystoreSecurityLevel, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, - KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode, + KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, }; use anyhow::{anyhow, Context, Result}; -use std::convert::TryInto; -use std::time::SystemTime; /// Implementation of the IKeystoreSecurityLevel Interface. pub struct KeystoreSecurityLevel { security_level: SecurityLevel, - keymint: Strong<dyn IKeyMintDevice>, + keymint: Asp, hw_info: KeyMintHardwareInfo, km_uuid: Uuid, operation_db: OperationDb, @@ -133,7 +130,8 @@ impl KeystoreSecurityLevel { _ => Some( certificate_chain .iter() - .flat_map(|c| c.encodedCertificate.iter()) + .map(|c| c.encodedCertificate.iter()) + .flatten() .copied() .collect(), ), @@ -160,11 +158,9 @@ impl KeystoreSecurityLevel { let mut db = db.borrow_mut(); let (key_blob, mut blob_metadata) = SUPER_KEY - .read() - .unwrap() .handle_super_encryption_on_key_init( &mut db, - &LEGACY_IMPORTER, + &LEGACY_MIGRATOR, &(key.domain), &key_parameters, flags, @@ -182,7 +178,7 @@ impl KeystoreSecurityLevel { &key, KeyType::Client, &key_parameters, - &BlobInfo::new(&key_blob, &blob_metadata), + &(&key_blob, &blob_metadata), &cert_info, &key_metadata, &self.km_uuid, @@ -220,10 +216,10 @@ impl KeystoreSecurityLevel { let scoping_blob: Vec<u8>; let (km_blob, key_properties, key_id_guard, blob_metadata) = match key.domain { Domain::BLOB => { - check_key_permission(KeyPerm::Use, key, &None) + check_key_permission(KeyPerm::use_(), key, &None) .context("In create_operation: checking use permission for Domain::BLOB.")?; if forced { - check_key_permission(KeyPerm::ReqForcedOp, key, &None).context( + check_key_permission(KeyPerm::req_forced_op(), key, &None).context( "In create_operation: checking forced permission for Domain::BLOB.", )?; } @@ -243,22 +239,18 @@ impl KeystoreSecurityLevel { ) } _ => { - let super_key = SUPER_KEY - .read() - .unwrap() - .get_per_boot_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, || { + LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || { db.borrow_mut().load_key_entry( - key, + &key, KeyType::Client, KeyEntryLoadBits::KM, caller_uid, |k, av| { - check_key_permission(KeyPerm::Use, k, &av)?; + check_key_permission(KeyPerm::use_(), k, &av)?; if forced { - check_key_permission(KeyPerm::ReqForcedOp, k, &av)?; + check_key_permission(KeyPerm::req_forced_op(), k, &av)?; } Ok(()) }, @@ -309,30 +301,28 @@ impl KeystoreSecurityLevel { .context("In create_operation.")?; let km_blob = SUPER_KEY - .read() - .unwrap() .unwrap_key_if_required(&blob_metadata, km_blob) .context("In create_operation. Failed to handle super encryption.")?; + let km_dev: Strong<dyn IKeyMintDevice> = self + .keymint + .get_interface() + .context("In create_operation: Failed to get KeyMint device")?; + let (begin_result, upgraded_blob) = self .upgrade_keyblob_if_required_with( - &*self.keymint, + &*km_dev, key_id_guard, &km_blob, - blob_metadata.km_uuid().copied(), - operation_parameters, + &blob_metadata, + &operation_parameters, |blob| loop { match map_km_error({ let _wp = self.watch_millis( "In KeystoreSecurityLevel::create_operation: calling begin", 500, ); - self.keymint.begin( - purpose, - blob, - operation_parameters, - immediate_hat.as_ref(), - ) + km_dev.begin(purpose, blob, &operation_parameters, immediate_hat.as_ref()) }) { Err(Error::Km(ErrorCode::TOO_MANY_OPERATIONS)) => { self.operation_db.prune(caller_uid, forced)?; @@ -376,7 +366,7 @@ impl KeystoreSecurityLevel { } }; - let op_binder: binder::Strong<dyn IKeystoreOperation> = + let op_binder: binder::public_api::Strong<dyn IKeystoreOperation> = KeystoreOperation::new_native_binder(operation) .as_binder() .into_interface() @@ -396,53 +386,25 @@ impl KeystoreSecurityLevel { }) } - fn add_required_parameters( + fn add_certificate_parameters( &self, uid: u32, params: &[KeyParameter], key: &KeyDescriptor, ) -> Result<Vec<KeyParameter>> { let mut result = params.to_vec(); - - // Unconditionally add the CREATION_DATETIME tag and prevent callers from - // specifying it. - if params.iter().any(|kp| kp.tag == Tag::CREATION_DATETIME) { - return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context( - "In KeystoreSecurityLevel::add_required_parameters: \ - Specifying Tag::CREATION_DATETIME is not allowed.", - ); - } - - // 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() - .duration_since(SystemTime::UNIX_EPOCH) - .context( - "In KeystoreSecurityLevel::add_required_parameters: \ - Failed to get epoch time.", - )? - .as_millis() - .try_into() - .context( - "In KeystoreSecurityLevel::add_required_parameters: \ - Failed to convert epoch time.", - )?, - ), - }); - } - // If there is an attestation challenge we need to get an application id. if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) { let aaid = { let _wp = self.watch_millis( - "In KeystoreSecurityLevel::add_required_parameters calling: get_aaid", + "In KeystoreSecurityLevel::add_certificate_parameters calling: get_aaid", 500, ); keystore2_aaid::get_aaid(uid).map_err(|e| { - anyhow!(format!("In add_required_parameters: get_aaid returned status {}.", e)) + anyhow!(format!( + "In add_certificate_parameters: get_aaid returned status {}.", + e + )) }) }?; @@ -453,18 +415,14 @@ impl KeystoreSecurityLevel { } if params.iter().any(|kp| kp.tag == Tag::INCLUDE_UNIQUE_ID) { - if check_key_permission(KeyPerm::GenUniqueId, key, &None).is_err() - && check_unique_id_attestation_permissions().is_err() - { - return Err(Error::perm()).context( - "In add_required_parameters: \ - Caller does not have the permission to generate a unique ID", - ); - } + check_key_permission(KeyPerm::gen_unique_id(), key, &None).context(concat!( + "In add_certificate_parameters: ", + "Caller does not have the permission to generate a unique ID" + ))?; if self.id_rotation_state.had_factory_reset_since_id_rotation().context( - "In add_required_parameters: Call to had_factory_reset_since_id_rotation failed.", + "In add_certificate_parameters: Call to had_factory_reset_since_id_rotation failed." )? { - result.push(KeyParameter { + result.push(KeyParameter{ tag: Tag::RESET_SINCE_ID_ROTATION, value: KeyParameterValue::BoolValue(true), }) @@ -475,7 +433,7 @@ impl KeystoreSecurityLevel { // correct Android permission. if params.iter().any(|kp| is_device_id_attestation_tag(kp.tag)) { check_device_attestation_permissions().context(concat!( - "In add_required_parameters: ", + "In add_certificate_parameters: ", "Caller does not have the permission to attest device identifiers." ))?; } @@ -529,7 +487,7 @@ impl KeystoreSecurityLevel { // generate_key requires the rebind permission. // Must return on error for security reasons. - check_key_permission(KeyPerm::Rebind, &key, &None).context("In generate_key.")?; + check_key_permission(KeyPerm::rebind(), &key, &None).context("In generate_key.")?; let attestation_key_info = match (key.domain, attest_key_descriptor) { (Domain::BLOB, _) => None, @@ -547,9 +505,11 @@ impl KeystoreSecurityLevel { .context("In generate_key: Trying to get an attestation key")?, }; let params = self - .add_required_parameters(caller_uid, params, &key) + .add_certificate_parameters(caller_uid, params, &key) .context("In generate_key: Trying to get aaid.")?; + let km_dev: Strong<dyn IKeyMintDevice> = self.keymint.get_interface()?; + let creation_result = match attestation_key_info { Some(AttestationKeyInfo::UserGenerated { key_id_guard, @@ -558,10 +518,10 @@ impl KeystoreSecurityLevel { issuer_subject, }) => self .upgrade_keyblob_if_required_with( - &*self.keymint, + &*km_dev, Some(key_id_guard), &KeyBlob::Ref(&blob), - blob_metadata.km_uuid().copied(), + &blob_metadata, ¶ms, |blob| { let attest_key = Some(AttestationKey { @@ -577,46 +537,29 @@ impl KeystoreSecurityLevel { ), 5000, // Generate can take a little longer. ); - self.keymint.generateKey(¶ms, attest_key.as_ref()) + km_dev.generateKey(¶ms, attest_key.as_ref()) }) }, ) .context("In generate_key: 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()) - }) - }, - ) + Some(AttestationKeyInfo::RemoteProvisioned { attestation_key, attestation_certs }) => { + map_km_error({ + let _wp = self.watch_millis( + concat!( + "In KeystoreSecurityLevel::generate_key (RemoteProvisioned): ", + "calling generate_key.", + ), + 5000, // Generate can take a little longer. + ); + km_dev.generateKey(¶ms, Some(&attestation_key)) + }) .context("While generating Key with remote provisioned attestation key.") - .map(|(mut result, _)| { - result.certificateChain.push(attestation_certs); - result - }), + .map(|mut creation_result| { + creation_result.certificateChain.push(attestation_certs); + creation_result + }) + } None => map_km_error({ let _wp = self.watch_millis( concat!( @@ -625,7 +568,7 @@ impl KeystoreSecurityLevel { ), 5000, // Generate can take a little longer. ); - self.keymint.generateKey(¶ms, None) + km_dev.generateKey(¶ms, None) }) .context("While generating Key without explicit attestation key."), } @@ -660,10 +603,10 @@ impl KeystoreSecurityLevel { }; // import_key requires the rebind permission. - check_key_permission(KeyPerm::Rebind, &key, &None).context("In import_key.")?; + check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_key.")?; let params = self - .add_required_parameters(caller_uid, params, &key) + .add_certificate_parameters(caller_uid, params, &key) .context("In import_key: Trying to get aaid.")?; let format = params @@ -682,7 +625,8 @@ impl KeystoreSecurityLevel { }) .context("In import_key.")?; - let km_dev = &self.keymint; + let km_dev: Strong<dyn IKeyMintDevice> = + self.keymint.get_interface().context("In import_key: Trying to get the KM device")?; let creation_result = map_km_error({ let _wp = self.watch_millis("In KeystoreSecurityLevel::import_key: calling importKey.", 500); @@ -744,19 +688,17 @@ impl KeystoreSecurityLevel { }; // Import_wrapped_key requires the rebind permission for the new key. - check_key_permission(KeyPerm::Rebind, &key, &None).context("In import_wrapped_key.")?; - - let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id); + check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_wrapped_key.")?; let (wrapping_key_id_guard, mut wrapping_key_entry) = DB .with(|db| { - LEGACY_IMPORTER.with_try_import(&key, caller_uid, super_key, || { + LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || { db.borrow_mut().load_key_entry( - wrapping_key, + &wrapping_key, KeyType::Client, KeyEntryLoadBits::KM, caller_uid, - |k, av| check_key_permission(KeyPerm::Use, k, &av), + |k, av| check_key_permission(KeyPerm::use_(), k, &av), ) }) }) @@ -767,11 +709,8 @@ impl KeystoreSecurityLevel { .ok_or_else(error::Error::sys) .context("No km_blob after successfully loading key. This should never happen.")?; - let wrapping_key_blob = SUPER_KEY - .read() - .unwrap() - .unwrap_key_if_required(&wrapping_blob_metadata, &wrapping_key_blob) - .context( + let wrapping_key_blob = + SUPER_KEY.unwrap_key_if_required(&wrapping_blob_metadata, &wrapping_key_blob).context( "In import_wrapped_key. Failed to handle super encryption for wrapping key.", )?; @@ -797,23 +736,24 @@ impl KeystoreSecurityLevel { let masking_key = masking_key.unwrap_or(ZERO_BLOB_32); + let km_dev: Strong<dyn IKeyMintDevice> = self.keymint.get_interface()?; let (creation_result, _) = self .upgrade_keyblob_if_required_with( - &*self.keymint, + &*km_dev, Some(wrapping_key_id_guard), &wrapping_key_blob, - wrapping_blob_metadata.km_uuid().copied(), + &wrapping_blob_metadata, &[], |wrapping_blob| { let _wp = self.watch_millis( "In KeystoreSecurityLevel::import_wrapped_key: calling importWrappedKey.", 500, ); - let creation_result = map_km_error(self.keymint.importWrappedKey( + let creation_result = map_km_error(km_dev.importWrappedKey( wrapped_data, wrapping_blob, masking_key, - params, + ¶ms, pw_sid, fp_sid, ))?; @@ -828,17 +768,17 @@ impl KeystoreSecurityLevel { fn store_upgraded_keyblob( key_id_guard: KeyIdGuard, - km_uuid: Option<Uuid>, + km_uuid: Option<&Uuid>, key_blob: &KeyBlob, upgraded_blob: &[u8], ) -> Result<()> { let (upgraded_blob_to_be_stored, new_blob_metadata) = - SuperKeyManager::reencrypt_if_required(key_blob, upgraded_blob) + SuperKeyManager::reencrypt_if_required(key_blob, &upgraded_blob) .context("In store_upgraded_keyblob: Failed to handle super encryption.")?; let mut new_blob_metadata = new_blob_metadata.unwrap_or_default(); if let Some(uuid) = km_uuid { - new_blob_metadata.add(BlobMetaEntry::KmUuid(uuid)); + new_blob_metadata.add(BlobMetaEntry::KmUuid(*uuid)); } DB.with(|db| { @@ -856,46 +796,69 @@ impl KeystoreSecurityLevel { fn upgrade_keyblob_if_required_with<T, F>( &self, km_dev: &dyn IKeyMintDevice, - mut key_id_guard: Option<KeyIdGuard>, + key_id_guard: Option<KeyIdGuard>, key_blob: &KeyBlob, - km_uuid: Option<Uuid>, + blob_metadata: &BlobMetaData, params: &[KeyParameter], f: F, ) -> Result<(T, Option<Vec<u8>>)> where F: Fn(&[u8]) -> Result<T, Error>, { - let (v, upgraded_blob) = crate::utils::upgrade_keyblob_if_required_with( - km_dev, - key_blob, - params, - f, - |upgraded_blob| { - if key_id_guard.is_some() { - // Unwrap cannot panic, because the is_some was true. - let kid = key_id_guard.take().unwrap(); - Self::store_upgraded_keyblob(kid, km_uuid, key_blob, upgraded_blob).context( - "In upgrade_keyblob_if_required_with: store_upgraded_keyblob failed", + match f(key_blob) { + Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => { + let upgraded_blob = { + let _wp = self.watch_millis( + concat!( + "In KeystoreSecurityLevel::upgrade_keyblob_if_required_with: ", + "calling upgradeKey." + ), + 500, + ); + map_km_error(km_dev.upgradeKey(key_blob, params)) + } + .context("In upgrade_keyblob_if_required_with: Upgrade failed.")?; + + if let Some(kid) = key_id_guard { + Self::store_upgraded_keyblob( + kid, + blob_metadata.km_uuid(), + key_blob, + &upgraded_blob, ) - } else { - Ok(()) + .context( + "In upgrade_keyblob_if_required_with: store_upgraded_keyblob failed", + )?; } - }, - ) - .context("In KeystoreSecurityLevel::upgrade_keyblob_if_required_with.")?; - - // If no upgrade was needed, use the opportunity to reencrypt the blob if required - // and if the a key_id_guard is held. Note: key_id_guard can only be Some if no - // upgrade was performed above and if one was given in the first place. - if key_blob.force_reencrypt() { - if let Some(kid) = key_id_guard { - Self::store_upgraded_keyblob(kid, km_uuid, key_blob, key_blob).context(concat!( - "In upgrade_keyblob_if_required_with: ", - "store_upgraded_keyblob failed in forced reencrypt" - ))?; + + match f(&upgraded_blob) { + Ok(v) => Ok((v, Some(upgraded_blob))), + Err(e) => Err(e).context(concat!( + "In upgrade_keyblob_if_required_with: ", + "Failed to perform operation on second try." + )), + } + } + result => { + if let Some(kid) = key_id_guard { + if key_blob.force_reencrypt() { + Self::store_upgraded_keyblob( + kid, + blob_metadata.km_uuid(), + key_blob, + key_blob, + ) + .context(concat!( + "In upgrade_keyblob_if_required_with: ", + "store_upgraded_keyblob failed in forced reencrypt" + ))?; + } + } + result + .map(|v| (v, None)) + .context("In upgrade_keyblob_if_required_with: Called closure failed.") } } - Ok((v, upgraded_blob)) } fn convert_storage_key_to_ephemeral( @@ -917,10 +880,13 @@ impl KeystoreSecurityLevel { )?; // convert_storage_key_to_ephemeral requires the associated permission - check_key_permission(KeyPerm::ConvertStorageKeyToEphemeral, storage_key, &None) + check_key_permission(KeyPerm::convert_storage_key_to_ephemeral(), storage_key, &None) .context("In convert_storage_key_to_ephemeral: Check permission")?; - let km_dev = &self.keymint; + let km_dev: Strong<dyn IKeyMintDevice> = self.keymint.get_interface().context(concat!( + "In IKeystoreSecurityLevel convert_storage_key_to_ephemeral: ", + "Getting keymint device interface" + ))?; match { let _wp = self.watch_millis( concat!( @@ -976,14 +942,17 @@ impl KeystoreSecurityLevel { .ok_or(error::Error::Km(ErrorCode::INVALID_ARGUMENT)) .context("In IKeystoreSecurityLevel delete_key: No key blob specified")?; - check_key_permission(KeyPerm::Delete, key, &None) + check_key_permission(KeyPerm::delete(), key, &None) .context("In IKeystoreSecurityLevel delete_key: Checking delete permissions")?; - let km_dev = &self.keymint; + let km_dev: Strong<dyn IKeyMintDevice> = self + .keymint + .get_interface() + .context("In IKeystoreSecurityLevel delete_key: Getting keymint device interface")?; { let _wp = self.watch_millis("In KeystoreSecuritylevel::delete_key: calling deleteKey", 500); - map_km_error(km_dev.deleteKey(key_blob)).context("In keymint device deleteKey") + map_km_error(km_dev.deleteKey(&key_blob)).context("In keymint device deleteKey") } } } @@ -996,7 +965,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { key: &KeyDescriptor, operation_parameters: &[KeyParameter], forced: bool, - ) -> binder::Result<CreateOperationResponse> { + ) -> binder::public_api::Result<CreateOperationResponse> { let _wp = self.watch_millis("IKeystoreSecurityLevel::createOperation", 500); map_or_log_err(self.create_operation(key, operation_parameters, forced), Ok) } @@ -1007,7 +976,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { params: &[KeyParameter], flags: i32, entropy: &[u8], - ) -> binder::Result<KeyMetadata> { + ) -> binder::public_api::Result<KeyMetadata> { // Duration is set to 5 seconds, because generateKey - especially for RSA keys, takes more // time than other operations let _wp = self.watch_millis("IKeystoreSecurityLevel::generateKey", 5000); @@ -1023,7 +992,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { params: &[KeyParameter], flags: i32, key_data: &[u8], - ) -> binder::Result<KeyMetadata> { + ) -> binder::public_api::Result<KeyMetadata> { let _wp = self.watch_millis("IKeystoreSecurityLevel::importKey", 500); let result = self.import_key(key, attestation_key, params, flags, key_data); log_key_creation_event_stats(self.security_level, params, &result); @@ -1037,7 +1006,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { masking_key: Option<&[u8]>, params: &[KeyParameter], authenticators: &[AuthenticatorSpec], - ) -> binder::Result<KeyMetadata> { + ) -> binder::public_api::Result<KeyMetadata> { let _wp = self.watch_millis("IKeystoreSecurityLevel::importWrappedKey", 500); let result = self.import_wrapped_key(key, wrapping_key, masking_key, params, authenticators); @@ -1048,11 +1017,11 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { fn convertStorageKeyToEphemeral( &self, storage_key: &KeyDescriptor, - ) -> binder::Result<EphemeralStorageKeyResponse> { + ) -> binder::public_api::Result<EphemeralStorageKeyResponse> { let _wp = self.watch_millis("IKeystoreSecurityLevel::convertStorageKeyToEphemeral", 500); map_or_log_err(self.convert_storage_key_to_ephemeral(storage_key), Ok) } - fn deleteKey(&self, key: &KeyDescriptor) -> binder::Result<()> { + fn deleteKey(&self, key: &KeyDescriptor) -> binder::public_api::Result<()> { let _wp = self.watch_millis("IKeystoreSecurityLevel::deleteKey", 500); let result = self.delete_key(key); log_key_deleted(key, ThreadState::get_calling_uid(), result.is_ok()); diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs index d634e0c0..d65743d2 100644 --- a/keystore2/src/service.rs +++ b/keystore2/src/service.rs @@ -22,11 +22,11 @@ use crate::permission::{KeyPerm, KeystorePerm}; use crate::security_level::KeystoreSecurityLevel; use crate::utils::{ check_grant_permission, check_key_permission, check_keystore_permission, - key_parameters_to_authorizations, list_key_entries, uid_to_android_user, watchdog as wd, + key_parameters_to_authorizations, watchdog as wd, Asp, }; use crate::{ database::Uuid, - globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_IMPORTER, SUPER_KEY}, + globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_MIGRATOR}, }; use crate::{database::KEYSTORE_UUID, permission}; use crate::{ @@ -51,7 +51,7 @@ use keystore2_selinux as selinux; /// Implementation of the IKeystoreService. #[derive(Default)] pub struct KeystoreService { - i_sec_level_by_uuid: HashMap<Uuid, Strong<dyn IKeystoreSecurityLevel>>, + i_sec_level_by_uuid: HashMap<Uuid, Asp>, uuid_by_sec_level: HashMap<SecurityLevel, Uuid>, } @@ -68,20 +68,22 @@ impl KeystoreService { .context(concat!( "In KeystoreService::new_native_binder: ", "Trying to construct mandatory security level TEE." - ))?; + )) + .map(|(dev, uuid)| (Asp::new(dev.as_binder()), uuid))?; result.i_sec_level_by_uuid.insert(uuid, dev); result.uuid_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid); // Strongbox is optional, so we ignore errors and turn the result into an Option. if let Ok((dev, uuid)) = KeystoreSecurityLevel::new_native_binder(SecurityLevel::STRONGBOX, id_rotation_state) + .map(|(dev, uuid)| (Asp::new(dev.as_binder()), uuid)) { result.i_sec_level_by_uuid.insert(uuid, dev); result.uuid_by_sec_level.insert(SecurityLevel::STRONGBOX, uuid); } let uuid_by_sec_level = result.uuid_by_sec_level.clone(); - LEGACY_IMPORTER + LEGACY_MIGRATOR .set_init(move || { (create_thread_local_db(), uuid_by_sec_level, LEGACY_BLOB_LOADER.clone()) }) @@ -105,7 +107,7 @@ impl KeystoreService { fn get_i_sec_level_by_uuid(&self, uuid: &Uuid) -> Result<Strong<dyn IKeystoreSecurityLevel>> { if let Some(dev) = self.i_sec_level_by_uuid.get(uuid) { - Ok(dev.clone()) + dev.get_interface().context("In get_i_sec_level_by_uuid.") } else { Err(error::Error::sys()) .context("In get_i_sec_level_by_uuid: KeyMint instance for key not found.") @@ -121,7 +123,7 @@ impl KeystoreService { .get(&sec_level) .and_then(|uuid| self.i_sec_level_by_uuid.get(uuid)) { - Ok(dev.clone()) + dev.get_interface().context("In get_security_level.") } else { Err(error::Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) .context("In get_security_level: No such security level.") @@ -130,19 +132,15 @@ 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 (key_id_guard, mut key_entry) = DB .with(|db| { - LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { + LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || { db.borrow_mut().load_key_entry( - key, + &key, KeyType::Client, KeyEntryLoadBits::PUBLIC, caller_uid, - |k, av| check_key_permission(KeyPerm::GetInfo, k, &av), + |k, av| check_key_permission(KeyPerm::get_info(), k, &av), ) }) }) @@ -186,18 +184,15 @@ 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)); - DB.with::<_, Result<()>>(|db| { - let entry = match LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { + let entry = match LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || { db.borrow_mut().load_key_entry( - key, + &key, KeyType::Client, KeyEntryLoadBits::NONE, caller_uid, |k, av| { - check_key_permission(KeyPerm::Update, k, &av) + check_key_permission(KeyPerm::update(), k, &av) .context("In update_subcomponent.") }, ) @@ -243,7 +238,7 @@ impl KeystoreService { }; // Security critical: This must return on failure. Do not remove the `?`; - check_key_permission(KeyPerm::Rebind, &key, &None) + check_key_permission(KeyPerm::rebind(), &key, &None) .context("Caller does not have permission to insert this certificate.")?; db.store_new_certificate( @@ -276,32 +271,47 @@ impl KeystoreService { // If the first check fails we check if the caller has the list permission allowing to list // any namespace. In that case we also adjust the queried namespace if a specific uid was // selected. - if let Err(e) = check_key_permission(KeyPerm::GetInfo, &k, &None) { - if let Some(selinux::Error::PermissionDenied) = - e.root_cause().downcast_ref::<selinux::Error>() { - - check_keystore_permission(KeystorePerm::List) - .context("In list_entries: While checking keystore permission.")?; - if namespace != -1 { - k.nspace = namespace; + match check_key_permission(KeyPerm::get_info(), &k, &None) { + Err(e) => { + if let Some(selinux::Error::PermissionDenied) = + e.root_cause().downcast_ref::<selinux::Error>() + { + check_keystore_permission(KeystorePerm::list()) + .context("In list_entries: While checking keystore permission.")?; + if namespace != -1 { + k.nspace = namespace; + } + } else { + return Err(e).context("In list_entries: While checking key permission.")?; } - } else { - return Err(e).context("In list_entries: While checking key permission.")?; } - } + Ok(()) => {} + }; - DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace)) + let mut result = LEGACY_MIGRATOR + .list_uid(k.domain, k.nspace) + .context("In list_entries: Trying to list legacy keys.")?; + + result.append( + &mut DB + .with(|db| { + let mut db = db.borrow_mut(); + db.list(k.domain, k.nspace, KeyType::Client) + }) + .context("In list_entries: Trying to list keystore database.")?, + ); + + result.sort_unstable(); + result.dedup(); + Ok(result) } 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)); - DB.with(|db| { - LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { - db.borrow_mut().unbind_key(key, KeyType::Client, caller_uid, |k, av| { - check_key_permission(KeyPerm::Delete, k, &av).context("During delete_key.") + LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || { + db.borrow_mut().unbind_key(&key, KeyType::Client, caller_uid, |k, av| { + check_key_permission(KeyPerm::delete(), k, &av).context("During delete_key.") }) }) }) @@ -316,13 +326,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)); - DB.with(|db| { - LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || { + LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || { db.borrow_mut().grant( - key, + &key, caller_uid, grantee_uid as u32, access_vector, @@ -335,8 +342,8 @@ impl KeystoreService { fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> Result<()> { DB.with(|db| { - db.borrow_mut().ungrant(key, ThreadState::get_calling_uid(), grantee_uid as u32, |k| { - check_key_permission(KeyPerm::Grant, k, &None) + db.borrow_mut().ungrant(&key, ThreadState::get_calling_uid(), grantee_uid as u32, |k| { + check_key_permission(KeyPerm::grant(), k, &None) }) }) .context("In KeystoreService::ungrant.") @@ -351,13 +358,13 @@ impl IKeystoreService for KeystoreService { fn getSecurityLevel( &self, security_level: SecurityLevel, - ) -> binder::Result<Strong<dyn IKeystoreSecurityLevel>> { + ) -> binder::public_api::Result<Strong<dyn IKeystoreSecurityLevel>> { let _wp = wd::watch_millis_with("IKeystoreService::getSecurityLevel", 500, move || { format!("security_level: {}", security_level.0) }); map_or_log_err(self.get_security_level(security_level), Ok) } - fn getKeyEntry(&self, key: &KeyDescriptor) -> binder::Result<KeyEntryResponse> { + fn getKeyEntry(&self, key: &KeyDescriptor) -> binder::public_api::Result<KeyEntryResponse> { let _wp = wd::watch_millis("IKeystoreService::get_key_entry", 500); map_or_log_err(self.get_key_entry(key), Ok) } @@ -366,15 +373,19 @@ impl IKeystoreService for KeystoreService { key: &KeyDescriptor, public_cert: Option<&[u8]>, certificate_chain: Option<&[u8]>, - ) -> binder::Result<()> { + ) -> binder::public_api::Result<()> { let _wp = wd::watch_millis("IKeystoreService::updateSubcomponent", 500); map_or_log_err(self.update_subcomponent(key, public_cert, certificate_chain), Ok) } - fn listEntries(&self, domain: Domain, namespace: i64) -> binder::Result<Vec<KeyDescriptor>> { + fn listEntries( + &self, + domain: Domain, + namespace: i64, + ) -> binder::public_api::Result<Vec<KeyDescriptor>> { let _wp = wd::watch_millis("IKeystoreService::listEntries", 500); map_or_log_err(self.list_entries(domain, namespace), Ok) } - fn deleteKey(&self, key: &KeyDescriptor) -> binder::Result<()> { + fn deleteKey(&self, key: &KeyDescriptor) -> binder::public_api::Result<()> { let _wp = wd::watch_millis("IKeystoreService::deleteKey", 500); let result = self.delete_key(key); log_key_deleted(key, ThreadState::get_calling_uid(), result.is_ok()); @@ -385,11 +396,11 @@ impl IKeystoreService for KeystoreService { key: &KeyDescriptor, grantee_uid: i32, access_vector: i32, - ) -> binder::Result<KeyDescriptor> { + ) -> binder::public_api::Result<KeyDescriptor> { let _wp = wd::watch_millis("IKeystoreService::grant", 500); map_or_log_err(self.grant(key, grantee_uid, access_vector.into()), Ok) } - fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> binder::Result<()> { + fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> binder::public_api::Result<()> { let _wp = wd::watch_millis("IKeystoreService::ungrant", 500); map_or_log_err(self.ungrant(key, grantee_uid), Ok) } diff --git a/keystore2/src/shared_secret_negotiation.rs b/keystore2/src/shared_secret_negotiation.rs index 42d38d29..64bc2c33 100644 --- a/keystore2/src/shared_secret_negotiation.rs +++ b/keystore2/src/shared_secret_negotiation.rs @@ -15,14 +15,13 @@ //! This module implements the shared secret negotiation. use crate::error::{map_binder_status, map_binder_status_code, Error}; -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, }; use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; -use anyhow::Result; +use anyhow::{Context, Result}; use keystore2_vintf::{get_aidl_instances, get_hidl_instances}; use std::fmt::{self, Display, Formatter}; use std::time::Duration; @@ -44,10 +43,6 @@ pub fn perform_shared_secret_negotiation() { let connected = connect_participants(participants); negotiate_shared_secret(connected); log::info!("Shared secret negotiation concluded successfully."); - - // Once shared secret negotiation is done, the StrongBox and TEE have a common key that - // can be used to authenticate a possible RootOfTrust transfer. - transfer_root_of_trust(); }); } @@ -123,32 +118,47 @@ fn list_participants() -> Result<Vec<SharedSecretParticipant>> { .iter() .map(|(ma, mi)| { get_hidl_instances(KEYMASTER_PACKAGE_NAME, *ma, *mi, KEYMASTER_INTERFACE_NAME) - .into_iter() - .filter_map(|name| { - filter_map_legacy_km_instances(name, (*ma, *mi)).and_then(|sp| { - if let SharedSecretParticipant::Hidl { is_strongbox: true, .. } = &sp { - if !legacy_strongbox_found { - legacy_strongbox_found = true; - return Some(sp); - } - } else if !legacy_default_found { - legacy_default_found = true; - return Some(sp); - } - None - }) + .as_vec() + .with_context(|| format!("Trying to convert KM{}.{} names to vector.", *ma, *mi)) + .map(|instances| { + instances + .into_iter() + .filter_map(|name| { + 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; + return Some(sp); + } + } else if !legacy_default_found { + legacy_default_found = true; + return Some(sp); + } + None + }, + ) + }) + .collect::<Vec<SharedSecretParticipant>>() }) - .collect::<Vec<SharedSecretParticipant>>() }) - .into_iter() - .flatten() - .chain({ - get_aidl_instances(SHARED_SECRET_PACKAGE_NAME, 1, SHARED_SECRET_INTERFACE_NAME) - .into_iter() - .map(SharedSecretParticipant::Aidl) - .collect::<Vec<_>>() - .into_iter() + .collect::<Result<Vec<_>>>() + .map(|v| v.into_iter().flatten()) + .and_then(|i| { + let participants_aidl: Vec<SharedSecretParticipant> = + get_aidl_instances(SHARED_SECRET_PACKAGE_NAME, 1, SHARED_SECRET_INTERFACE_NAME) + .as_vec() + .context("In list_participants: Trying to convert KM1.0 names to vector.")? + .into_iter() + .map(|name| SharedSecretParticipant::Aidl(name.to_string())) + .collect(); + Ok(i.chain(participants_aidl.into_iter())) }) + .context("In list_participants.")? .collect()) } @@ -283,48 +293,3 @@ fn negotiate_shared_secret( } } } - -/// Perform RootOfTrust transfer from TEE to StrongBox (if available). -pub fn transfer_root_of_trust() { - let strongbox = match get_keymint_device(&SecurityLevel::STRONGBOX) { - Ok((s, _, _)) => s, - Err(_e) => { - log::info!("No StrongBox Keymint available, so no RoT transfer"); - return; - } - }; - // Ask the StrongBox KeyMint for a challenge. - let challenge = match strongbox.getRootOfTrustChallenge() { - Ok(data) => data, - Err(e) => { - // If StrongBox doesn't provide a challenge, it might be because: - // - it already has RootOfTrust information - // - it's a KeyMint v1 implementation that doesn't understand the method. - // In either case, we're done. - log::info!("StrongBox does not provide a challenge, so no RoT transfer: {:?}", e); - return; - } - }; - // Get the RoT info from the TEE - let tee = match get_keymint_device(&SecurityLevel::TRUSTED_ENVIRONMENT) { - Ok((s, _, _)) => s, - Err(e) => { - log::error!("No TEE KeyMint implementation found! {:?}", e); - return; - } - }; - let root_of_trust = match tee.getRootOfTrust(&challenge) { - Ok(rot) => rot, - Err(e) => { - log::error!("TEE KeyMint failed to return RootOfTrust info: {:?}", e); - return; - } - }; - // The RootOfTrust information is CBOR-serialized data, but we don't need to parse it. - // Just pass it on to the StrongBox KeyMint instance. - let result = strongbox.sendRootOfTrust(&root_of_trust); - if let Err(e) = result { - log::error!("Failed to send RootOfTrust to StrongBox: {:?}", e); - } - log::info!("RootOfTrust transfer process complete"); -} diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs index 74e3e560..e02d9bcf 100644 --- a/keystore2/src/super_key.rs +++ b/keystore2/src/super_key.rs @@ -26,9 +26,11 @@ use crate::{ error::ResponseCode, key_parameter::{KeyParameter, KeyParameterValue}, legacy_blob::LegacyBlobLoader, - legacy_importer::LegacyImporter, + legacy_migrator::LegacyMigrator, raw_device::KeyMintDevice, - utils::{watchdog as wd, AesGcm, AID_KEYSTORE}, + try_insert::TryInsert, + utils::watchdog as wd, + utils::AID_KEYSTORE, }; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, HardwareAuthToken::HardwareAuthToken, @@ -44,11 +46,11 @@ use keystore2_crypto::{ aes_gcm_decrypt, aes_gcm_encrypt, generate_aes256_key, generate_salt, Password, ZVec, AES_256_KEY_LENGTH, }; -use rustutils::system_properties::PropertyWatcher; +use keystore2_system_property::PropertyWatcher; use std::{ collections::HashMap, sync::Arc, - sync::{Mutex, RwLock, Weak}, + sync::{Mutex, Weak}, }; use std::{convert::TryFrom, ops::Deref}; @@ -73,9 +75,9 @@ pub enum SuperEncryptionAlgorithm { /// A particular user may have several superencryption keys in the database, each for a /// different purpose, distinguished by alias. Each is associated with a static /// constant of this type. -pub struct SuperKeyType<'a> { - /// Alias used to look up the key in the `persistent.keyentry` table. - pub alias: &'a str, +pub struct SuperKeyType { + /// Alias used to look the key up in the `persistent.keyentry` table. + pub alias: &'static str, /// Encryption algorithm pub algorithm: SuperEncryptionAlgorithm, } @@ -124,8 +126,10 @@ impl SuperKeyIdentifier { fn from_metadata(metadata: &BlobMetaData) -> Option<Self> { if let Some(EncryptedBy::KeyId(key_id)) = metadata.encrypted_by() { Some(SuperKeyIdentifier::DatabaseId(*key_id)) + } else if let Some(boot_level) = metadata.max_boot_level() { + Some(SuperKeyIdentifier::BootLevel(*boot_level)) } else { - metadata.max_boot_level().map(|boot_level| SuperKeyIdentifier::BootLevel(*boot_level)) + None } } @@ -153,22 +157,15 @@ pub struct SuperKey { reencrypt_with: Option<Arc<SuperKey>>, } -impl AesGcm for SuperKey { - fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> { +impl SuperKey { + /// For most purposes `unwrap_key` handles decryption, + /// but legacy handling and some tests need to assume AES and decrypt directly. + pub fn aes_gcm_decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> { if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm { aes_gcm_decrypt(data, iv, tag, &self.key) - .context("In SuperKey::decrypt: Decryption failed.") + .context("In aes_gcm_decrypt: decryption failed") } else { - Err(Error::sys()).context("In SuperKey::decrypt: Key is not an AES key.") - } - } - - fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> { - if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm { - aes_gcm_encrypt(plaintext, &self.key) - .context("In SuperKey::encrypt: Encryption failed.") - } else { - Err(Error::sys()).context("In SuperKey::encrypt: Key is not an AES key.") + Err(Error::sys()).context("In aes_gcm_decrypt: Key is not an AES key") } } } @@ -261,7 +258,7 @@ struct UserSuperKeys { struct SkmState { user_keys: HashMap<UserId, UserSuperKeys>, key_index: HashMap<i64, Weak<SuperKey>>, - boot_level_key_cache: Option<Mutex<BootLevelKeyCache>>, + boot_level_key_cache: Option<BootLevelKeyCache>, } impl SkmState { @@ -280,24 +277,24 @@ impl SkmState { #[derive(Default)] pub struct SuperKeyManager { - data: SkmState, + data: Mutex<SkmState>, } impl SuperKeyManager { - pub fn set_up_boot_level_cache(skm: &Arc<RwLock<Self>>, db: &mut KeystoreDB) -> Result<()> { - let mut skm_guard = skm.write().unwrap(); - if skm_guard.data.boot_level_key_cache.is_some() { + pub fn set_up_boot_level_cache(self: &Arc<Self>, db: &mut KeystoreDB) -> Result<()> { + let mut data = self.data.lock().unwrap(); + if data.boot_level_key_cache.is_some() { log::info!("In set_up_boot_level_cache: called for a second time"); return Ok(()); } let level_zero_key = get_level_zero_key(db) .context("In set_up_boot_level_cache: get_level_zero_key failed")?; - skm_guard.data.boot_level_key_cache = - Some(Mutex::new(BootLevelKeyCache::new(level_zero_key))); + data.boot_level_key_cache = Some(BootLevelKeyCache::new(level_zero_key)); log::info!("Starting boot level watcher."); - let clone = skm.clone(); + let clone = self.clone(); std::thread::spawn(move || { - Self::watch_boot_level(clone) + clone + .watch_boot_level() .unwrap_or_else(|e| log::error!("watch_boot_level failed:\n{:?}", e)); }); Ok(()) @@ -305,40 +302,32 @@ impl SuperKeyManager { /// Watch the `keystore.boot_level` system property, and keep boot level up to date. /// Blocks waiting for system property changes, so must be run in its own thread. - fn watch_boot_level(skm: Arc<RwLock<Self>>) -> Result<()> { + fn watch_boot_level(&self) -> Result<()> { let mut w = PropertyWatcher::new("keystore.boot_level") .context("In watch_boot_level: PropertyWatcher::new failed")?; loop { let level = w .read(|_n, v| v.parse::<usize>().map_err(std::convert::Into::into)) .context("In watch_boot_level: read of property failed")?; - - // This scope limits the skm_guard life, so we don't hold the skm_guard while - // waiting. - { - let mut skm_guard = skm.write().unwrap(); - let boot_level_key_cache = skm_guard - .data - .boot_level_key_cache + // watch_boot_level should only be called once data.boot_level_key_cache is Some, + // so it's safe to unwrap in the branches below. + if level < MAX_MAX_BOOT_LEVEL { + log::info!("Read keystore.boot_level value {}", level); + let mut data = self.data.lock().unwrap(); + data.boot_level_key_cache .as_mut() - .ok_or_else(Error::sys) - .context("In watch_boot_level: Boot level cache not initialized")? - .get_mut() - .unwrap(); - if level < MAX_MAX_BOOT_LEVEL { - log::info!("Read keystore.boot_level value {}", level); - boot_level_key_cache - .advance_boot_level(level) - .context("In watch_boot_level: advance_boot_level failed")?; - } else { - log::info!( - "keystore.boot_level {} hits maximum {}, finishing.", - level, - MAX_MAX_BOOT_LEVEL - ); - boot_level_key_cache.finish(); - break; - } + .unwrap() + .advance_boot_level(level) + .context("In watch_boot_level: advance_boot_level failed")?; + } else { + log::info!( + "keystore.boot_level {} hits maximum {}, finishing.", + level, + MAX_MAX_BOOT_LEVEL + ); + let mut data = self.data.lock().unwrap(); + data.boot_level_key_cache.as_mut().unwrap().finish(); + break; } w.wait().context("In watch_boot_level: property wait failed")?; } @@ -347,37 +336,34 @@ impl SuperKeyManager { pub fn level_accessible(&self, boot_level: i32) -> bool { self.data + .lock() + .unwrap() .boot_level_key_cache .as_ref() - .map_or(false, |c| c.lock().unwrap().level_accessible(boot_level as usize)) + .map_or(false, |c| c.level_accessible(boot_level as usize)) } - pub fn forget_all_keys_for_user(&mut self, user: UserId) { - self.data.user_keys.remove(&user); + pub fn forget_all_keys_for_user(&self, user: UserId) { + let mut data = self.data.lock().unwrap(); + data.user_keys.remove(&user); } - fn install_per_boot_key_for_user( - &mut self, - user: UserId, - super_key: Arc<SuperKey>, - ) -> Result<()> { - self.data - .add_key_to_key_index(&super_key) + fn install_per_boot_key_for_user(&self, user: UserId, super_key: Arc<SuperKey>) -> Result<()> { + let mut data = self.data.lock().unwrap(); + data.add_key_to_key_index(&super_key) .context("In install_per_boot_key_for_user: add_key_to_key_index failed")?; - self.data.user_keys.entry(user).or_default().per_boot = Some(super_key); + data.user_keys.entry(user).or_default().per_boot = Some(super_key); Ok(()) } fn lookup_key(&self, key_id: &SuperKeyIdentifier) -> Result<Option<Arc<SuperKey>>> { + let mut data = self.data.lock().unwrap(); Ok(match key_id { - SuperKeyIdentifier::DatabaseId(id) => { - self.data.key_index.get(id).and_then(|k| k.upgrade()) - } - SuperKeyIdentifier::BootLevel(level) => self - .data + SuperKeyIdentifier::DatabaseId(id) => data.key_index.get(id).and_then(|k| k.upgrade()), + SuperKeyIdentifier::BootLevel(level) => data .boot_level_key_cache - .as_ref() - .map(|b| b.lock().unwrap().aes_key(*level as usize)) + .as_mut() + .map(|b| b.aes_key(*level as usize)) .transpose() .context("In lookup_key: aes_key failed")? .flatten() @@ -392,16 +378,9 @@ impl SuperKeyManager { }) } - pub fn get_per_boot_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) - .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()) + pub fn get_per_boot_key_by_user_id(&self, user_id: UserId) -> Option<Arc<SuperKey>> { + let data = self.data.lock().unwrap(); + 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. @@ -409,7 +388,7 @@ impl SuperKeyManager { /// 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, + &self, db: &mut KeystoreDB, user: UserId, pw: &Password, @@ -419,7 +398,7 @@ impl SuperKeyManager { .get_or_create_key_with( Domain::APP, user as u64 as i64, - USER_SUPER_KEY.alias, + &USER_SUPER_KEY.alias, crate::database::KEYSTORE_UUID, || { // For backward compatibility we need to check if there is a super key present. @@ -478,7 +457,7 @@ impl SuperKeyManager { match key.algorithm { SuperEncryptionAlgorithm::Aes256Gcm => match (metadata.iv(), metadata.aead_tag()) { (Some(iv), Some(tag)) => key - .decrypt(blob, iv, tag) + .aes_gcm_decrypt(blob, iv, tag) .context("In unwrap_key_with_key: Failed to decrypt the key blob."), (iv, tag) => Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!( concat!( @@ -516,22 +495,19 @@ impl SuperKeyManager { } /// Checks if user has setup LSKF, even when super key cache is empty for the user. - /// 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( - &self, + pub fn super_key_exists_in_db_for_user( db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + legacy_migrator: &LegacyMigrator, 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_SUPER_KEY.alias, KeyType::Super) .context("In super_key_exists_in_db_for_user.")?; if key_in_db { Ok(key_in_db) } else { - legacy_importer + legacy_migrator .has_super_key(user_id) .context("In super_key_exists_in_db_for_user: Trying to query legacy db.") } @@ -541,15 +517,15 @@ impl SuperKeyManager { /// 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, + &self, db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + legacy_migrator: &LegacyMigrator, 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)) + let result = legacy_migrator + .with_try_migrate_super_key(user_id, pw, || db.load_super_key(alias, user_id)) .context("In check_and_unlock_super_key. Failed to load super key")?; match result { @@ -570,23 +546,24 @@ impl SuperKeyManager { /// and return LskfUnlocked state. /// If the password is not provided, return Uninitialized state. pub fn check_and_initialize_super_key( - &mut self, + &self, db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + legacy_migrator: &LegacyMigrator, 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("In check_and_initialize_super_key. Failed to check if super key exists.")?; + let super_key_exists_in_db = + Self::super_key_exists_in_db_for_user(db, legacy_migrator, user_id).context( + "In check_and_initialize_super_key. 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. + //generate a new super key. let super_key = generate_aes256_key() .context("In check_and_initialize_super_key: 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. + //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("In check_and_initialize_super_key.")?; @@ -614,9 +591,9 @@ impl SuperKeyManager { } } - // Helper function to populate super key cache from the super key blob loaded from the database. + //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, + &self, user_id: UserId, algorithm: SuperEncryptionAlgorithm, entry: KeyEntry, @@ -630,7 +607,7 @@ impl SuperKeyManager { Ok(super_key) } - /// Extracts super key from the entry loaded from the database. + /// Extracts super key from the entry loaded from the database pub fn extract_super_key_from_key_entry( algorithm: SuperEncryptionAlgorithm, entry: KeyEntry, @@ -645,7 +622,7 @@ impl SuperKeyManager { metadata.aead_tag(), ) { (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => { - // Note that password encryption is AES no matter the value of algorithm. + // Note that password encryption is AES no matter the value of algorithm let key = pw.derive_key(Some(salt), AES_256_KEY_LENGTH).context( "In extract_super_key_from_key_entry: Failed to generate key from password.", )?; @@ -705,12 +682,11 @@ impl SuperKeyManager { fn super_encrypt_on_key_init( &self, db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + legacy_migrator: &LegacyMigrator, user_id: UserId, key_blob: &[u8], ) -> Result<(Vec<u8>, BlobMetaData)> { - match self - .get_user_state(db, legacy_importer, user_id) + match UserState::get(db, legacy_migrator, self, user_id) .context("In super_encrypt. Failed to get user state.")? { UserState::LskfUnlocked(super_key) => { @@ -725,9 +701,9 @@ impl SuperKeyManager { } } - // 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. + //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. fn encrypt_with_aes_super_key( key_blob: &[u8], super_key: &SuperKey, @@ -751,7 +727,7 @@ impl SuperKeyManager { pub fn handle_super_encryption_on_key_init( &self, db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + legacy_migrator: &LegacyMigrator, domain: &Domain, key_parameters: &[KeyParameter], flags: Option<i32>, @@ -761,16 +737,16 @@ impl SuperKeyManager { 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) + .super_encrypt_on_key_init(db, legacy_migrator, user_id, &key_blob) .context(concat!( "In handle_super_encryption_on_key_init. ", "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(concat!( + let mut data = self.data.lock().unwrap(); + let entry = data.user_keys.entry(user_id).or_default(); + if let Some(super_key) = entry.screen_lock_bound.as_ref() { + Self::encrypt_with_aes_super_key(key_blob, &super_key).context(concat!( "In handle_super_encryption_on_key_init. ", "Failed to encrypt with ScreenLockBound key." )) @@ -839,7 +815,6 @@ impl SuperKeyManager { /// 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. fn get_or_create_super_key( - &mut self, db: &mut KeystoreDB, user_id: UserId, key_type: &SuperKeyType, @@ -874,8 +849,8 @@ impl SuperKeyManager { ) } }; - // Derive an AES256 key from the password and re-encrypt the super key - // before we insert it in the database. + //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("In get_or_create_super_key.")?; @@ -903,64 +878,52 @@ impl SuperKeyManager { /// Decrypt the screen-lock bound keys for this user using the password and store in memory. pub fn unlock_screen_lock_bound_key( - &mut self, + &self, db: &mut KeystoreDB, user_id: UserId, password: &Password, ) -> Result<()> { - let (screen_lock_bound, screen_lock_bound_private) = self - .data - .user_keys - .get(&user_id) - .map(|e| (e.screen_lock_bound.clone(), e.screen_lock_bound_private.clone())) - .unwrap_or((None, None)); - - if screen_lock_bound.is_some() && screen_lock_bound_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 - } else { - self.get_or_create_super_key(db, user_id, &USER_SCREEN_LOCK_BOUND_KEY, password, None) - .context("In unlock_screen_lock_bound_key: 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 - } else { - self.get_or_create_super_key( - db, - user_id, - &USER_SCREEN_LOCK_BOUND_P521_KEY, - password, - Some(aes.clone()), - ) - .context("In unlock_screen_lock_bound_key: Trying to get or create asymmetric key.")? - }; - - 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); + let mut data = self.data.lock().unwrap(); + let entry = data.user_keys.entry(user_id).or_default(); + let aes = entry + .screen_lock_bound + .get_or_try_to_insert_with(|| { + Self::get_or_create_super_key( + db, + user_id, + &USER_SCREEN_LOCK_BOUND_KEY, + password, + None, + ) + })? + .clone(); + let ecdh = entry + .screen_lock_bound_private + .get_or_try_to_insert_with(|| { + Self::get_or_create_super_key( + db, + user_id, + &USER_SCREEN_LOCK_BOUND_P521_KEY, + password, + Some(aes.clone()), + ) + })? + .clone(); + data.add_key_to_key_index(&aes)?; + data.add_key_to_key_index(&ecdh)?; Ok(()) } /// Wipe the screen-lock bound keys for this user from memory. pub fn lock_screen_lock_bound_key( - &mut self, + &self, db: &mut KeystoreDB, user_id: UserId, unlocking_sids: &[i64], ) { log::info!("Locking screen bound for user {} sids {:?}", user_id, unlocking_sids); - let mut entry = self.data.user_keys.entry(user_id).or_default(); + let mut data = self.data.lock().unwrap(); + let mut entry = 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(), @@ -1032,11 +995,12 @@ impl SuperKeyManager { /// User has unlocked, not using a password. See if any of our stored auth tokens can be used /// to unlock the keys protecting UNLOCKED_DEVICE_REQUIRED keys. pub fn try_unlock_user_with_biometric( - &mut self, + &self, db: &mut KeystoreDB, user_id: UserId, ) -> Result<()> { - let mut entry = self.data.user_keys.entry(user_id).or_default(); + let mut data = self.data.lock().unwrap(); + let mut entry = data.user_keys.entry(user_id).or_default(); if let Some(biometric) = entry.biometric_unlock.as_ref() { let (key_id_guard, key_entry) = db .load_key_entry( @@ -1076,8 +1040,8 @@ impl SuperKeyManager { 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)?; + data.add_key_to_key_index(&slb)?; + data.add_key_to_key_index(&slbp)?; log::info!(concat!( "In try_unlock_user_with_biometric: ", "Successfully unlocked with biometric" @@ -1093,24 +1057,37 @@ impl SuperKeyManager { } Ok(()) } +} - /// Returns the keystore locked state of the given user. It requires the thread local - /// keystore database and a reference to the legacy migrator because it may need to - /// import the super key from the legacy blob database to the keystore database. - pub fn get_user_state( - &self, +/// 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. + Uninitialized, +} + +impl UserState { + pub fn get( db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + legacy_migrator: &LegacyMigrator, + skm: &SuperKeyManager, user_id: UserId, ) -> Result<UserState> { - match self.get_per_boot_key_by_user_id_internal(user_id) { + match skm.get_per_boot_key_by_user_id(user_id) { Some(super_key) => Ok(UserState::LskfUnlocked(super_key)), None => { - // Check if a super key exists in the database or legacy database. - // If so, return locked user state. - if self - .super_key_exists_in_db_for_user(db, legacy_importer, user_id) - .context("In get_user_state.")? + //Check if a super key exists in the database or legacy database. + //If so, return locked user state. + if SuperKeyManager::super_key_exists_in_db_for_user(db, legacy_migrator, user_id) + .context("In get.")? { Ok(UserState::LskfLocked) } else { @@ -1120,70 +1097,60 @@ 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( - &mut self, + /// Queries user state when serving password change requests. + pub fn get_with_password_changed( db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + legacy_migrator: &LegacyMigrator, + skm: &SuperKeyManager, 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( - "In reset_or_init_user_and_get_user_state: Trying to delete keys from the db.", - )?; - // Lskf is now removed in Keystore. - Ok(UserState::Uninitialized) - } + match skm.get_per_boot_key_by_user_id(user_id) { 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)) + 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, skm, legacy_migrator, user_id, true).context( + "In get_with_password_changed: Trying to delete keys from the db.", + )?; + //Lskf is now removed in Keystore + Ok(UserState::Uninitialized) + } else { + //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)) + } } 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) + //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. + skm.check_and_initialize_super_key(db, legacy_migrator, user_id, password) } } } - /// 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( - &mut self, + /// Queries user state when serving password unlock requests. + pub fn get_with_password_unlock( db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + legacy_migrator: &LegacyMigrator, + skm: &SuperKeyManager, user_id: UserId, password: &Password, ) -> Result<UserState> { - match self.get_per_boot_key_by_user_id_internal(user_id) { + match skm.get_per_boot_key_by_user_id(user_id) { Some(super_key) => { - log::info!("In unlock_and_get_user_state. Trying to unlock when already unlocked."); + log::info!("In get_with_password_unlock. Trying to unlock when already unlocked."); Ok(UserState::LskfUnlocked(super_key)) } 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("In unlock_and_get_user_state. Failed to unlock super key.") + //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 state + skm.check_and_unlock_super_key(db, legacy_migrator, user_id, password) + .context("In get_with_password_unlock. Failed to unlock super key.") } } } @@ -1192,40 +1159,25 @@ impl SuperKeyManager { /// If 'keep_non_super_encrypted_keys' is set to true, delete only the super key and super /// encrypted keys. pub fn reset_user( - &mut self, db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, + skm: &SuperKeyManager, + legacy_migrator: &LegacyMigrator, user_id: UserId, keep_non_super_encrypted_keys: bool, ) -> Result<()> { - // Mark keys created on behalf of the user as unreferenced. - legacy_importer + // mark keys created on behalf of the user as unreferenced. + legacy_migrator .bulk_delete_user(user_id, keep_non_super_encrypted_keys) .context("In reset_user: Trying to delete legacy keys.")?; db.unbind_keys_for_user(user_id, keep_non_super_encrypted_keys) .context("In reset user. Error in unbinding keys.")?; - // Delete super key in cache, if exists. - self.forget_all_keys_for_user(user_id); + //delete super key in cache, if exists + skm.forget_all_keys_for_user(user_id); Ok(()) } } -/// 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. - Uninitialized, -} - /// This enum represents three states a KeyMint Blob can be in, w.r.t super encryption. /// `Sensitive` holds the non encrypted key and a reference to its super key. /// `NonSensitive` holds a non encrypted key that is never supposed to be encrypted. @@ -1263,8 +1215,8 @@ impl<'a> Deref for KeyBlob<'a> { fn deref(&self) -> &Self::Target { match self { - Self::Sensitive { key, .. } => key, - Self::NonSensitive(key) => key, + Self::Sensitive { key, .. } => &key, + Self::NonSensitive(key) => &key, Self::Ref(key) => key, } } diff --git a/keystore2/src/try_insert.rs b/keystore2/src/try_insert.rs new file mode 100644 index 00000000..6dd39620 --- /dev/null +++ b/keystore2/src/try_insert.rs @@ -0,0 +1,100 @@ +// 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. + +//! The TryInsert trait adds to Option<T> the method +//! get_or_try_to_insert_with, which is analogous to +//! get_or_insert_with, but allows the called function to fail and propagates the failure. + +/// The TryInsert trait adds to Option<T> the method +/// get_or_try_to_insert_with, which is analogous to +/// get_or_insert_with, but allows the called function to fail and propagates the failure. +pub trait TryInsert { + /// Type of the Ok branch of the Result + type Item; + /// Inserts a value computed from `f` into the option if it is [`None`], + /// then returns a mutable reference to the contained value. If `f` + /// returns Err, the Option is unchanged. + /// + /// # Examples + /// + /// ``` + /// let mut x = None; + /// assert_eq!(x.get_or_try_to_insert_with(Err("oops".to_string())), Err("oops".to_string())) + /// { + /// let y: &mut u32 = x.get_or_try_to_insert_with(|| Ok(5))?; + /// assert_eq!(y, &5); + /// + /// *y = 7; + /// } + /// + /// assert_eq!(x, Some(7)); + /// ``` + fn get_or_try_to_insert_with<E, F: FnOnce() -> Result<Self::Item, E>>( + &mut self, + f: F, + ) -> Result<&mut Self::Item, E>; +} + +impl<T> TryInsert for Option<T> { + type Item = T; + fn get_or_try_to_insert_with<E, F: FnOnce() -> Result<Self::Item, E>>( + &mut self, + f: F, + ) -> Result<&mut Self::Item, E> { + if self.is_none() { + *self = Some(f()?); + } + + match self { + Some(v) => Ok(v), + // SAFETY: a `None` variant for `self` would have been replaced by a `Some` + // variant in the code above. + None => unsafe { std::hint::unreachable_unchecked() }, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn fails() -> Result<i32, String> { + Err("fail".to_string()) + } + + fn succeeds() -> Result<i32, String> { + Ok(99) + } + + #[test] + fn test() { + let mut x = None; + assert_eq!(x.get_or_try_to_insert_with(fails), Err("fail".to_string())); + assert_eq!(x, None); + assert_eq!(*x.get_or_try_to_insert_with(succeeds).unwrap(), 99); + assert_eq!(x, Some(99)); + x = Some(42); + assert_eq!(*x.get_or_try_to_insert_with(fails).unwrap(), 42); + assert_eq!(x, Some(42)); + assert_eq!(*x.get_or_try_to_insert_with(succeeds).unwrap(), 42); + assert_eq!(x, Some(42)); + *x.get_or_try_to_insert_with(fails).unwrap() = 2; + assert_eq!(x, Some(2)); + *x.get_or_try_to_insert_with(succeeds).unwrap() = 3; + assert_eq!(x, Some(3)); + x = None; + *x.get_or_try_to_insert_with(succeeds).unwrap() = 5; + assert_eq!(x, Some(5)); + } +} diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs index 9db2eb9d..a110c64e 100644 --- a/keystore2/src/utils.rs +++ b/keystore2/src/utils.rs @@ -15,17 +15,11 @@ //! This module implements utility functions used by the Keystore 2.0 service //! implementation. -use crate::error::{map_binder_status, map_km_error, Error, ErrorCode}; -use crate::key_parameter::KeyParameter; +use crate::error::{map_binder_status, Error, ErrorCode}; use crate::permission; use crate::permission::{KeyPerm, KeyPermSet, KeystorePerm}; -use crate::{ - database::{KeyType, KeystoreDB}, - globals::LEGACY_IMPORTER, -}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics, - KeyParameter::KeyParameter as KmKeyParameter, Tag::Tag, + KeyCharacteristics::KeyCharacteristics, Tag::Tag, }; use android_os_permissions_aidl::aidl::android::os::IPermissionController; use android_security_apc::aidl::android::security::apc::{ @@ -33,17 +27,16 @@ use android_security_apc::aidl::android::security::apc::{ ResponseCode::ResponseCode as ApcResponseCode, }; use android_system_keystore2::aidl::android::system::keystore2::{ - Authorization::Authorization, Domain::Domain, KeyDescriptor::KeyDescriptor, + Authorization::Authorization, KeyDescriptor::KeyDescriptor, }; -use anyhow::{Context, Result}; -use binder::{Strong, ThreadState}; +use anyhow::{anyhow, Context}; +use binder::{FromIBinder, SpIBinder, ThreadState}; use keystore2_apc_compat::{ ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED, APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING, APC_COMPAT_ERROR_SYSTEM_ERROR, }; -use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt, ZVec}; -use std::iter::IntoIterator; +use std::sync::Mutex; /// This function uses its namesake in the permission module and in /// combination with with_calling_sid from the binder crate to check @@ -51,7 +44,7 @@ use std::iter::IntoIterator; pub fn check_keystore_permission(perm: KeystorePerm) -> anyhow::Result<()> { ThreadState::with_calling_sid(|calling_sid| { permission::check_keystore_permission( - calling_sid.ok_or_else(Error::sys).context( + &calling_sid.ok_or_else(Error::sys).context( "In check_keystore_permission: Cannot check permission without calling_sid.", )?, perm, @@ -65,7 +58,7 @@ pub fn check_keystore_permission(perm: KeystorePerm) -> anyhow::Result<()> { pub fn check_grant_permission(access_vec: KeyPermSet, key: &KeyDescriptor) -> anyhow::Result<()> { ThreadState::with_calling_sid(|calling_sid| { permission::check_grant_permission( - calling_sid.ok_or_else(Error::sys).context( + &calling_sid.ok_or_else(Error::sys).context( "In check_grant_permission: Cannot check permission without calling_sid.", )?, access_vec, @@ -85,7 +78,7 @@ pub fn check_key_permission( ThreadState::with_calling_sid(|calling_sid| { permission::check_key_permission( ThreadState::get_calling_uid(), - calling_sid + &calling_sid .ok_or_else(Error::sys) .context("In check_key_permission: Cannot check permission without calling_sid.")?, perm, @@ -107,21 +100,10 @@ pub fn is_device_id_attestation_tag(tag: Tag) -> bool { } /// This function checks whether the calling app has the Android permissions needed to attest device -/// identifiers. It throws an error if the permissions cannot be verified or if the caller doesn't -/// have the right permissions. Otherwise it returns silently. +/// identifiers. It throws an error if the permissions cannot be verified, or if the caller doesn't +/// have the right permissions, and returns silently otherwise. pub fn check_device_attestation_permissions() -> anyhow::Result<()> { - check_android_permission("android.permission.READ_PRIVILEGED_PHONE_STATE") -} - -/// This function checks whether the calling app has the Android permissions needed to attest the -/// device-unique identifier. It throws an error if the permissions cannot be verified or if the -/// caller doesn't have the right permissions. Otherwise it returns silently. -pub fn check_unique_id_attestation_permissions() -> anyhow::Result<()> { - check_android_permission("android.permission.REQUEST_UNIQUE_ID_ATTESTATION") -} - -fn check_android_permission(permission: &str) -> anyhow::Result<()> { - let permission_controller: Strong<dyn IPermissionController::IPermissionController> = + let permission_controller: binder::Strong<dyn IPermissionController::IPermissionController> = binder::get_interface("permission")?; let binder_result = { @@ -130,7 +112,7 @@ fn check_android_permission(permission: &str) -> anyhow::Result<()> { 500, ); permission_controller.checkPermission( - permission, + "android.permission.READ_PRIVILEGED_PHONE_STATE", ThreadState::get_calling_pid(), ThreadState::get_calling_uid() as i32, ) @@ -146,64 +128,55 @@ fn check_android_permission(permission: &str) -> anyhow::Result<()> { } } +/// Thread safe wrapper around SpIBinder. It is safe to have SpIBinder smart pointers to the +/// same object in multiple threads, but cloning a SpIBinder is not thread safe. +/// Keystore frequently hands out binder tokens to the security level interface. If this +/// is to happen from a multi threaded thread pool, the SpIBinder needs to be protected by a +/// Mutex. +#[derive(Debug)] +pub struct Asp(Mutex<SpIBinder>); + +impl Asp { + /// Creates a new instance owning a SpIBinder wrapped in a Mutex. + pub fn new(i: SpIBinder) -> Self { + Self(Mutex::new(i)) + } + + /// Clones the owned SpIBinder and attempts to convert it into the requested interface. + pub fn get_interface<T: FromIBinder + ?Sized>(&self) -> anyhow::Result<binder::Strong<T>> { + // We can use unwrap here because we never panic when locked, so the mutex + // can never be poisoned. + let lock = self.0.lock().unwrap(); + (*lock) + .clone() + .into_interface() + .map_err(|e| anyhow!(format!("get_interface failed with error code {:?}", e))) + } +} + +impl Clone for Asp { + fn clone(&self) -> Self { + let lock = self.0.lock().unwrap(); + Self(Mutex::new((*lock).clone())) + } +} + /// Converts a set of key characteristics as returned from KeyMint into the internal /// representation of the keystore service. pub fn key_characteristics_to_internal( key_characteristics: Vec<KeyCharacteristics>, -) -> Vec<KeyParameter> { +) -> Vec<crate::key_parameter::KeyParameter> { key_characteristics .into_iter() .flat_map(|aidl_key_char| { let sec_level = aidl_key_char.securityLevel; - aidl_key_char - .authorizations - .into_iter() - .map(move |aidl_kp| KeyParameter::new(aidl_kp.into(), sec_level)) + aidl_key_char.authorizations.into_iter().map(move |aidl_kp| { + crate::key_parameter::KeyParameter::new(aidl_kp.into(), sec_level) + }) }) .collect() } -/// 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 -/// with the upgraded blob as argument. Then `km_op` is called a second time with the -/// upgraded blob as argument. On success a tuple of the `km_op`s result and the -/// optional upgraded blob is returned. -pub fn upgrade_keyblob_if_required_with<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<()>, -{ - 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, - ); - map_km_error(km_dev.upgradeKey(key_blob, upgrade_params)) - } - .context("In utils::upgrade_keyblob_if_required_with: Upgrade failed.")?; - - new_blob_handler(&upgraded_blob) - .context("In utils::upgrade_keyblob_if_required_with: calling new_blob_handler.")?; - - km_op(&upgraded_blob) - .map(|v| (v, Some(upgraded_blob))) - .context("In utils::upgrade_keyblob_if_required_with: Calling km_op after upgrade.") - } - r => r - .map(|v| (v, None)) - .context("In utils::upgrade_keyblob_if_required_with: Calling km_op."), - } -} - /// Converts a set of key characteristics from the internal representation into a set of /// Authorizations as they are used to convey key characteristics to the clients of keystore. pub fn key_parameters_to_authorizations( @@ -249,37 +222,16 @@ pub fn ui_opts_2_compat(opt: i32) -> ApcCompatUiOptions { } /// AID offset for uid space partitioning. -pub const AID_USER_OFFSET: u32 = rustutils::users::AID_USER_OFFSET; +pub const AID_USER_OFFSET: u32 = cutils_bindgen::AID_USER_OFFSET; /// AID of the keystore process itself, used for keys that /// keystore generates for its own use. -pub const AID_KEYSTORE: u32 = rustutils::users::AID_KEYSTORE; +pub const AID_KEYSTORE: u32 = cutils_bindgen::AID_KEYSTORE; /// Extracts the android user from the given uid. 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("In list_key_entries: Trying to list legacy keys.")?, - ); - result.append( - &mut db - .list(domain, namespace, KeyType::Client) - .context("In list_key_entries: Trying to list keystore database.")?, - ); - result.sort_unstable(); - result.dedup(); - Ok(result) + // Safety: No memory access + unsafe { cutils_bindgen::multiuser_get_user_id(uid) } } /// This module provides helpers for simplified use of the watchdog module. @@ -312,36 +264,6 @@ pub mod watchdog { } } -/// Trait implemented by objects that can be used to decrypt cipher text using AES-GCM. -pub trait AesGcm { - /// Deciphers `data` using the initialization vector `iv` and AEAD tag `tag` - /// and AES-GCM. The implementation provides the key material and selects - /// the implementation variant, e.g., AES128 or AES265. - fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec>; - - /// Encrypts `data` and returns the ciphertext, the initialization vector `iv` - /// and AEAD tag `tag`. The implementation provides the key material and selects - /// the implementation variant, e.g., AES128 or AES265. - fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)>; -} - -/// Marks an object as AES-GCM key. -pub trait AesGcmKey { - /// Provides access to the raw key material. - fn key(&self) -> &[u8]; -} - -impl<T: AesGcmKey> AesGcm for T { - fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> { - aes_gcm_decrypt(data, iv, tag, self.key()) - .context("In AesGcm<T>::decrypt: Decryption failed") - } - - fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> { - aes_gcm_encrypt(plaintext, self.key()).context("In AesGcm<T>::encrypt: Encryption failed.") - } -} - /// This module provides empty/noop implementations of the watch dog utility functions. #[cfg(not(feature = "watchdog"))] pub mod watchdog { diff --git a/keystore2/src/vintf/Android.bp b/keystore2/src/vintf/Android.bp index 34719aaa..3ab0ec51 100644 --- a/keystore2/src/vintf/Android.bp +++ b/keystore2/src/vintf/Android.bp @@ -26,32 +26,39 @@ rust_library { crate_name: "keystore2_vintf", srcs: ["lib.rs"], rustlibs: [ - "libcxx", + "libkeystore2_vintf_bindgen", ], shared_libs: [ - "libvintf", - ], - static_libs: [ "libkeystore2_vintf_cpp", + "libvintf", ], } -cc_library_static { +cc_library { name: "libkeystore2_vintf_cpp", - srcs: ["vintf.cpp"], - generated_headers: ["cxx-bridge-header"], - generated_sources: ["vintf_bridge_code"], + srcs: [ + "vintf.cpp", + ], shared_libs: [ "libvintf", ], } -genrule { - name: "vintf_bridge_code", - tools: ["cxxbridge"], - cmd: "$(location cxxbridge) $(in) >> $(out)", - srcs: ["lib.rs"], - out: ["vintf_cxx_generated.cc"], +rust_bindgen { + name: "libkeystore2_vintf_bindgen", + wrapper_src: "vintf.hpp", + crate_name: "keystore2_vintf_bindgen", + source_stem: "bindings", + host_supported: true, + shared_libs: ["libvintf"], + bindgen_flags: [ + "--size_t-is-usize", + "--allowlist-function", "getHalNames", + "--allowlist-function", "getHalNamesAndVersions", + "--allowlist-function", "getHidlInstances", + "--allowlist-function", "getAidlInstances", + "--allowlist-function", "freeNames", + ], } rust_test { @@ -61,7 +68,7 @@ rust_test { test_suites: ["general-tests"], auto_gen_config: true, rustlibs: [ - "libcxx", + "libkeystore2_vintf_bindgen", ], static_libs: [ "libkeystore2_vintf_cpp", diff --git a/keystore2/src/vintf/lib.rs b/keystore2/src/vintf/lib.rs index 89e18eba..8730a3e3 100644 --- a/keystore2/src/vintf/lib.rs +++ b/keystore2/src/vintf/lib.rs @@ -14,35 +14,96 @@ //! Bindings for getting the list of HALs. -#[cxx::bridge] -mod ffi { - unsafe extern "C++" { - include!("vintf.hpp"); - - /// Gets all HAL names. - /// Note that this is not a zero-cost shim: it will make copies of the strings. - fn get_hal_names() -> Vec<String>; - - /// Gets all HAL names and versions. - /// Note that this is not a zero-cost shim: it will make copies of the strings. - fn get_hal_names_and_versions() -> 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_hidl_instances( - package: &str, - major_version: usize, - 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>; +use keystore2_vintf_bindgen::{ + freeNames, getAidlInstances, getHalNames, getHalNamesAndVersions, getHidlInstances, +}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::str::Utf8Error; + +/// A struct that contains a list of HALs (optionally with version numbers). +/// To use it, call as_vec to get a Vec view of the data it contains. +pub struct HalNames { + data: *mut *mut c_char, + len: usize, +} + +impl Drop for HalNames { + fn drop(&mut self) { + // Safety: The memory is allocated by our C shim so it must free it as well. + unsafe { freeNames(self.data, self.len) } } } -pub use ffi::*; +impl<'a> HalNames { + /// Get a Vec view of the list of HALs. + pub fn as_vec(&'a self) -> Result<Vec<&'a str>, Utf8Error> { + // Safety: self.data contains self.len C strings. + // The lifetimes ensure that the HalNames (and hence the strings) live + // at least as long as the returned vector. + unsafe { (0..self.len).map(|i| CStr::from_ptr(*self.data.add(i)).to_str()) }.collect() + } +} + +/// Gets all HAL names. +/// Note that this is not a zero-cost shim: it will make copies of the strings. +pub fn get_hal_names() -> HalNames { + let mut len: usize = 0; + // Safety: We'll wrap this in HalNames to free the memory it allocates. + // It stores the size of the array it returns in len. + let raw_strs = unsafe { getHalNames(&mut len) }; + HalNames { data: raw_strs, len } +} + +/// Gets all HAL names and versions. +/// Note that this is not a zero-cost shim: it will make copies of the strings. +pub fn get_hal_names_and_versions() -> HalNames { + let mut len: usize = 0; + // Safety: We'll wrap this in HalNames to free the memory it allocates. + // It stores the size of the array it returns in len. + let raw_strs = unsafe { getHalNamesAndVersions(&mut len) }; + HalNames { data: raw_strs, len } +} + +/// 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. +pub fn get_hidl_instances( + package: &str, + major_version: usize, + minor_version: usize, + interface_name: &str, +) -> HalNames { + let mut len: usize = 0; + let packages = CString::new(package).expect("Failed to make CString from package."); + let interface_name = + CString::new(interface_name).expect("Failed to make CString from interface_name."); + // Safety: We'll wrap this in HalNames to free the memory it allocates. + // It stores the size of the array it returns in len. + let raw_strs = unsafe { + getHidlInstances( + &mut len, + packages.as_ptr(), + major_version, + minor_version, + interface_name.as_ptr(), + ) + }; + HalNames { data: raw_strs, len } +} + +/// 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. +pub fn get_aidl_instances(package: &str, version: usize, interface_name: &str) -> HalNames { + let mut len: usize = 0; + let packages = CString::new(package).expect("Failed to make CString from package."); + let interface_name = + CString::new(interface_name).expect("Failed to make CString from interface_name."); + // Safety: We'll wrap this in HalNames to free the memory it allocates. + // It stores the size of the array it returns in len. + let raw_strs = + unsafe { getAidlInstances(&mut len, packages.as_ptr(), version, interface_name.as_ptr()) }; + HalNames { data: raw_strs, len } +} #[cfg(test)] mod tests { @@ -50,13 +111,17 @@ mod tests { use super::*; #[test] - fn test() { - let names = get_hal_names(); + fn test() -> Result<(), Utf8Error> { + let result = get_hal_names(); + let names = result.as_vec()?; assert_ne!(names.len(), 0); - let names_and_versions = get_hal_names_and_versions(); + let result = get_hal_names_and_versions(); + let names_and_versions = result.as_vec()?; assert_ne!(names_and_versions.len(), 0); assert!(names_and_versions.len() >= names.len()); + + Ok(()) } } diff --git a/keystore2/src/vintf/vintf.cpp b/keystore2/src/vintf/vintf.cpp index 00625bff..e407efac 100644 --- a/keystore2/src/vintf/vintf.cpp +++ b/keystore2/src/vintf/vintf.cpp @@ -14,43 +14,55 @@ * limitations under the License. */ -#include <algorithm> +#include "vintf.hpp" + #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; +// Converts a set<string> into a C-style array of C strings. +static char** convert(const std::set<std::string>& names) { + char** ret = new char*[names.size()]; + char** ptr = ret; + for (const auto& name : names) { + *(ptr++) = strdup(name.c_str()); + } + return ret; } -rust::Vec<rust::String> get_hal_names() { - const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); +char** getHalNames(size_t* len) { + auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); const auto names = manifest->getHalNames(); + *len = names.size(); return convert(names); } -rust::Vec<rust::String> get_hal_names_and_versions() { - const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); +char** getHalNamesAndVersions(size_t* len) { + auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); const auto names = manifest->getHalNamesAndVersions(); + *len = names.size(); return convert(names); } -rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_version, - size_t minor_version, rust::Str interfaceName) { +char** getHidlInstances(size_t* len, const char* package, size_t major_version, + size_t minor_version, const char* 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)); + auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); + const auto names = manifest->getHidlInstances(package, version, interfaceName); + *len = names.size(); 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)); +char** getAidlInstances(size_t* len, const char* package, size_t version, + const char* interfaceName) { + auto manifest = android::vintf::VintfObject::GetDeviceHalManifest(); + const auto names = manifest->getAidlInstances(package, version, interfaceName); + *len = names.size(); return convert(names); } + +void freeNames(char** names, size_t len) { + for (int i = 0; i < len; i++) { + free(names[i]); + } + delete[] names; +} diff --git a/keystore2/src/vintf/vintf.hpp b/keystore2/src/vintf/vintf.hpp index dbc88f0f..091e8e8d 100644 --- a/keystore2/src/vintf/vintf.hpp +++ b/keystore2/src/vintf/vintf.hpp @@ -14,13 +14,20 @@ * limitations under the License. */ -#pragma once +#ifndef __VINTF_H__ +#define __VINTF_H__ -#include "rust/cxx.h" +#include <stddef.h> -rust::Vec<rust::String> get_hal_names(); -rust::Vec<rust::String> get_hal_names_and_versions(); -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); +extern "C" { + +char** getHalNames(size_t* len); +char** getHalNamesAndVersions(size_t* len); +char** getHidlInstances(size_t* len, const char* package, size_t major_version, + size_t minor_version, const char* interfaceName); +char** getAidlInstances(size_t* len, const char* package, size_t version, + const char* interfaceName); +void freeNames(char** names, size_t len); +} + +#endif // __VINTF_H__ diff --git a/keystore2/src/watchdog.rs b/keystore2/src/watchdog.rs index a26b632a..9cca171e 100644 --- a/keystore2/src/watchdog.rs +++ b/keystore2/src/watchdog.rs @@ -111,44 +111,11 @@ impl WatchdogState { } self.last_report = Instant::now(); self.has_overdue = has_overdue; - log::warn!("### Keystore Watchdog report - BEGIN ###"); - + log::warn!("Keystore Watchdog report:"); + log::warn!("Overdue records:"); let now = Instant::now(); - let mut overdue_records: Vec<(&Index, &Record)> = self - .records - .iter() - .filter(|(_, r)| r.deadline.saturating_duration_since(now) == Duration::new(0, 0)) - .collect(); - - log::warn!("When extracting from a bug report, please include this header"); - log::warn!("and all {} records below.", overdue_records.len()); - - // Watch points can be nested, i.e., a single thread may have multiple armed - // watch points. And the most recent on each thread (thread recent) is closest to the point - // where something is blocked. Furthermore, keystore2 has various critical section - // and common backend resources KeyMint that can only be entered serialized. So if one - // thread hangs, the others will soon follow suite. Thus the oldest "thread recent" watch - // point is most likely pointing toward the culprit. - // Thus, sort by start time first. - overdue_records.sort_unstable_by(|(_, r1), (_, r2)| r1.started.cmp(&r2.started)); - // Then we groups all of the watch points per thread preserving the order within - // groups. - let groups = overdue_records.iter().fold( - HashMap::<thread::ThreadId, Vec<(&Index, &Record)>>::new(), - |mut acc, (i, r)| { - acc.entry(i.tid).or_default().push((i, r)); - acc - }, - ); - // Put the groups back into a vector. - let mut groups: Vec<Vec<(&Index, &Record)>> = groups.into_iter().map(|(_, v)| v).collect(); - // Sort the groups by start time of the most recent (.last()) of each group. - // It is panic safe to use unwrap() here because we never add empty vectors to - // the map. - groups.sort_by(|v1, v2| v1.last().unwrap().1.started.cmp(&v2.last().unwrap().1.started)); - - for g in groups.iter() { - for (i, r) in g.iter() { + for (i, r) in self.records.iter() { + if r.deadline.saturating_duration_since(now) == Duration::new(0, 0) { match &r.callback { Some(cb) => { log::warn!( @@ -172,7 +139,6 @@ impl WatchdogState { } } } - log::warn!("### Keystore Watchdog report - END ###"); true } diff --git a/keystore2/tests/legacy_blobs/Android.bp b/keystore2/system_property/Android.bp index 9322a411..773804d9 100644 --- a/keystore2/tests/legacy_blobs/Android.bp +++ b/keystore2/system_property/Android.bp @@ -1,4 +1,4 @@ -// Copyright 2022, The Android Open Source Project +// 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. @@ -21,31 +21,33 @@ package { default_applicable_licenses: ["system_security_license"], } -rust_test { - name: "keystore2_legacy_blobs_test", - srcs: ["keystore2_legacy_blob_tests.rs"], - test_suites: [ - "general-tests", +rust_bindgen { + name: "libkeystore2_system_property_bindgen", + wrapper_src: "system_property_bindgen.hpp", + crate_name: "keystore2_system_property_bindgen", + source_stem: "bindings", + + bindgen_flags: [ + "--size_t-is-usize", + "--allowlist-function=__system_property_find", + "--allowlist-function=__system_property_read_callback", + "--allowlist-function=__system_property_set", + "--allowlist-function=__system_property_wait", ], - // auto_gen_config: true, - test_config: "AndroidTest.xml", +} +rust_library { + name: "libkeystore2_system_property-rust", + crate_name: "keystore2_system_property", + srcs: [ + "lib.rs", + ], rustlibs: [ - "libkeystore2_with_test_utils", - "libkeystore2_crypto_rust", - "android.system.keystore2-V2-rust", - "android.hardware.security.keymint-V2-rust", - "android.security.maintenance-rust", - "android.security.authorization-rust", - "librustutils", - "libkeystore2_test_utils", - "libnix", "libanyhow", - "libbinder_rs", - "liblazy_static", - "liblibc", - "libserde", + "libkeystore2_system_property_bindgen", "libthiserror", ], - require_root: true, + shared_libs: [ + "libbase", + ], } diff --git a/keystore2/system_property/lib.rs b/keystore2/system_property/lib.rs new file mode 100644 index 00000000..b993c879 --- /dev/null +++ b/keystore2/system_property/lib.rs @@ -0,0 +1,217 @@ +// 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 crate provides the PropertyWatcher type, which watches for changes +//! in Android system properties. + +use anyhow::{anyhow, Context, Result as AnyhowResult}; +use keystore2_system_property_bindgen::prop_info as PropInfo; +use std::os::raw::c_char; +use std::ptr::null; +use std::{ + ffi::{c_void, CStr, CString}, + str::Utf8Error, +}; +use thiserror::Error; + +/// Errors this crate can generate +#[derive(Error, Debug)] +pub enum PropertyWatcherError { + /// We can't watch for a property whose name contains a NUL character. + #[error("Cannot convert name to C string")] + BadNameError(#[from] std::ffi::NulError), + /// We can only watch for properties that exist when the watcher is created. + #[error("System property is absent")] + SystemPropertyAbsent, + /// __system_property_wait timed out despite being given no timeout. + #[error("Wait failed")] + WaitFailed, + /// read callback was not called + #[error("__system_property_read_callback did not call callback")] + ReadCallbackNotCalled, + /// read callback gave us a NULL pointer + #[error("__system_property_read_callback gave us a NULL pointer instead of a string")] + MissingCString, + /// read callback gave us a bad C string + #[error("__system_property_read_callback gave us a non-UTF8 C string")] + BadCString(#[from] Utf8Error), + /// read callback returned an error + #[error("Callback failed")] + CallbackError(#[from] anyhow::Error), + /// Failure in setting the system property + #[error("__system_property_set failed.")] + SetPropertyFailed, +} + +/// Result type specific for this crate. +pub type Result<T> = std::result::Result<T, PropertyWatcherError>; + +/// PropertyWatcher takes the name of an Android system property such +/// as `keystore.boot_level`; it can report the current value of this +/// property, or wait for it to change. +pub struct PropertyWatcher { + prop_name: CString, + prop_info: *const PropInfo, + serial: keystore2_system_property_bindgen::__uint32_t, +} + +impl PropertyWatcher { + /// Create a PropertyWatcher for the named system property. + pub fn new(name: &str) -> Result<Self> { + Ok(Self { prop_name: CString::new(name)?, prop_info: null(), serial: 0 }) + } + + // Lazy-initializing accessor for self.prop_info. + fn get_prop_info(&mut self) -> Option<*const PropInfo> { + if self.prop_info.is_null() { + // Unsafe required for FFI call. Input and output are both const. + // The returned pointer is valid for the lifetime of the program. + self.prop_info = unsafe { + keystore2_system_property_bindgen::__system_property_find(self.prop_name.as_ptr()) + }; + } + if self.prop_info.is_null() { + None + } else { + Some(self.prop_info) + } + } + + fn read_raw(prop_info: *const PropInfo, mut f: impl FnOnce(Option<&CStr>, Option<&CStr>)) { + // Unsafe function converts values passed to us by + // __system_property_read_callback to Rust form + // and pass them to inner callback. + unsafe extern "C" fn callback( + res_p: *mut c_void, + name: *const c_char, + value: *const c_char, + _: keystore2_system_property_bindgen::__uint32_t, + ) { + let name = if name.is_null() { None } else { Some(CStr::from_ptr(name)) }; + let value = if value.is_null() { None } else { Some(CStr::from_ptr(value)) }; + let f = &mut *res_p.cast::<&mut dyn FnMut(Option<&CStr>, Option<&CStr>)>(); + f(name, value); + } + + let mut f: &mut dyn FnOnce(Option<&CStr>, Option<&CStr>) = &mut f; + + // Unsafe block for FFI call. We convert the FnOnce + // to a void pointer, and unwrap it in our callback. + unsafe { + keystore2_system_property_bindgen::__system_property_read_callback( + prop_info, + Some(callback), + &mut f as *mut _ as *mut c_void, + ) + } + } + + /// Call the passed function, passing it the name and current value + /// of this system property. See documentation for + /// `__system_property_read_callback` for details. + /// Returns an error if the property is empty or doesn't exist. + pub fn read<T, F>(&mut self, f: F) -> Result<T> + where + F: FnOnce(&str, &str) -> anyhow::Result<T>, + { + let prop_info = self.get_prop_info().ok_or(PropertyWatcherError::SystemPropertyAbsent)?; + let mut result = Err(PropertyWatcherError::ReadCallbackNotCalled); + Self::read_raw(prop_info, |name, value| { + // use a wrapping closure as an erzatz try block. + result = (|| { + let name = name.ok_or(PropertyWatcherError::MissingCString)?.to_str()?; + let value = value.ok_or(PropertyWatcherError::MissingCString)?.to_str()?; + f(name, value).map_err(PropertyWatcherError::CallbackError) + })() + }); + result + } + + // Waits for the property that self is watching to be created. Returns immediately if the + // property already exists. + fn wait_for_property_creation(&mut self) -> Result<()> { + let mut global_serial = 0; + loop { + match self.get_prop_info() { + Some(_) => return Ok(()), + None => { + // Unsafe call for FFI. The function modifies only global_serial, and has + // no side-effects. + if !unsafe { + // Wait for a global serial number change, then try again. On success, + // the function will update global_serial with the last version seen. + keystore2_system_property_bindgen::__system_property_wait( + null(), + global_serial, + &mut global_serial, + null(), + ) + } { + return Err(PropertyWatcherError::WaitFailed); + } + } + } + } + } + + /// Wait for the system property to change. This + /// records the serial number of the last change, so + /// race conditions are avoided. + pub fn wait(&mut self) -> Result<()> { + // If the property is null, then wait for it to be created. Subsequent waits will + // skip this step and wait for our specific property to change. + if self.prop_info.is_null() { + return self.wait_for_property_creation(); + } + + let mut new_serial = self.serial; + // Unsafe block to call __system_property_wait. + // All arguments are private to PropertyWatcher so we + // can be confident they are valid. + if !unsafe { + keystore2_system_property_bindgen::__system_property_wait( + self.prop_info, + self.serial, + &mut new_serial, + null(), + ) + } { + return Err(PropertyWatcherError::WaitFailed); + } + self.serial = new_serial; + Ok(()) + } +} + +/// Writes a system property. +pub fn write(name: &str, value: &str) -> AnyhowResult<()> { + if + // Unsafe required for FFI call. Input and output are both const and valid strings. + unsafe { + // If successful, __system_property_set returns 0, otherwise, returns -1. + keystore2_system_property_bindgen::__system_property_set( + CString::new(name) + .context("In keystore2::system_property::write: Construction CString from name.")? + .as_ptr(), + CString::new(value) + .context("In keystore2::system_property::write: Constructing CString from value.")? + .as_ptr(), + ) + } == 0 + { + Ok(()) + } else { + Err(anyhow!(PropertyWatcherError::SetPropertyFailed)) + } +} diff --git a/fsverity/fsverity_digests.proto b/keystore2/system_property/system_property_bindgen.hpp index 816ae61b..e3c1ade0 100644 --- a/fsverity/fsverity_digests.proto +++ b/keystore2/system_property/system_property_bindgen.hpp @@ -13,15 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#pragma once -syntax = "proto3"; - -package android.security.fsverity; - -message FSVerityDigests { - message Digest { - bytes digest = 1; - string hash_alg = 2; - } - map<string, Digest> digests = 1; -} +#include "sys/system_properties.h" diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs deleted file mode 100644 index 4fbe1241..00000000 --- a/keystore2/test_utils/authorizations.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2022, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This module implements test utils to create Autherizations. - -use std::ops::Deref; - -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyParameter::KeyParameter, - KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, Tag::Tag, -}; - -/// Helper struct to create set of Authorizations. -pub struct AuthSetBuilder(Vec<KeyParameter>); - -impl Default for AuthSetBuilder { - fn default() -> Self { - Self::new() - } -} - -impl AuthSetBuilder { - /// Creates new Authorizations list. - pub fn new() -> Self { - Self(Vec::new()) - } - - /// Add Purpose. - pub fn purpose(mut self, p: KeyPurpose) -> Self { - self.0.push(KeyParameter { tag: Tag::PURPOSE, value: KeyParameterValue::KeyPurpose(p) }); - self - } - - /// Add Digest. - pub fn digest(mut self, d: Digest) -> Self { - self.0.push(KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(d) }); - self - } - - /// Add Algorithm. - pub fn algorithm(mut self, a: Algorithm) -> Self { - self.0.push(KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(a) }); - self - } - - /// Add EC-Curve. - pub fn ec_curve(mut self, e: EcCurve) -> Self { - self.0.push(KeyParameter { tag: Tag::EC_CURVE, value: KeyParameterValue::EcCurve(e) }); - self - } - - /// Add Attestation-Challenge. - pub fn attestation_challenge(mut self, b: Vec<u8>) -> Self { - self.0.push(KeyParameter { - tag: Tag::ATTESTATION_CHALLENGE, - value: KeyParameterValue::Blob(b), - }); - self - } - - /// Add Attestation-ID. - pub fn attestation_app_id(mut self, b: Vec<u8>) -> Self { - self.0.push(KeyParameter { - tag: Tag::ATTESTATION_APPLICATION_ID, - value: KeyParameterValue::Blob(b), - }); - self - } -} - -impl Deref for AuthSetBuilder { - type Target = Vec<KeyParameter>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs deleted file mode 100644 index f49aa9ff..00000000 --- a/keystore2/test_utils/key_generations.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2022, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This module implements test utils to generate various types of keys. - -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose, -}; -use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, - KeyMetadata::KeyMetadata, -}; - -use crate::authorizations::AuthSetBuilder; - -const SELINUX_SHELL_NAMESPACE: i64 = 1; - -/// Generate attested EC Key blob using given security level with below key parameters - -/// Purposes: SIGN and VERIFY -/// Digest: SHA_2_256 -/// Curve: P_256 -pub fn generate_ec_p256_signing_key_with_attestation( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, -) -> binder::Result<KeyMetadata> { - let att_challenge: &[u8] = b"foo"; - let att_app_id: &[u8] = b"bar"; - let gen_params = AuthSetBuilder::new() - .algorithm(Algorithm::EC) - .purpose(KeyPurpose::SIGN) - .purpose(KeyPurpose::VERIFY) - .digest(Digest::SHA_2_256) - .ec_curve(EcCurve::P_256) - .attestation_challenge(att_challenge.to_vec()) - .attestation_app_id(att_app_id.to_vec()); - - match sec_level.generateKey( - &KeyDescriptor { - domain: Domain::BLOB, - nspace: SELINUX_SHELL_NAMESPACE, - alias: None, - blob: None, - }, - None, - &gen_params, - 0, - b"entropy", - ) { - Ok(key_metadata) => { - assert!(key_metadata.certificate.is_some()); - assert!(key_metadata.certificateChain.is_some()); - assert!(key_metadata.key.blob.is_some()); - - Ok(key_metadata) - } - Err(e) => Err(e), - } -} diff --git a/keystore2/test_utils/lib.rs b/keystore2/test_utils/lib.rs index c63bfacc..627af20c 100644 --- a/keystore2/test_utils/lib.rs +++ b/keystore2/test_utils/lib.rs @@ -19,14 +19,6 @@ use std::io::ErrorKind; use std::path::{Path, PathBuf}; use std::{env::temp_dir, ops::Deref}; -use android_system_keystore2::aidl::android::system::keystore2::IKeystoreService::IKeystoreService; - -pub mod authorizations; -pub mod key_generations; -pub mod run_as; - -static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default"; - /// Represents the lifecycle of a temporary directory for testing. #[derive(Debug)] pub struct TempDir { @@ -110,8 +102,3 @@ impl Deref for PathBuilder { &self.0 } } - -/// Get Keystore2 service. -pub fn get_keystore_service() -> binder::Strong<dyn IKeystoreService> { - binder::get_interface(KS2_SERVICE_NAME).unwrap() -} diff --git a/keystore2/test_utils/run_as.rs b/keystore2/test_utils/run_as.rs deleted file mode 100644 index 2485ab57..00000000 --- a/keystore2/test_utils/run_as.rs +++ /dev/null @@ -1,474 +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 is intended for testing access control enforcement of services such as keystore2, -//! by assuming various identities with varying levels of privilege. Consequently, appropriate -//! privileges are required, or the attempt will fail causing a panic. -//! The `run_as` module provides the function `run_as`, which takes a UID, GID, an SELinux -//! context, and a closure. The return type of the closure, which is also the return type of -//! `run_as`, must implement `serde::Serialize` and `serde::Deserialize`. -//! `run_as` forks, transitions to the given identity, and executes the closure in the newly -//! forked process. If the closure returns, i.e., does not panic, the forked process exits with -//! a status of `0`, and the return value is serialized and sent through a pipe to the parent where -//! it gets deserialized and returned. The STDIO is not changed and the parent's panic handler -//! remains unchanged. So if the closure panics, the panic message is printed on the parent's STDERR -//! and the exit status is set to a non `0` value. The latter causes the parent to panic as well, -//! and if run in a test context, the test to fail. - -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, -}; -use serde::{de::DeserializeOwned, Serialize}; -use std::io::{Read, Write}; -use std::marker::PhantomData; -use std::os::unix::io::RawFd; - -fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) { - setgid(gid).expect("Failed to set GID. This test might need more privileges."); - setuid(uid).expect("Failed to set UID. This test might need more privileges."); - - selinux::setcon(&se_context) - .expect("Failed to set SELinux context. This test might need more privileges."); -} - -/// 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); - -impl Read for PipeReader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - let bytes = nix_read(self.0, 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."); - } -} - -impl Write for PipeWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { - let written = nix_write(self.0, buf)?; - Ok(written) - } - - fn flush(&mut self) -> std::io::Result<()> { - // Flush is a NO-OP. - Ok(()) - } -} - -/// Denotes the sender side of a serializing channel. -pub struct ChannelWriter<T: Serialize + DeserializeOwned>(PipeWriter, PhantomData<T>); - -impl<T: Serialize + DeserializeOwned> ChannelWriter<T> { - /// Sends a serializable object to a the corresponding ChannelReader. - /// Sending is always non blocking. Panics if any error occurs during io or serialization. - pub fn send(&mut self, value: &T) { - let serialized = serde_cbor::to_vec(value) - .expect("In ChannelWriter::send: Failed to serialize to vector."); - let size = serialized.len().to_be_bytes(); - match self.0.write(&size).expect("In ChannelWriter::send: Failed to write serialized size.") - { - w if w != std::mem::size_of::<usize>() => { - panic!( - "In ChannelWriter::send: Failed to write serialized size. (written: {}).", - w - ); - } - _ => {} - }; - match self - .0 - .write(&serialized) - .expect("In ChannelWriter::send: Failed to write serialized data.") - { - w if w != serialized.len() => { - panic!( - "In ChannelWriter::send: Failed to write serialized data. (written: {}).", - w - ); - } - _ => {} - }; - } -} - -/// Represents the receiving and of a serializing channel. -pub struct ChannelReader<T>(PipeReader, PhantomData<T>); - -impl<T: Serialize + DeserializeOwned> ChannelReader<T> { - /// Receives a serializable object from the corresponding ChannelWriter. - /// Receiving blocks until an object of type T has been read from the channel. - /// Panics if an error occurs during io or deserialization. - pub fn recv(&mut self) -> T { - let mut size_buffer = [0u8; std::mem::size_of::<usize>()]; - match self.0.read(&mut size_buffer).expect("In ChannelReader::recv: Failed to read size.") { - r if r != size_buffer.len() => { - panic!("In ChannelReader::recv: Failed to read size. Insufficient data: {}", r); - } - _ => {} - }; - let size = usize::from_be_bytes(size_buffer); - let mut data_buffer = vec![0u8; size]; - match self - .0 - .read(&mut data_buffer) - .expect("In ChannelReader::recv: Failed to read serialized data.") - { - r if r != data_buffer.len() => { - panic!( - "In ChannelReader::recv: Failed to read serialized data. Insufficient data: {}", - r - ); - } - _ => {} - }; - - serde_cbor::from_slice(&data_buffer) - .expect("In ChannelReader::recv: Failed to deserialize data.") - } -} - -fn pipe() -> Result<(PipeReader, PipeWriter), nix::Error> { - let (read_fd, write_fd) = nix_pipe()?; - Ok((PipeReader(read_fd), PipeWriter(write_fd))) -} - -fn pipe_channel<T>() -> Result<(ChannelReader<T>, ChannelWriter<T>), nix::Error> -where - T: Serialize + DeserializeOwned, -{ - let (reader, writer) = pipe()?; - Ok(( - ChannelReader::<T>(reader, Default::default()), - ChannelWriter::<T>(writer, Default::default()), - )) -} - -/// Handle for handling child processes. -pub struct ChildHandle<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> { - pid: Pid, - result_reader: ChannelReader<R>, - cmd_writer: ChannelWriter<M>, - response_reader: ChannelReader<M>, - exit_status: Option<WaitStatus>, -} - -impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> ChildHandle<R, M> { - /// Send a command message to the child. - pub fn send(&mut self, data: &M) { - self.cmd_writer.send(data) - } - - /// Receive a response from the child. - pub fn recv(&mut self) -> M { - self.response_reader.recv() - } - - /// Get child result. Panics if the child did not exit with status 0 or if a serialization - /// error occurred. - pub fn get_result(mut self) -> R { - let status = - waitpid(self.pid, None).expect("ChildHandle::wait: Failed while waiting for child."); - match status { - WaitStatus::Exited(pid, 0) => { - // Child exited successfully. - // Read the result from the pipe. - self.exit_status = Some(WaitStatus::Exited(pid, 0)); - self.result_reader.recv() - } - WaitStatus::Exited(pid, c) => { - panic!("Child did not exit as expected: {:?}", WaitStatus::Exited(pid, c)); - } - status => { - panic!("Child did not exit at all: {:?}", status); - } - } - } -} - -impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> Drop for ChildHandle<R, M> { - fn drop(&mut self) { - if self.exit_status.is_none() { - panic!("Child result not checked.") - } - } -} - -/// Run the given closure in a new process running with the new identity given as -/// `uid`, `gid`, and `se_context`. Parent process will run without waiting for child status. -/// -/// # Safety -/// run_as_child runs the given closure in the client branch of fork. And it uses non -/// async signal safe API. This means that calling this function in a multi threaded program -/// yields undefined behavior in the child. As of this writing, it is safe to call this function -/// from a Rust device test, because every test itself is spawned as a separate process. -/// -/// # Safety Binder -/// It is okay for the closure to use binder services, however, this does not work -/// if the parent initialized libbinder already. So do not use binder outside of the closure -/// in your test. -pub unsafe fn run_as_child<F, R, M>( - se_context: &str, - uid: Uid, - gid: Gid, - f: F, -) -> Result<ChildHandle<R, M>, nix::Error> -where - R: Serialize + DeserializeOwned, - M: Serialize + DeserializeOwned, - F: 'static + Send + FnOnce(&mut ChannelReader<M>, &mut ChannelWriter<M>) -> R, -{ - let se_context = - selinux::Context::new(se_context).expect("Unable to construct selinux::Context."); - let (result_reader, mut result_writer) = pipe_channel().expect("Failed to create pipe."); - let (mut cmd_reader, cmd_writer) = pipe_channel().expect("Failed to create cmd pipe."); - let (response_reader, mut response_writer) = - pipe_channel().expect("Failed to create cmd pipe."); - - match fork() { - Ok(ForkResult::Parent { child, .. }) => { - drop(response_writer); - drop(cmd_reader); - drop(result_writer); - - Ok(ChildHandle::<R, M> { - pid: child, - result_reader, - response_reader, - cmd_writer, - exit_status: None, - }) - } - Ok(ForkResult::Child) => { - drop(cmd_writer); - drop(response_reader); - drop(result_reader); - - // This will panic on error or insufficient privileges. - transition(se_context, uid, gid); - - // Run the closure. - let result = f(&mut cmd_reader, &mut response_writer); - - // Serialize the result of the closure. - result_writer.send(&result); - - // Set exit status to `0`. - std::process::exit(0); - } - Err(errno) => { - panic!("Failed to fork: {:?}", errno); - } - } -} - -/// Run the given closure in a new process running with the new identity given as -/// `uid`, `gid`, and `se_context`. -/// -/// # Safety -/// run_as runs the given closure in the client branch of fork. And it uses non -/// async signal safe API. This means that calling this function in a multi threaded program -/// yields undefined behavior in the child. As of this writing, it is safe to call this function -/// from a Rust device test, because every test itself is spawned as a separate process. -/// -/// # Safety Binder -/// It is okay for the closure to use binder services, however, this does not work -/// if the parent initialized libbinder already. So do not use binder outside of the closure -/// in your test. -pub unsafe fn run_as<F, R>(se_context: &str, uid: Uid, gid: Gid, f: F) -> R -where - R: Serialize + DeserializeOwned, - F: 'static + Send + FnOnce() -> R, -{ - let se_context = - 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() { - Ok(ForkResult::Parent { child, .. }) => { - drop(writer); - let status = waitpid(child, None).expect("Failed while waiting for child."); - if let WaitStatus::Exited(_, 0) = status { - // Child exited successfully. - // Read the result from the pipe. - // let serialized_result = - // reader.read_all().expect("Failed to read result from child."); - - // Deserialize the result and return it. - reader.recv() - } else { - panic!("Child did not exit as expected {:?}", status); - } - } - Ok(ForkResult::Child) => { - // This will panic on error or insufficient privileges. - transition(se_context, uid, gid); - - // Run the closure. - let result = f(); - - // Serialize the result of the closure. - writer.send(&result); - - // Set exit status to `0`. - std::process::exit(0); - } - Err(errno) => { - panic!("Failed to fork: {:?}", errno); - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use keystore2_selinux as selinux; - use nix::unistd::{getgid, getuid}; - use serde::{Deserialize, Serialize}; - - /// This test checks that the closure does not produce an exit status of `0` when run inside a - /// test and the closure panics. This would mask test failures as success. - #[test] - #[should_panic] - fn test_run_as_panics_on_closure_panic() { - // Safety: run_as must be called from a single threaded process. - // This device test is run as a separate single threaded process. - unsafe { - run_as(selinux::getcon().unwrap().to_str().unwrap(), getuid(), getgid(), || { - panic!("Closure must panic.") - }) - }; - } - - static TARGET_UID: Uid = Uid::from_raw(10020); - static TARGET_GID: Gid = Gid::from_raw(10020); - static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; - - /// Tests that the closure is running as the target identity. - #[test] - fn test_transition_to_untrusted_app() { - // Safety: run_as must be called from a single threaded process. - // This device test is run as a separate single threaded process. - unsafe { - run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || { - assert_eq!(TARGET_UID, getuid()); - assert_eq!(TARGET_GID, getgid()); - assert_eq!(TARGET_CTX, selinux::getcon().unwrap().to_str().unwrap()); - }) - }; - } - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - struct SomeResult { - a: u32, - b: u64, - c: String, - } - - #[test] - fn test_serialized_result() { - let test_result = SomeResult { - a: 5, - b: 0xffffffffffffffff, - c: "supercalifragilisticexpialidocious".to_owned(), - }; - let test_result_clone = test_result.clone(); - // Safety: run_as must be called from a single threaded process. - // This device test is run as a separate single threaded process. - let result = unsafe { run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || test_result_clone) }; - assert_eq!(test_result, result); - } - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - enum PingPong { - Ping, - Pong, - } - - /// Tests that closure is running under given user identity and communicates with calling - /// process using pipe. - #[test] - fn test_run_as_child() { - let test_result = SomeResult { - a: 5, - b: 0xffffffffffffffff, - c: "supercalifragilisticexpialidocious".to_owned(), - }; - let test_result_clone = test_result.clone(); - - // Safety: run_as_child must be called from a single threaded process. - // This device test is run as a separate single threaded process. - let mut child_handle: ChildHandle<SomeResult, PingPong> = unsafe { - run_as_child(TARGET_CTX, TARGET_UID, TARGET_GID, |cmd_reader, response_writer| { - assert_eq!(TARGET_UID, getuid()); - assert_eq!(TARGET_GID, getgid()); - assert_eq!(TARGET_CTX, selinux::getcon().unwrap().to_str().unwrap()); - - let ping: PingPong = cmd_reader.recv(); - assert_eq!(ping, PingPong::Ping); - - response_writer.send(&PingPong::Pong); - - let ping: PingPong = cmd_reader.recv(); - assert_eq!(ping, PingPong::Ping); - let pong: PingPong = cmd_reader.recv(); - assert_eq!(pong, PingPong::Pong); - - response_writer.send(&PingPong::Pong); - response_writer.send(&PingPong::Ping); - - test_result_clone - }) - .unwrap() - }; - - // Send one ping. - child_handle.send(&PingPong::Ping); - - // Expect one pong. - let pong = child_handle.recv(); - assert_eq!(pong, PingPong::Pong); - - // Send ping and pong. - child_handle.send(&PingPong::Ping); - child_handle.send(&PingPong::Pong); - - // Expect pong and ping. - let pong = child_handle.recv(); - assert_eq!(pong, PingPong::Pong); - let ping = child_handle.recv(); - assert_eq!(ping, PingPong::Ping); - - assert_eq!(child_handle.get_result(), test_result); - } -} diff --git a/keystore2/tests/legacy_blobs/AndroidTest.xml b/keystore2/tests/legacy_blobs/AndroidTest.xml deleted file mode 100644 index ea83fbf7..00000000 --- a/keystore2/tests/legacy_blobs/AndroidTest.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration description="Config to run keystore2_legacy_blobs_test device tests."> - - <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> - </target_preparer> - - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> - <option name="cleanup" value="true" /> - <option - name="push" - value="keystore2_legacy_blobs_test->/data/local/tmp/keystore2_legacy_blobs_test" - /> - </target_preparer> - - <test class="com.android.tradefed.testtype.rust.RustBinaryTest" > - <option name="test-device-path" value="/data/local/tmp" /> - <option name="module-name" value="keystore2_legacy_blobs_test" /> - <option name="native-test-flag" value="--test-threads=1" /> - </test> -</configuration> diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs deleted file mode 100644 index 6def39e2..00000000 --- a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright 2022, The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use nix::unistd::{getuid, Gid, Uid}; -use rustutils::users::AID_USER_OFFSET; -use serde::{Deserialize, Serialize}; - -use std::ops::Deref; -use std::path::PathBuf; - -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel; - -use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, KeyDescriptor::KeyDescriptor, -}; - -use android_security_maintenance::aidl::android::security::maintenance::{ - IKeystoreMaintenance::IKeystoreMaintenance, UserState::UserState, -}; - -use android_security_authorization::aidl::android::security::authorization::{ - IKeystoreAuthorization::IKeystoreAuthorization, LockScreenEvent::LockScreenEvent, -}; - -use keystore2::key_parameter::KeyParameter as KsKeyparameter; -use keystore2::legacy_blob::test_utils::legacy_blob_test_vectors::*; -use keystore2::legacy_blob::test_utils::*; -use keystore2::legacy_blob::LegacyKeyCharacteristics; -use keystore2::utils::AesGcm; -use keystore2_crypto::{Password, ZVec}; - -use keystore2_test_utils::get_keystore_service; -use keystore2_test_utils::key_generations; -use keystore2_test_utils::run_as; - -static USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance"; -static AUTH_SERVICE_NAME: &str = "android.security.authorization"; -const SELINUX_SHELL_NAMESPACE: i64 = 1; - -fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> { - binder::get_interface(USER_MANAGER_SERVICE_NAME).unwrap() -} - -fn get_authorization() -> binder::Strong<dyn IKeystoreAuthorization> { - binder::get_interface(AUTH_SERVICE_NAME).unwrap() -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -struct KeygenResult { - cert: Vec<u8>, - cert_chain: Vec<u8>, - key_parameters: Vec<KsKeyparameter>, -} - -struct TestKey(ZVec); - -impl keystore2::utils::AesGcmKey for TestKey { - fn key(&self) -> &[u8] { - &self.0 - } -} - -impl Deref for TestKey { - type Target = [u8]; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -fn keystore2_restart_service() { - let output = std::process::Command::new("pidof") - .arg("keystore2") - .output() - .expect("failed to execute pidof keystore2"); - - let id = String::from_utf8(output.stdout).unwrap(); - let id: String = id.chars().filter(|c| c.is_digit(10)).collect(); - - let _status = std::process::Command::new("kill").arg("-9").arg(id).status().unwrap(); - - // Loop till we find keystore2 service up and running. - loop { - let output = std::process::Command::new("pidof") - .arg("keystore2") - .output() - .expect("failed to execute pidof keystore2"); - - if output.status.code() == Some(0) { - break; - } - } -} - -/// Create legacy blobs file layout for a user with user-id 99 and app-id 10001 with -/// user-cert, ca-certs and encrypted key-characteristics files and tries to import -/// these legacy blobs under user context. -/// -/// Expected File layout for user with user-id "98" and app-id "10001" and key-alias -/// "authbound": -/// /data/misc/keystore/user_99/.masterkey -/// /data/misc/keystore/user_99/9910001_USRPKEY_authbound -/// /data/misc/keystore/user_99/.9910001_chr_USRPKEY_authbound -/// /data/misc/keystore/user_99/9910001_USRCERT_authbound -/// /data/misc/keystore/user_99/9910001_CACERT_authbound -/// -/// Test performs below tasks - -/// With su context it performs following tasks - -/// 1. Remove this user if already exist. -/// 2. Generate a key-blob, user cert-blob and ca-cert-blob to store it in legacy blobs file -/// layout. -/// 3. Prepare file layout using generated key-blob, user cert and ca certs. -/// 4. Restart the keystore2 service to make it detect the populated legacy blobs. -/// 5. Inform the keystore2 service about the user and unlock the user. -/// With user-99 context it performs following tasks - -/// 6. To load and import the legacy key using its alias. -/// 7. After successful key import validate the user cert and cert-chain with initially -/// generated blobs. -/// 8. Validate imported key perameters. Imported key parameters list should be the combination -/// of the key-parameters in characteristics file and the characteristics according to -/// the augmentation rules. There might be duplicate entries with different values for the -/// parameters like OS_VERSION, OS_VERSION, BOOT_PATCHLEVEL, VENDOR_PATCHLEVEL etc. -/// 9. Confirm keystore2 service cleanup the legacy blobs after successful import. -#[test] -fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { - let auid = 99 * AID_USER_OFFSET + 10001; - let agid = 99 * AID_USER_OFFSET + 10001; - static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; - static TARGET_SU_CTX: &str = "u:r:su:s0"; - - // Cleanup user directory if it exists - let path_buf = PathBuf::from("/data/misc/keystore/user_99"); - if path_buf.as_path().is_dir() { - std::fs::remove_dir_all(path_buf.as_path()).unwrap(); - } - - // Safety: run_as must be called from a single threaded process. - // This device test is run as a separate single threaded process. - let mut gen_key_result = unsafe { - run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - // Remove user if already exist. - let maint_service = get_maintenance(); - match maint_service.onUserRemoved(99) { - Ok(_) => { - println!("User was existed, deleted successfully"); - } - Err(e) => { - println!("onUserRemoved error: {:#?}", e); - } - } - - let keystore2 = get_keystore_service(); - let sec_level = keystore2 - .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT) - .unwrap(); - // Generate Key BLOB and prepare legacy keystore blob files. - let key_metadata = - key_generations::generate_ec_p256_signing_key_with_attestation(&sec_level) - .expect("Failed to generate key blob"); - - // Create keystore file layout for user_99. - let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); - let super_key = - TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()); - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_99"); - if !path_buf.as_path().is_dir() { - std::fs::create_dir(path_buf.as_path()).unwrap(); - } - path_buf.push(".masterkey"); - if !path_buf.as_path().is_file() { - std::fs::write(path_buf.as_path(), SUPERKEY).unwrap(); - } - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_99"); - path_buf.push("9910001_USRPKEY_authbound"); - if !path_buf.as_path().is_file() { - make_encrypted_key_file( - path_buf.as_path(), - &super_key, - &key_metadata.key.blob.unwrap(), - ) - .unwrap(); - } - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_99"); - path_buf.push(".9910001_chr_USRPKEY_authbound"); - if !path_buf.as_path().is_file() { - make_encrypted_characteristics_file(path_buf.as_path(), &super_key, KEY_PARAMETERS) - .unwrap(); - } - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_99"); - path_buf.push("9910001_USRCERT_authbound"); - if !path_buf.as_path().is_file() { - make_cert_blob_file(path_buf.as_path(), key_metadata.certificate.as_ref().unwrap()) - .unwrap(); - } - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_99"); - path_buf.push("9910001_CACERT_authbound"); - if !path_buf.as_path().is_file() { - make_cert_blob_file( - path_buf.as_path(), - key_metadata.certificateChain.as_ref().unwrap(), - ) - .unwrap(); - } - - // Keystore2 disables the legacy importer when it finds the legacy database empty. - // However, if the device boots with an empty legacy database, the optimization kicks in - // and keystore2 never checks the legacy file system layout. - // So, restart keystore2 service to detect populated legacy database. - keystore2_restart_service(); - - let auth_service = get_authorization(); - match auth_service.onLockScreenEvent(LockScreenEvent::UNLOCK, 99, Some(PASSWORD), None) - { - Ok(result) => { - println!("Unlock Result: {:?}", result); - } - Err(e) => { - panic!("Unlock should have succeeded: {:?}", e); - } - } - - let maint_service = get_maintenance(); - assert_eq!(Ok(UserState(1)), maint_service.getState(99)); - - let mut key_params: Vec<KsKeyparameter> = Vec::new(); - for param in key_metadata.authorizations { - let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel); - key_params.push(key_param); - } - - KeygenResult { - cert: key_metadata.certificate.unwrap(), - cert_chain: key_metadata.certificateChain.unwrap(), - key_parameters: key_params, - } - }) - }; - - // Safety: run_as must be called from a single threaded process. - // This device test is run as a separate single threaded process. - unsafe { - run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || { - println!("UID: {}", getuid()); - println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9910001)); - println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9910001)); - - let test_alias = "authbound"; - let keystore2 = get_keystore_service(); - - match keystore2.getKeyEntry(&KeyDescriptor { - domain: Domain::APP, - nspace: SELINUX_SHELL_NAMESPACE, - alias: Some(test_alias.to_string()), - blob: None, - }) { - Ok(key_entry_response) => { - assert_eq!( - key_entry_response.metadata.certificate.unwrap(), - gen_key_result.cert - ); - assert_eq!( - key_entry_response.metadata.certificateChain.unwrap(), - gen_key_result.cert_chain - ); - assert_eq!(key_entry_response.metadata.key.domain, Domain::KEY_ID); - assert_ne!(key_entry_response.metadata.key.nspace, 0); - assert_eq!( - key_entry_response.metadata.keySecurityLevel, - SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT - ); - - // Preapare KsKeyParameter list from getKeEntry response Authorizations. - let mut key_params: Vec<KsKeyparameter> = Vec::new(); - for param in key_entry_response.metadata.authorizations { - let key_param = - KsKeyparameter::new(param.keyParameter.into(), param.securityLevel); - key_params.push(key_param); - } - - // Combine keyparameters from gen_key_result and keyparameters - // from legacy key-char file. - let mut legacy_file_key_params: Vec<KsKeyparameter> = Vec::new(); - match structured_test_params() { - LegacyKeyCharacteristics::File(legacy_key_params) => { - for param in &legacy_key_params { - let mut present_in_gen_params = false; - for gen_param in &gen_key_result.key_parameters { - if param.get_tag() == gen_param.get_tag() { - present_in_gen_params = true; - } - } - if !present_in_gen_params { - legacy_file_key_params.push(param.clone()); - } - } - } - _ => { - panic!("Expecting file characteristics"); - } - } - - // Remove Key-Params which have security levels other than TRUSTED_ENVIRONMENT - gen_key_result.key_parameters.retain(|in_element| { - *in_element.security_level() - == SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT - }); - - println!("GetKeyEntry response key params: {:#?}", key_params); - println!("Generated key params: {:#?}", gen_key_result.key_parameters); - - gen_key_result.key_parameters.append(&mut legacy_file_key_params); - - println!("Combined key params: {:#?}", gen_key_result.key_parameters); - - // Validate all keyparameters present in getKeyEntry response. - for param in &key_params { - gen_key_result.key_parameters.retain(|in_element| *in_element != *param); - } - - println!( - "GetKeyEntry response unmatched key params: {:#?}", - gen_key_result.key_parameters - ); - assert_eq!(gen_key_result.key_parameters.len(), 0); - } - Err(s) => { - panic!("getKeyEntry should have succeeded. {:?}", s); - } - }; - }) - }; - - // Make sure keystore2 clean up imported legacy db. - let path_buf = PathBuf::from("/data/misc/keystore/user_99"); - if path_buf.as_path().is_dir() { - panic!("Keystore service should have deleted this dir {:?}", path_buf); - } - Ok(()) -} - -/// Create legacy blobs file layout for a user with user-id 98 and app-id 10001 with encrypted -/// user-cert and ca-certs files and tries to import these legacy blobs under user context. -/// -/// Expected File layout for user with user-id "98" and app-id "10001" and key-alias -/// "authboundcertenc": -/// /data/misc/keystore/user_98/.masterkey -/// /data/misc/keystore/user_98/9810001_USRPKEY_authboundcertenc -/// /data/misc/keystore/user_98/.9810001_chr_USRPKEY_authboundcertenc -/// /data/misc/keystore/user_98/9810001_USRCERT_authboundcertenc -/// /data/misc/keystore/user_98/9810001_CACERT_authboundcertenc -/// -/// Test performs below tasks - -/// With su context it performs following tasks - -/// 1. Remove this user if already exist. -/// 2. Generate a key-blob, user cert-blob and ca-cert-blob to store it in legacy blobs file -/// layout. -/// 3. Prepare file layout using generated key-blob, user cert and ca certs. -/// 4. Restart the keystore2 service to make it detect the populated legacy blobs. -/// 5. Inform the keystore2 service about the user and unlock the user. -/// With user-98 context it performs following tasks - -/// 6. To load and import the legacy key using its alias. -/// 7. After successful key import validate the user cert and cert-chain with initially -/// generated blobs. -/// 8. Validate imported key perameters. Imported key parameters list should be the combination -/// of the key-parameters in characteristics file and the characteristics according to -/// the augmentation rules. There might be duplicate entries with different values for the -/// parameters like OS_VERSION, OS_VERSION, BOOT_PATCHLEVEL, VENDOR_PATCHLEVEL etc. -/// 9. Confirm keystore2 service cleanup the legacy blobs after successful import. -#[test] -fn keystore2_encrypted_certificates() -> anyhow::Result<()> { - let auid = 98 * AID_USER_OFFSET + 10001; - let agid = 98 * AID_USER_OFFSET + 10001; - static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; - static TARGET_SU_CTX: &str = "u:r:su:s0"; - - // Cleanup user directory if it exists - let path_buf = PathBuf::from("/data/misc/keystore/user_98"); - if path_buf.as_path().is_dir() { - std::fs::remove_dir_all(path_buf.as_path()).unwrap(); - } - - // Safety: run_as must be called from a single threaded process. - // This device test is run as a separate single threaded process. - let gen_key_result = unsafe { - run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - // Remove user if already exist. - let maint_service = get_maintenance(); - match maint_service.onUserRemoved(98) { - Ok(_) => { - println!("User was existed, deleted successfully"); - } - Err(e) => { - println!("onUserRemoved error: {:#?}", e); - } - } - - let keystore2 = get_keystore_service(); - let sec_level = keystore2 - .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT) - .unwrap(); - // Generate Key BLOB and prepare legacy keystore blob files. - let key_metadata = - key_generations::generate_ec_p256_signing_key_with_attestation(&sec_level) - .expect("Failed to generate key blob"); - - // Create keystore file layout for user_98. - let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap()); - let super_key = - TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()); - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_98"); - if !path_buf.as_path().is_dir() { - std::fs::create_dir(path_buf.as_path()).unwrap(); - } - path_buf.push(".masterkey"); - if !path_buf.as_path().is_file() { - std::fs::write(path_buf.as_path(), SUPERKEY).unwrap(); - } - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_98"); - path_buf.push("9810001_USRPKEY_authboundcertenc"); - if !path_buf.as_path().is_file() { - make_encrypted_key_file( - path_buf.as_path(), - &super_key, - &key_metadata.key.blob.unwrap(), - ) - .unwrap(); - } - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_98"); - path_buf.push(".9810001_chr_USRPKEY_authboundcertenc"); - if !path_buf.as_path().is_file() { - std::fs::write(path_buf.as_path(), USRPKEY_AUTHBOUND_CHR).unwrap(); - } - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_98"); - path_buf.push("9810001_USRCERT_authboundcertenc"); - if !path_buf.as_path().is_file() { - make_encrypted_usr_cert_file( - path_buf.as_path(), - &super_key, - key_metadata.certificate.as_ref().unwrap(), - ) - .unwrap(); - } - - let mut path_buf = PathBuf::from("/data/misc/keystore/user_98"); - path_buf.push("9810001_CACERT_authboundcertenc"); - if !path_buf.as_path().is_file() { - make_encrypted_ca_cert_file( - path_buf.as_path(), - &super_key, - key_metadata.certificateChain.as_ref().unwrap(), - ) - .unwrap(); - } - - // Keystore2 disables the legacy importer when it finds the legacy database empty. - // However, if the device boots with an empty legacy database, the optimization kicks in - // and keystore2 never checks the legacy file system layout. - // So, restart keystore2 service to detect populated legacy database. - keystore2_restart_service(); - - let auth_service = get_authorization(); - match auth_service.onLockScreenEvent(LockScreenEvent::UNLOCK, 98, Some(PASSWORD), None) - { - Ok(result) => { - println!("Unlock Result: {:?}", result); - } - Err(e) => { - panic!("Unlock should have succeeded: {:?}", e); - } - } - - let maint_service = get_maintenance(); - assert_eq!(Ok(UserState(1)), maint_service.getState(98)); - - let mut key_params: Vec<KsKeyparameter> = Vec::new(); - for param in key_metadata.authorizations { - let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel); - key_params.push(key_param); - } - - KeygenResult { - cert: key_metadata.certificate.unwrap(), - cert_chain: key_metadata.certificateChain.unwrap(), - key_parameters: key_params, - } - }) - }; - - // Safety: run_as must be called from a single threaded process. - // This device test is run as a separate single threaded process. - unsafe { - run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || { - println!("UID: {}", getuid()); - println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9810001)); - println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9810001)); - - let test_alias = "authboundcertenc"; - let keystore2 = get_keystore_service(); - - match keystore2.getKeyEntry(&KeyDescriptor { - domain: Domain::APP, - nspace: SELINUX_SHELL_NAMESPACE, - alias: Some(test_alias.to_string()), - blob: None, - }) { - Ok(key_entry_response) => { - assert_eq!( - key_entry_response.metadata.certificate.unwrap(), - gen_key_result.cert - ); - assert_eq!( - key_entry_response.metadata.certificateChain.unwrap(), - gen_key_result.cert_chain - ); - - // Preapare KsKeyParameter list from getKeEntry response Authorizations. - let mut key_params: Vec<KsKeyparameter> = Vec::new(); - for param in key_entry_response.metadata.authorizations { - let key_param = - KsKeyparameter::new(param.keyParameter.into(), param.securityLevel); - key_params.push(key_param); - } - - println!("GetKeyEntry response key params: {:#?}", key_params); - println!("Generated key params: {:#?}", gen_key_result.key_parameters); - match structured_test_params_cache() { - LegacyKeyCharacteristics::Cache(legacy_key_params) => { - println!("Legacy key-char cache: {:#?}", legacy_key_params); - // Validate all keyparameters present in getKeyEntry response. - for param in &legacy_key_params { - key_params.retain(|in_element| *in_element != *param); - } - - println!( - "GetKeyEntry response unmatched key params: {:#?}", - key_params - ); - assert_eq!(key_params.len(), 0); - } - _ => { - panic!("Expecting file characteristics"); - } - } - } - Err(s) => { - panic!("getKeyEntry should have succeeded. {:?}", s); - } - }; - }) - }; - - // Make sure keystore2 clean up imported legacy db. - let path_buf = PathBuf::from("/data/misc/keystore/user_98"); - if path_buf.as_path().is_dir() { - panic!("Keystore service should have deleted this dir {:?}", path_buf); - } - Ok(()) -} diff --git a/ondevice-signing/Android.bp b/ondevice-signing/Android.bp index d73f8fe5..432e5854 100644 --- a/ondevice-signing/Android.bp +++ b/ondevice-signing/Android.bp @@ -11,6 +11,8 @@ // 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. +// List of clang-tidy checks that are reported as errors. +// Please keep this list ordered lexicographically. package { // See: http://go/android-license-faq @@ -21,8 +23,6 @@ package { default_applicable_licenses: ["system_security_license"], } -// List of clang-tidy checks that are reported as errors. -// Please keep this list ordered lexicographically. tidy_errors = [ "cert-err34-c", "google-default-arguments", @@ -74,33 +74,6 @@ cc_defaults { ], } -cc_library { - name: "libsigningutils", - defaults: [ - "odsign_flags_defaults", - ], - cpp_std: "experimental", - srcs: [ - "CertUtils.cpp", - "VerityUtils.cpp", - ], - - static_libs: [ - "libc++fs", - ], - - shared_libs: [ - "libbase", - "libcrypto", - "libcrypto_utils", - "libfsverity", - "libprotobuf-cpp-lite", - "libutils", - ], - export_include_dirs: ["include"], - recovery_available: true, -} - cc_binary { name: "odsign", defaults: [ @@ -109,19 +82,20 @@ cc_binary { cpp_std: "experimental", init_rc: ["odsign.rc"], srcs: [ + "odsign_main.cpp", + "CertUtils.cpp", "KeystoreKey.cpp", "KeystoreHmacKey.cpp", - "odsign_main.cpp", - "StatsReporter.cpp", + "VerityUtils.cpp", ], header_libs: ["odrefresh_headers"], static_libs: [ "libc++fs", - "libsigningutils", "lib_odsign_proto", ], + shared_libs: [ "android.system.keystore2-V1-cpp", "android.hardware.security.keymint-V1-cpp", @@ -131,7 +105,7 @@ cc_binary { "libcrypto_utils", "libfsverity", "liblogwrap", - "libprotobuf-cpp-lite", + "libprotobuf-cpp-full", "libutils", ], } diff --git a/ondevice-signing/CertUtils.cpp b/ondevice-signing/CertUtils.cpp index 8fe0816c..b0b75a6b 100644 --- a/ondevice-signing/CertUtils.cpp +++ b/ondevice-signing/CertUtils.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "CertUtils.h" - #include <android-base/logging.h> #include <android-base/result.h> @@ -23,126 +21,54 @@ #include <openssl/crypto.h> #include <openssl/pkcs7.h> #include <openssl/rsa.h> -#include <openssl/x509.h> #include <openssl/x509v3.h> -#include <optional> +#include <fcntl.h> #include <vector> #include "KeyConstants.h" -// Common properties for all of our certificates. +const char kBasicConstraints[] = "CA:TRUE"; +const char kKeyUsage[] = "critical,keyCertSign,cRLSign,digitalSignature"; +const char kSubjectKeyIdentifier[] = "hash"; constexpr int kCertLifetimeSeconds = 10 * 365 * 24 * 60 * 60; -const char* const kIssuerCountry = "US"; -const char* const kIssuerOrg = "Android"; -using android::base::ErrnoError; -using android::base::Error; using android::base::Result; +// using android::base::ErrnoError; +using android::base::Error; -static Result<bssl::UniquePtr<X509>> loadX509(const std::string& path) { - X509* rawCert; - auto f = fopen(path.c_str(), "re"); - if (f == nullptr) { - return Error() << "Failed to open " << path; - } - if (!d2i_X509_fp(f, &rawCert)) { - fclose(f); - return Error() << "Unable to decode x509 cert at " << path; - } - bssl::UniquePtr<X509> cert(rawCert); +static bool add_ext(X509* cert, int nid, const char* value) { + size_t len = strlen(value) + 1; + std::vector<char> mutableValue(value, value + len); + X509V3_CTX context; - fclose(f); - return cert; -} + X509V3_set_ctx_nodb(&context); -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)); + X509V3_set_ctx(&context, cert, cert, nullptr, nullptr, 0); + X509_EXTENSION* ex = X509V3_EXT_nconf_nid(nullptr, &context, nid, mutableValue.data()); if (!ex) { return false; } - X509_add_ext(cert, ex.get(), -1); + X509_add_ext(cert, ex, -1); + X509_EXTENSION_free(ex); return true; } -static void addNameEntry(X509_NAME* name, const char* field, const char* value) { - X509_NAME_add_entry_by_txt(name, field, MBSTRING_ASC, - reinterpret_cast<const unsigned char*>(value), -1, -1, 0); -} - -static Result<bssl::UniquePtr<RSA>> getRsaFromModulus(const std::vector<uint8_t>& publicKey) { - bssl::UniquePtr<BIGNUM> n(BN_new()); - bssl::UniquePtr<BIGNUM> e(BN_new()); +Result<bssl::UniquePtr<RSA>> getRsa(const std::vector<uint8_t>& publicKey) { bssl::UniquePtr<RSA> rsaPubkey(RSA_new()); - if (!n || !e || !rsaPubkey || !BN_bin2bn(publicKey.data(), publicKey.size(), n.get()) || - !BN_set_word(e.get(), kRsaKeyExponent) || - !RSA_set0_key(rsaPubkey.get(), n.get(), e.get(), /*d=*/nullptr)) { - return Error() << "Failed to create RSA key"; - } - // RSA_set0_key takes ownership of |n| and |e| on success. - (void)n.release(); - (void)e.release(); + rsaPubkey->n = BN_new(); + rsaPubkey->e = BN_new(); - return rsaPubkey; -} + BN_bin2bn(publicKey.data(), publicKey.size(), rsaPubkey->n); + BN_set_word(rsaPubkey->e, kRsaKeyExponent); -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. - auto rsaPubkey = getRsaFromModulus(publicKey); - if (!rsaPubkey.ok()) { - return rsaPubkey.error(); - } - - bssl::UniquePtr<EVP_PKEY> public_key(EVP_PKEY_new()); - if (!EVP_PKEY_assign_RSA(public_key.get(), rsaPubkey->release())) { - return Error() << "Failed to assign key"; - } - 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; + return rsaPubkey; } Result<void> verifySignature(const std::string& message, const std::string& signature, const std::vector<uint8_t>& publicKey) { - auto rsaKey = getRsaFromModulus(publicKey); - if (!rsaKey.ok()) { - return rsaKey.error(); - } + auto rsaKey = getRsa(publicKey); uint8_t hashBuf[SHA256_DIGEST_LENGTH]; SHA256(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(message.c_str())), message.length(), hashBuf); @@ -151,172 +77,101 @@ Result<void> verifySignature(const std::string& message, const std::string& sign (const uint8_t*)signature.c_str(), signature.length(), rsaKey->get()); if (!success) { - return Error() << "Failed to verify signature"; - } - 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"; + return Error() << "Failed to verify signature."; } 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; - +Result<void> createSelfSignedCertificate( + const std::vector<uint8_t>& publicKey, + const std::function<Result<std::string>(const std::string&)>& signFunction, + const std::string& path) { bssl::UniquePtr<X509> x509(X509_new()); if (!x509) { return Error() << "Unable to allocate x509 container"; } X509_set_version(x509.get(), 2); + + ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), 1); 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); - - bssl::UniquePtr<X509_ALGOR> algor(X509_ALGOR_new()); - if (!algor || - !X509_ALGOR_set0(algor.get(), OBJ_nid2obj(NID_sha256WithRSAEncryption), V_ASN1_NULL, - NULL) || - !X509_set1_signature_algo(x509.get(), algor.get())) { - return Error() << "Unable to set x509 signature algorithm"; - } - if (!X509_set_pubkey(x509.get(), publicKey)) { + // "publicKey" corresponds to the raw public key bytes - need to create + // a new RSA key with the correct exponent. + auto rsaPubkey = getRsa(publicKey); + + EVP_PKEY* public_key = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(public_key, rsaPubkey->release()); + + if (!X509_set_pubkey(x509.get(), public_key)) { return Error() << "Unable to set x509 public key"; } - X509_NAME* subjectName = X509_get_subject_name(x509.get()); - if (!subjectName) { + X509_NAME* name = X509_get_subject_name(x509.get()); + if (!name) { return Error() << "Unable to get x509 subject name"; } - 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()); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, + reinterpret_cast<const unsigned char*>("US"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, + reinterpret_cast<const unsigned char*>("Android"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + reinterpret_cast<const unsigned char*>("ODS"), -1, -1, 0); + if (!X509_set_issuer_name(x509.get(), name)) { + 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(x509.get(), NID_basic_constraints, kBasicConstraints); + add_ext(x509.get(), NID_key_usage, kKeyUsage); + add_ext(x509.get(), NID_subject_key_identifier, kSubjectKeyIdentifier); + add_ext(x509.get(), NID_authority_key_identifier, "keyid:always"); - add_ext(&context, x509.get(), NID_basic_constraints, basicConstraints); - add_ext(&context, x509.get(), NID_key_usage, keyUsage); - add_ext(&context, x509.get(), NID_subject_key_identifier, "hash"); - add_ext(&context, x509.get(), NID_authority_key_identifier, "keyid:always"); + X509_ALGOR_set0(x509->cert_info->signature, OBJ_nid2obj(NID_sha256WithRSAEncryption), + V_ASN1_NULL, NULL); + X509_ALGOR_set0(x509->sig_alg, OBJ_nid2obj(NID_sha256WithRSAEncryption), V_ASN1_NULL, NULL); // Get the data to be signed - unsigned char* to_be_signed_buf(nullptr); - size_t to_be_signed_length = i2d_re_X509_tbs(x509.get(), &to_be_signed_buf); + char* to_be_signed_buf(nullptr); + size_t to_be_signed_length = i2d_re_X509_tbs(x509.get(), (unsigned char**)&to_be_signed_buf); - auto signed_data = signFunction( - std::string(reinterpret_cast<const char*>(to_be_signed_buf), to_be_signed_length)); + auto signed_data = signFunction(std::string(to_be_signed_buf, to_be_signed_length)); if (!signed_data.ok()) { return signed_data.error(); } - if (!X509_set1_signature_value(x509.get(), - reinterpret_cast<const uint8_t*>(signed_data->data()), - signed_data->size())) { - return Error() << "Unable to set x509 signature"; - } + // This is the only part that doesn't use boringssl default functions - we manually copy in the + // signature that was provided to us. + x509->signature->data = (unsigned char*)OPENSSL_malloc(signed_data->size()); + memcpy(x509->signature->data, signed_data->c_str(), signed_data->size()); + x509->signature->length = signed_data->size(); + x509->signature->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07); + x509->signature->flags |= ASN1_STRING_FLAG_BITS_LEFT; auto f = fopen(path.c_str(), "wbe"); if (f == nullptr) { - return ErrnoError() << "Failed to open " << path; + return Error() << "Failed to open " << path; } i2d_X509_fp(f, x509.get()); - if (fclose(f) != 0) { - return ErrnoError() << "Failed to close " << path; - } + fclose(f); + EVP_PKEY_free(public_key); 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) { if (pkey == nullptr) { return Error() << "Failed to extract public key from x509 cert"; } - if (EVP_PKEY_id(pkey) != EVP_PKEY_RSA) { + if (EVP_PKEY_type(pkey->type) != EVP_PKEY_RSA) { return Error() << "The public key is not an RSA key"; } - RSA* rsa = EVP_PKEY_get0_RSA(pkey); - auto num_bytes = BN_num_bytes(RSA_get0_n(rsa)); + RSA* rsa = EVP_PKEY_get1_RSA(pkey); + auto num_bytes = BN_num_bytes(rsa->n); std::vector<uint8_t> pubKey(num_bytes); - int res = BN_bn2bin(RSA_get0_n(rsa), pubKey.data()); + int res = BN_bn2bin(rsa->n, pubKey.data()); + RSA_free(rsa); if (!res) { return Error() << "Failed to convert public key to bytes"; @@ -328,14 +183,14 @@ Result<std::vector<uint8_t>> extractPublicKey(EVP_PKEY* pkey) { 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())); + EVP_PKEY* public_key = d2i_PUBKEY(nullptr, &keyDataBytes, keyData.size()); - return extractPublicKey(public_key.get()); + return extractPublicKey(public_key); } -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())); +Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t>& keyData) { + auto keyDataBytes = keyData.data(); + bssl::UniquePtr<X509> decoded_cert(d2i_X509(nullptr, &keyDataBytes, keyData.size())); if (decoded_cert.get() == nullptr) { return Error() << "Failed to decode X509 certificate."; } @@ -345,75 +200,21 @@ Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t> } Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::string& path) { - auto cert = loadX509(path); - if (!cert.ok()) { - return cert.error(); - } - 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(); + X509* cert; + auto f = fopen(path.c_str(), "re"); + if (f == nullptr) { + return Error() << "Failed to open " << path; } - - // 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"; + if (!d2i_X509_fp(f, &cert)) { + fclose(f); + return Error() << "Unable to decode x509 cert at " << path; } - 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; + fclose(f); + return extractPublicKey(X509_get_pubkey(cert)); } -Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signed_digest, - const CertSubject& signer) { +Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signed_digest) { 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; @@ -421,20 +222,19 @@ Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signed_dige 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* name = X509_NAME_new(); + if (!name) { + return Error() << "Unable to get 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); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, + reinterpret_cast<const unsigned char*>("US"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, + reinterpret_cast<const unsigned char*>("Android"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + reinterpret_cast<const unsigned char*>("ODS"), -1, -1, 0); + + BN_set_word(serial, 1); + name_der_len = i2d_X509_NAME(name, &name_der); CBB_init(&out, 1024); if (!CBB_add_asn1(&out, &outer_seq, CBS_ASN1_SEQUENCE) || diff --git a/ondevice-signing/include/CertUtils.h b/ondevice-signing/CertUtils.h index fe703fa0..66dff04f 100644 --- a/ondevice-signing/include/CertUtils.h +++ b/ondevice-signing/CertUtils.h @@ -16,43 +16,13 @@ #pragma once -#include <functional> -#include <string> -#include <vector> - #include <android-base/result.h> -// Information extracted from a certificate. -struct CertInfo { - std::string subjectCn; - std::vector<uint8_t> subjectRsaPublicKey; -}; - -// Subjects of certificates we issue. -struct CertSubject { - const char* commonName; - 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. -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>> createPkcs7(const std::vector<uint8_t>& signedData); android::base::Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t>& x509); @@ -60,13 +30,6 @@ 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/KeystoreHmacKey.cpp b/ondevice-signing/KeystoreHmacKey.cpp index 916cbbcd..a2208ce4 100644 --- a/ondevice-signing/KeystoreHmacKey.cpp +++ b/ondevice-signing/KeystoreHmacKey.cpp @@ -49,14 +49,17 @@ using android::base::Result; using android::base::unique_fd; -static KeyDescriptor getHmacKeyDescriptor(const android::String16& keyAlias, int64_t keyNspace) { +// Keystore boot level that the odsign key uses +static const int kOdsignBootLevel = 30; + +static KeyDescriptor getHmacKeyDescriptor() { // AIDL parcelable objects don't have constructor static KeyDescriptor descriptor; static std::once_flag flag; std::call_once(flag, [&]() { descriptor.domain = Domain::SELINUX; - descriptor.alias = keyAlias + android::String16("-hmac"); - descriptor.nspace = keyNspace; + descriptor.alias = String16("ondevice-signing-hmac"); + descriptor.nspace = 101; // odsign_key }); return descriptor; @@ -103,13 +106,13 @@ Result<void> KeystoreHmacKey::createKey() { KeyParameter boot_level; boot_level.tag = Tag::MAX_BOOT_LEVEL; - boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(mKeyBootLevel); + boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(kOdsignBootLevel); params.push_back(boot_level); KeyMetadata metadata; auto status = mSecurityLevel->generateKey(mDescriptor, {}, params, 0, {}, &metadata); if (!status.isOk()) { - return Error() << "Failed to create new HMAC key: " << status; + return Error() << "Failed to create new HMAC key"; } return {}; @@ -130,7 +133,7 @@ Result<void> KeystoreHmacKey::initialize(sp<IKeystoreService> service, // Make sure this is an early boot key for (const auto& auth : keyEntryResponse.metadata.authorizations) { if (auth.keyParameter.tag == Tag::MAX_BOOT_LEVEL) { - if (auth.keyParameter.value.get<KeyParameterValue::integer>() == mKeyBootLevel) { + if (auth.keyParameter.value.get<KeyParameterValue::integer>() == kOdsignBootLevel) { keyValid = true; break; } @@ -149,9 +152,9 @@ Result<void> KeystoreHmacKey::initialize(sp<IKeystoreService> service, } } -KeystoreHmacKey::KeystoreHmacKey(const android::String16& keyAlias, int64_t keyNspace, - int keyBootLevel) - : mDescriptor(getHmacKeyDescriptor(keyAlias, keyNspace)), mKeyBootLevel(keyBootLevel) {} +KeystoreHmacKey::KeystoreHmacKey() { + mDescriptor = getHmacKeyDescriptor(); +} static std::vector<KeyParameter> getVerifyOpParameters() { std::vector<KeyParameter> opParameters; @@ -206,7 +209,8 @@ Result<std::string> KeystoreHmacKey::sign(const std::string& message) const { auto status = mSecurityLevel->createOperation(mDescriptor, params, false, &opResponse); if (!status.isOk()) { - return Error() << "Failed to create keystore signing operation: " << status; + return Error() << "Failed to create keystore signing operation: " + << status.serviceSpecificErrorCode(); } auto operation = opResponse.iOperation; @@ -236,7 +240,8 @@ Result<void> KeystoreHmacKey::verify(const std::string& message, auto status = mSecurityLevel->createOperation(mDescriptor, params, false, &opResponse); if (!status.isOk()) { - return Error() << "Failed to create keystore verification operation: " << status; + return Error() << "Failed to create keystore verification operation: " + << status.serviceSpecificErrorCode(); } auto operation = opResponse.iOperation; @@ -255,12 +260,3 @@ Result<void> KeystoreHmacKey::verify(const std::string& message, return {}; } - -Result<void> KeystoreHmacKey::deleteKey() const { - auto status = mService->deleteKey(mDescriptor); - if (!status.isOk()) { - return Error() << "Failed to delete HMAC key: " << status; - } - - return {}; -} diff --git a/ondevice-signing/KeystoreHmacKey.h b/ondevice-signing/KeystoreHmacKey.h index 1a815a38..fbad0fda 100644 --- a/ondevice-signing/KeystoreHmacKey.h +++ b/ondevice-signing/KeystoreHmacKey.h @@ -31,19 +31,16 @@ class KeystoreHmacKey { using KeyDescriptor = ::android::system::keystore2::KeyDescriptor; public: - KeystoreHmacKey(const android::String16& keyAlias, int64_t keyNspace, int keyBootLevel); + KeystoreHmacKey(); android::base::Result<void> initialize(android::sp<IKeystoreService> service, android::sp<IKeystoreSecurityLevel> securityLevel); android::base::Result<std::string> sign(const std::string& message) const; android::base::Result<void> verify(const std::string& message, const std::string& signature) const; - android::base::Result<void> deleteKey() const; private: android::base::Result<void> createKey(); KeyDescriptor mDescriptor; android::sp<IKeystoreService> mService; android::sp<IKeystoreSecurityLevel> mSecurityLevel; - - int mKeyBootLevel; }; diff --git a/ondevice-signing/KeystoreKey.cpp b/ondevice-signing/KeystoreKey.cpp index 6ce65d65..0951d929 100644 --- a/ondevice-signing/KeystoreKey.cpp +++ b/ondevice-signing/KeystoreKey.cpp @@ -50,24 +50,27 @@ using android::system::keystore2::KeyEntryResponse; using android::base::Error; using android::base::Result; -static KeyDescriptor getKeyDescriptor(const android::String16& keyAlias, int64_t keyNspace) { +// Keystore boot level that the odsign key uses +static const int kOdsignBootLevel = 30; + +const std::string kPublicKeySignature = "/data/misc/odsign/publickey.signature"; + +static KeyDescriptor getKeyDescriptor() { // AIDL parcelable objects don't have constructor static KeyDescriptor descriptor; static std::once_flag flag; std::call_once(flag, [&]() { descriptor.domain = Domain::SELINUX; - descriptor.alias = keyAlias; - descriptor.nspace = keyNspace; + descriptor.alias = String16("ondevice-signing"); + descriptor.nspace = 101; // odsign_key }); return descriptor; } -KeystoreKey::KeystoreKey(std::string signedPubKeyPath, const android::String16& keyAlias, - int64_t keyNspace, int keyBootLevel) - : mDescriptor(getKeyDescriptor(keyAlias, keyNspace)), - mHmacKey(keyAlias, keyNspace, keyBootLevel), mSignedPubKeyPath(std::move(signedPubKeyPath)), - mKeyBootLevel(keyBootLevel) {} +KeystoreKey::KeystoreKey() { + mDescriptor = getKeyDescriptor(); +} Result<std::vector<uint8_t>> KeystoreKey::createKey() { std::vector<KeyParameter> params; @@ -110,13 +113,13 @@ Result<std::vector<uint8_t>> KeystoreKey::createKey() { KeyParameter boot_level; boot_level.tag = Tag::MAX_BOOT_LEVEL; - boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(mKeyBootLevel); + boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(kOdsignBootLevel); params.push_back(boot_level); KeyMetadata metadata; auto status = mSecurityLevel->generateKey(mDescriptor, {}, params, 0, {}, &metadata); if (!status.isOk()) { - return Error() << "Failed to create new key: " << status; + return Error() << "Failed to create new key"; } // Extract the public key from the certificate, HMAC it and store the signature @@ -134,7 +137,7 @@ Result<std::vector<uint8_t>> KeystoreKey::createKey() { return Error() << "Failed to sign public key."; } - if (!android::base::WriteStringToFile(*signature, mSignedPubKeyPath)) { + if (!android::base::WriteStringToFile(*signature, kPublicKeySignature)) { return Error() << "Can't write public key signature."; } @@ -169,13 +172,11 @@ bool KeystoreKey::initialize() { auto key = getOrCreateKey(); if (!key.ok()) { - // Delete the HMAC, just in case signing failed, and we could recover by recreating it. - mHmacKey.deleteKey(); LOG(ERROR) << key.error().message(); return false; } mPublicKey = *key; - LOG(INFO) << "Initialized Keystore key."; + LOG(ERROR) << "Initialized Keystore key."; return true; } @@ -203,7 +204,7 @@ Result<std::vector<uint8_t>> KeystoreKey::verifyExistingKey() { bool foundBootLevel = false; for (const auto& auth : keyEntryResponse.metadata.authorizations) { if (auth.keyParameter.tag == Tag::MAX_BOOT_LEVEL) { - if (auth.keyParameter.value.get<KeyParameterValue::integer>() == mKeyBootLevel) { + if (auth.keyParameter.value.get<KeyParameterValue::integer>() == kOdsignBootLevel) { foundBootLevel = true; break; } @@ -229,7 +230,7 @@ Result<std::vector<uint8_t>> KeystoreKey::verifyExistingKey() { std::string publicKeyString = {publicKey->begin(), publicKey->end()}; std::string signature; - if (!android::base::ReadFileToString(mSignedPubKeyPath, &signature)) { + if (!android::base::ReadFileToString(kPublicKeySignature, &signature)) { return Error() << "Can't find signature for public key."; } @@ -253,15 +254,13 @@ Result<std::vector<uint8_t>> KeystoreKey::getOrCreateKey() { return *existingKey; } -Result<SigningKey*> KeystoreKey::getInstance(const std::string& signedPubKeyPath, - const android::String16& keyAlias, int64_t keyNspace, - int keyBootLevel) { - auto keystoreKey = new KeystoreKey(signedPubKeyPath, keyAlias, keyNspace, keyBootLevel); +Result<SigningKey*> KeystoreKey::getInstance() { + static KeystoreKey keystoreKey; - if (!keystoreKey->initialize()) { + if (!keystoreKey.initialize()) { return Error() << "Failed to initialize keystore key."; } else { - return keystoreKey; + return &keystoreKey; } } @@ -298,13 +297,19 @@ Result<std::string> KeystoreKey::sign(const std::string& message) const { auto status = mSecurityLevel->createOperation(mDescriptor, opParameters, false, &opResponse); if (!status.isOk()) { - return Error() << "Failed to create keystore signing operation: " << status; + return Error() << "Failed to create keystore signing operation: " + << status.serviceSpecificErrorCode(); } auto operation = opResponse.iOperation; - std::optional<std::vector<uint8_t>> input{std::in_place, message.begin(), message.end()}; + std::optional<std::vector<uint8_t>> out; + status = operation->update({message.begin(), message.end()}, &out); + if (!status.isOk()) { + return Error() << "Failed to call keystore update operation."; + } + std::optional<std::vector<uint8_t>> signature; - status = operation->finish(input, {}, &signature); + status = operation->finish({}, {}, &signature); if (!status.isOk()) { return Error() << "Failed to call keystore finish operation."; } diff --git a/ondevice-signing/KeystoreKey.h b/ondevice-signing/KeystoreKey.h index 3c9a0ab0..1257cbbe 100644 --- a/ondevice-signing/KeystoreKey.h +++ b/ondevice-signing/KeystoreKey.h @@ -20,6 +20,7 @@ #include <android-base/macros.h> #include <android-base/result.h> +#include <android-base/unique_fd.h> #include <utils/StrongPointer.h> @@ -36,16 +37,13 @@ class KeystoreKey : public SigningKey { public: virtual ~KeystoreKey(){}; - static android::base::Result<SigningKey*> getInstance(const std::string& signedPubKeyPath, - const android::String16& keyAlias, - int64_t KeyNspace, int keyBootLevel); + static android::base::Result<SigningKey*> getInstance(); virtual android::base::Result<std::string> sign(const std::string& message) const; virtual android::base::Result<std::vector<uint8_t>> getPublicKey() const; private: - KeystoreKey(std::string signedPubKeyPath, const android::String16& keyAlias, int64_t keyNspace, - int keyBootLevel); + KeystoreKey(); bool initialize(); android::base::Result<std::vector<uint8_t>> verifyExistingKey(); android::base::Result<std::vector<uint8_t>> createKey(); @@ -56,7 +54,4 @@ class KeystoreKey : public SigningKey { android::sp<IKeystoreService> mService; android::sp<IKeystoreSecurityLevel> mSecurityLevel; std::vector<uint8_t> mPublicKey; - - std::string mSignedPubKeyPath; - int mKeyBootLevel; }; diff --git a/ondevice-signing/include/SigningKey.h b/ondevice-signing/SigningKey.h index 89294fc9..89294fc9 100644 --- a/ondevice-signing/include/SigningKey.h +++ b/ondevice-signing/SigningKey.h diff --git a/ondevice-signing/StatsReporter.cpp b/ondevice-signing/StatsReporter.cpp deleted file mode 100644 index 65e645a3..00000000 --- a/ondevice-signing/StatsReporter.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "StatsReporter.h" -#include <android-base/logging.h> -#include <stdlib.h> -#include <string> -#include <sys/stat.h> - -// Keep these constant in sync with COMPOS_METRIC_NAME & METRICS_FILE in OdsignStatsLogger.java. -constexpr const char* kOdsignMetricsFile = "/data/misc/odsign/metrics/odsign-metrics.txt"; -constexpr const char* kComposMetricName = "comp_os_artifacts_check_record"; - -StatsReporter::~StatsReporter() { - if (comp_os_artifacts_check_record_ == nullptr) { - LOG(INFO) << "Metrics report is empty"; - - // Remove the metrics file if any old version of the file already exists - if (std::filesystem::remove(kOdsignMetricsFile) != 0 && - !((errno = ENOENT) || errno == ENOTDIR)) { - PLOG(ERROR) << "Could not remove already present file"; - } - return; - } - - std::ofstream odsign_metrics_file_; - odsign_metrics_file_.open(kOdsignMetricsFile, std::ios::trunc); - if (!odsign_metrics_file_) { - PLOG(ERROR) << "Could not open file: " << kOdsignMetricsFile; - return; - } - - odsign_metrics_file_ << kComposMetricName << ' '; - odsign_metrics_file_ << comp_os_artifacts_check_record_->current_artifacts_ok << ' '; - odsign_metrics_file_ << comp_os_artifacts_check_record_->comp_os_pending_artifacts_exists - << ' '; - odsign_metrics_file_ << comp_os_artifacts_check_record_->use_comp_os_generated_artifacts - << '\n'; - if (chmod(kOdsignMetricsFile, 0644) != 0) { - PLOG(ERROR) << "Could not set correct file permissions for " << kOdsignMetricsFile; - return; - } - odsign_metrics_file_.close(); - if (!odsign_metrics_file_) { - PLOG(ERROR) << "Failed to close the file"; - } -} - -StatsReporter::CompOsArtifactsCheckRecord* StatsReporter::GetComposArtifactsCheckRecord() { - if (comp_os_artifacts_check_record_ == nullptr) { - comp_os_artifacts_check_record_ = std::make_unique<CompOsArtifactsCheckRecord>(); - } - return comp_os_artifacts_check_record_.get(); -} diff --git a/ondevice-signing/StatsReporter.h b/ondevice-signing/StatsReporter.h deleted file mode 100644 index 2682b963..00000000 --- a/ondevice-signing/StatsReporter.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <fstream> - -// Class to store CompOsArtifactsCheck related metrics. -// These are flushed to a file kOdsignMetricsFile and consumed by -// System Server (in class OdsignStatsLogger) & sent to statsd. -class StatsReporter { - public: - // Keep sync with EarlyBootCompOsArtifactsCheckReported - // definition in proto_logging/stats/atoms.proto. - struct CompOsArtifactsCheckRecord { - bool current_artifacts_ok = false; - bool comp_os_pending_artifacts_exists = false; - bool use_comp_os_generated_artifacts = false; - }; - - // The report is flushed (from buffer) into a file by the destructor. - ~StatsReporter(); - - // Get pointer to comp_os_artifacts_check_record, caller can then modify it. - // Note: pointer remains valid for the lifetime of this StatsReporter. - CompOsArtifactsCheckRecord* GetComposArtifactsCheckRecord(); - - private: - // Temporary buffer which stores the metrics. - std::unique_ptr<CompOsArtifactsCheckRecord> comp_os_artifacts_check_record_; -}; diff --git a/ondevice-signing/TEST_MAPPING b/ondevice-signing/TEST_MAPPING index 4b2c8c6b..03b9b95c 100644 --- a/ondevice-signing/TEST_MAPPING +++ b/ondevice-signing/TEST_MAPPING @@ -1,7 +1,7 @@ { "presubmit": [ { - "name": "libsigningutils_test" + "name": "odsign_e2e_tests" } ] } diff --git a/ondevice-signing/VerityUtils.cpp b/ondevice-signing/VerityUtils.cpp index cd9a1ea6..0bb3979d 100644 --- a/ondevice-signing/VerityUtils.cpp +++ b/ondevice-signing/VerityUtils.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include <charconv> #include <filesystem> #include <map> #include <span> @@ -26,10 +25,8 @@ #include <sys/types.h> #include <sys/wait.h> -#include <android-base/file.h> #include <android-base/logging.h> #include <android-base/unique_fd.h> -#include <asm/byteorder.h> #include <libfsverity.h> #include <linux/fsverity.h> @@ -44,13 +41,23 @@ 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() { - return access(kFsVerityProcPath, F_OK) == 0; -} +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define cpu_to_le16(v) ((__force __le16)(uint16_t)(v)) +#define le16_to_cpu(v) ((__force uint16_t)(__le16)(v)) +#else +#define cpu_to_le16(v) ((__force __le16)__builtin_bswap16(v)) +#define le16_to_cpu(v) (__builtin_bswap16((__force uint16_t)(v))) +#endif + +struct fsverity_signed_digest { + char magic[8]; /* must be "FSVerity" */ + __le16 digest_algorithm; + __le16 digest_size; + __u8 digest[]; +}; -static std::string toHex(std::span<const uint8_t> data) { +static std::string toHex(std::span<uint8_t> data) { std::stringstream ss; for (auto it = data.begin(); it != data.end(); ++it) { ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it); @@ -58,34 +65,22 @@ static std::string toHex(std::span<const uint8_t> data) { return ss.str(); } -static std::vector<uint8_t> fromHex(std::string_view hex) { - if (hex.size() % 2 != 0) { - return {}; - } - std::vector<uint8_t> result; - result.reserve(hex.size() / 2); - for (size_t i = 0; i < hex.size(); i += 2) { - uint8_t byte; - auto conversion_result = std::from_chars(&hex[i], &hex[i + 2], byte, 16); - if (conversion_result.ptr != &hex[i + 2] || conversion_result.ec != std::errc()) { - return {}; - } - result.push_back(byte); - } - return result; -} - static int read_callback(void* file, void* buf, size_t count) { int* fd = (int*)file; if (TEMP_FAILURE_RETRY(read(*fd, buf, count)) < 0) return errno ? -errno : -EIO; return 0; } -static Result<std::vector<uint8_t>> createDigest(int fd) { +Result<std::vector<uint8_t>> createDigest(const std::string& path) { struct stat filestat; - int ret = fstat(fd, &filestat); + unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd < 0) { + return ErrnoError() << "Failed to open " << path; + } + + int ret = stat(path.c_str(), &filestat); if (ret < 0) { - return ErrnoError() << "Failed to fstat"; + return ErrnoError() << "Failed to stat " << path; } struct libfsverity_merkle_tree_params params = { .version = 1, @@ -97,7 +92,7 @@ static Result<std::vector<uint8_t>> createDigest(int fd) { struct libfsverity_digest* digest; ret = libfsverity_compute_digest(&fd, &read_callback, ¶ms, &digest); if (ret < 0) { - return ErrnoError() << "Failed to compute fs-verity digest"; + return ErrnoError() << "Failed to compute fs-verity digest for " << path; } int expected_digest_size = libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA256); if (digest->digest_size != expected_digest_size) { @@ -109,14 +104,6 @@ static Result<std::vector<uint8_t>> createDigest(int fd) { return digestVector; } -Result<std::vector<uint8_t>> createDigest(const std::string& path) { - unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); - if (!fd.ok()) { - return ErrnoError() << "Unable to open"; - } - return createDigest(fd.get()); -} - namespace { template <typename T> struct DeleteAsPODArray { void operator()(T* x) { @@ -126,19 +113,6 @@ template <typename T> struct DeleteAsPODArray { } } }; - -static Result<void> measureFsVerity(int fd, const fsverity_digest* digest) { - if (ioctl(fd, FS_IOC_MEASURE_VERITY, digest) != 0) { - if (errno == ENODATA) { - return Error() << "File is not in fs-verity"; - } else { - return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY"; - } - } - - return {}; -} - } // namespace template <typename T> using trailing_unique_ptr = std::unique_ptr<T, DeleteAsPODArray<T>>; @@ -152,11 +126,11 @@ static trailing_unique_ptr<T> makeUniqueWithTrailingData(size_t trailing_data_si static Result<std::vector<uint8_t>> signDigest(const SigningKey& key, const std::vector<uint8_t>& digest) { - auto d = makeUniqueWithTrailingData<fsverity_formatted_digest>(digest.size()); + auto d = makeUniqueWithTrailingData<fsverity_signed_digest>(digest.size()); memcpy(d->magic, "FSVerity", 8); - d->digest_algorithm = __cpu_to_le16(FS_VERITY_HASH_ALG_SHA256); - d->digest_size = __cpu_to_le16(digest.size()); + d->digest_algorithm = cpu_to_le16(FS_VERITY_HASH_ALG_SHA256); + d->digest_size = cpu_to_le16(digest.size()); memcpy(d->digest, digest.data(), digest.size()); auto signed_digest = key.sign(std::string((char*)d.get(), sizeof(*d) + digest.size())); @@ -167,27 +141,10 @@ static Result<std::vector<uint8_t>> signDigest(const SigningKey& key, return std::vector<uint8_t>(signed_digest->begin(), signed_digest->end()); } -static Result<void> enableFsVerity(int fd, std::span<uint8_t> pkcs7) { - struct fsverity_enable_arg arg = {.version = 1}; - - arg.sig_ptr = reinterpret_cast<uint64_t>(pkcs7.data()); - arg.sig_size = pkcs7.size(); - arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; - arg.block_size = 4096; - - int ret = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg); - - if (ret != 0) { - return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY"; - } - - return {}; -} - -Result<std::string> enableFsVerity(int fd, const SigningKey& key) { - auto digest = createDigest(fd); +Result<std::string> enableFsVerity(const std::string& path, const SigningKey& key) { + auto digest = createDigest(path); if (!digest.ok()) { - return Error() << digest.error(); + return digest.error(); } auto signed_digest = signDigest(key, digest.value()); @@ -195,102 +152,75 @@ Result<std::string> enableFsVerity(int fd, const SigningKey& key) { return signed_digest.error(); } - auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject); - if (!pkcs7_data.ok()) { - return pkcs7_data.error(); - } - - auto enabled = enableFsVerity(fd, pkcs7_data.value()); - if (!enabled.ok()) { - return Error() << enabled.error(); - } + auto pkcs7_data = createPkcs7(signed_digest.value()); - // Return the root hash as a hex string - return toHex(digest.value()); -} - -static Result<std::string> isFileInVerity(int fd) { - auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE); - d->digest_size = FS_VERITY_MAX_DIGEST_SIZE; - - const auto& status = measureFsVerity(fd, d.get()); - if (!status.ok()) { - return status.error(); - } + struct fsverity_enable_arg arg = {.version = 1}; - return toHex({&d->digest[0], &d->digest[d->digest_size]}); -} + arg.sig_ptr = (uint64_t)pkcs7_data->data(); + arg.sig_size = pkcs7_data->size(); + arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; + arg.block_size = 4096; -static Result<std::string> isFileInVerity(const std::string& path) { unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); - if (!fd.ok()) { - return ErrnoError() << "Failed to open " << path; - } + int ret = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg); - auto digest = isFileInVerity(fd.get()); - if (!digest.ok()) { - return Error() << digest.error() << ": " << path; + if (ret != 0) { + return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY on " << path; } - return digest; + // Return the root hash as a hex string + return toHex(digest.value()); } Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path, const SigningKey& key) { std::map<std::string, std::string> digests; - std::error_code ec; + auto it = std::filesystem::recursive_directory_iterator(path, ec); - for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) { + auto end = std::filesystem::recursive_directory_iterator(); + + while (!ec && it != end) { if (it->is_regular_file()) { - unique_fd fd(TEMP_FAILURE_RETRY(open(it->path().c_str(), O_RDONLY | O_CLOEXEC))); - if (!fd.ok()) { - return ErrnoError() << "Failed to open " << path; - } - auto digest = isFileInVerity(fd); - if (!digest.ok()) { - LOG(INFO) << "Adding " << it->path() << " to fs-verity..."; - auto result = enableFsVerity(fd, key); - if (!result.ok()) { - return result.error(); - } - digests[it->path()] = *result; - } else { - LOG(INFO) << it->path() << " was already in fs-verity."; - digests[it->path()] = *digest; + LOG(INFO) << "Adding " << it->path() << " to fs-verity..."; + auto result = enableFsVerity(it->path(), key); + if (!result.ok()) { + return result.error(); } + digests[it->path()] = *result; } + ++it; } if (ec) { - return Error() << "Failed to iterate " << path << ": " << ec.message(); + return Error() << "Failed to iterate " << path << ": " << ec; } return digests; } -Result<void> enableFsVerity(const std::string& path, const std::string& signature_path) { +Result<std::string> isFileInVerity(const std::string& path) { + unsigned int flags; + unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); - if (!fd.ok()) { - return Error() << "Can't open " << path; + if (fd < 0) { + return ErrnoError() << "Failed to open " << path; } - std::string signature; - android::base::ReadFileToString(signature_path, &signature); - std::vector<uint8_t> span = std::vector<uint8_t>(signature.begin(), signature.end()); - - const auto& enable = enableFsVerity(fd.get(), span); - if (!enable.ok()) { - return enable.error(); + int ret = ioctl(fd, FS_IOC_GETFLAGS, &flags); + if (ret < 0) { + return ErrnoError() << "Failed to FS_IOC_GETFLAGS for " << path; } - - auto digest = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE); - digest->digest_size = FS_VERITY_MAX_DIGEST_SIZE; - const auto& measure = measureFsVerity(fd.get(), digest.get()); - if (!measure.ok()) { - return measure.error(); + if (!(flags & FS_VERITY_FL)) { + return Error() << "File is not in fs-verity: " << path; } - return {}; + auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE); + d->digest_size = FS_VERITY_MAX_DIGEST_SIZE; + ret = ioctl(fd, FS_IOC_MEASURE_VERITY, d.get()); + if (ret < 0) { + return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY for " << path; + } + return toHex({&d->digest[0], &d->digest[d->digest_size]}); } Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path) { @@ -324,90 +254,10 @@ Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::str return digests; } -Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path, - const std::map<std::string, std::string>& digests, - const SigningKey& signing_key) { - std::error_code ec; - size_t verified_count = 0; - auto it = std::filesystem::recursive_directory_iterator(directory_path, ec); - for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) { - auto& path = it->path(); - if (it->is_regular_file()) { - auto entry = digests.find(path); - if (entry == digests.end()) { - return Error() << "Unexpected file found: " << path; - } - auto& compos_digest = entry->second; - - unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); - if (!fd.ok()) { - return ErrnoError() << "Can't open " << path; - } - - auto verity_digest = isFileInVerity(fd); - if (verity_digest.ok()) { - // The file is already in fs-verity. We need to make sure it was signed - // by CompOS, so we just check that it has the digest we expect. - if (verity_digest.value() == compos_digest) { - ++verified_count; - } else { - return Error() << "fs-verity digest does not match CompOS digest: " << path; - } - } else { - // Not in fs-verity yet. We know the digest CompOS provided; If - // it's not the correct digest for the file then enabling - // fs-verity will fail, so we don't need to check it explicitly - // ourselves. Otherwise we should be good. - LOG(INFO) << "Adding " << path << " to fs-verity..."; - - auto digest_bytes = fromHex(compos_digest); - if (digest_bytes.empty()) { - return Error() << "Invalid digest " << compos_digest; - } - auto signed_digest = signDigest(signing_key, digest_bytes); - if (!signed_digest.ok()) { - return signed_digest.error(); - } - - auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject); - if (!pkcs7_data.ok()) { - return pkcs7_data.error(); - } - - auto enabled = enableFsVerity(fd, pkcs7_data.value()); - if (!enabled.ok()) { - return Error() << enabled.error(); - } - ++verified_count; - } - } else if (it->is_directory()) { - // These are fine to ignore - } else if (it->is_symlink()) { - return Error() << "Rejecting artifacts, symlink at " << path; - } else { - return Error() << "Rejecting artifacts, unexpected file type for " << path; - } - } - if (ec) { - return Error() << "Failed to iterate " << directory_path << ": " << ec.message(); - } - - // Make sure all the files we expected have been seen - if (verified_count != digests.size()) { - return Error() << "Verified " << verified_count << " files, but expected " - << digests.size(); - } - - return {}; -} - -Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName) { - const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", keyName}; +Result<void> addCertToFsVerityKeyring(const std::string& path) { + const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", "fsv_ods"}; 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); @@ -416,7 +266,7 @@ Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyNa char* argv_child[argc + 1]; memcpy(argv_child, argv, argc * sizeof(char*)); argv_child[argc] = nullptr; - execvp(argv_child[0], argv_child); + execvp(argv_child[0], const_cast<char**>(argv_child)); PLOG(ERROR) << "exec in ForkExecvp"; _exit(EXIT_FAILURE); } else { @@ -432,8 +282,10 @@ Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyNa if (!WIFEXITED(status)) { return Error() << kFsVerityInitPath << ": abnormal process exit"; } - if (WEXITSTATUS(status) != 0) { - return Error() << kFsVerityInitPath << " exited with " << WEXITSTATUS(status); + if (WEXITSTATUS(status)) { + if (status != 0) { + return Error() << kFsVerityInitPath << " exited with " << status; + } } return {}; diff --git a/ondevice-signing/include/VerityUtils.h b/ondevice-signing/VerityUtils.h index e6e49c7d..84af3196 100644 --- a/ondevice-signing/include/VerityUtils.h +++ b/ondevice-signing/VerityUtils.h @@ -18,29 +18,11 @@ #include <android-base/result.h> -#include <map> -#include <string> -#include <vector> - #include "SigningKey.h" -android::base::Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName); +android::base::Result<void> addCertToFsVerityKeyring(const std::string& path); android::base::Result<std::vector<uint8_t>> createDigest(const std::string& path); -android::base::Result<std::string> enableFsVerity(int fd, const SigningKey& key); -bool SupportsFsVerity(); android::base::Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path); - -// Note that this function will skip files that are already in fs-verity, and -// for those files it will return the existing digest. android::base::Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path, const SigningKey& key); - -// Enable verity on the provided file, using the given PKCS7 signature. -android::base::Result<void> enableFsVerity(const std::string& path, - const std::string& signature_path); - -android::base::Result<void> -verifyAllFilesUsingCompOs(const std::string& directory_path, - const std::map<std::string, std::string>& digests, - const SigningKey& signing_key); diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp index c45e3085..425776bd 100644 --- a/ondevice-signing/odsign_main.cpp +++ b/ondevice-signing/odsign_main.cpp @@ -33,7 +33,6 @@ #include "CertUtils.h" #include "KeystoreKey.h" -#include "StatsReporter.h" #include "VerityUtils.h" #include "odsign_info.pb.h" @@ -45,57 +44,29 @@ using android::base::SetProperty; using OdsignInfo = ::odsign::proto::OdsignInfo; -// Keystore boot level that the odsign key uses -const int kKeyBootLevel = 30; -const std::string kPublicKeySignature = "/data/misc/odsign/publickey.signature"; -const android::String16 kKeyAlias{"ondevice-signing"}; -constexpr int kKeyNspace = 101; // odsign_key - +const std::string kSigningKeyBlob = "/data/misc/odsign/key.blob"; const std::string kSigningKeyCert = "/data/misc/odsign/key.cert"; const std::string kOdsignInfo = "/data/misc/odsign/odsign.info"; const std::string kOdsignInfoSignature = "/data/misc/odsign/odsign.info.signature"; const std::string kArtArtifactsDir = "/data/misc/apexdata/com.android.art/dalvik-cache"; -constexpr const char* kOdrefreshPath = "/apex/com.android.art/bin/odrefresh"; -constexpr const char* kCompOsVerifyPath = "/apex/com.android.compos/bin/compos_verify"; - -constexpr bool kForceCompilation = false; -constexpr bool kUseCompOs = true; - -const std::string kCompOsPendingArtifactsDir = "/data/misc/apexdata/com.android.art/compos-pending"; -const std::string kCompOsInfo = kArtArtifactsDir + "/compos.info"; -const std::string kCompOsInfoSignature = kCompOsInfo + ".signature"; - -constexpr const char* kCompOsPendingInfoPath = - "/data/misc/apexdata/com.android.art/compos-pending/compos.info"; -constexpr const char* kCompOsPendingInfoSignaturePath = - "/data/misc/apexdata/com.android.art/compos-pending/compos.info.signature"; +static const char* kOdrefreshPath = "/apex/com.android.art/bin/odrefresh"; -constexpr const char* kOdsignVerificationDoneProp = "odsign.verification.done"; -constexpr const char* kOdsignKeyDoneProp = "odsign.key.done"; +static const char* kFsVerityProcPath = "/proc/sys/fs/verity"; -constexpr const char* kOdsignVerificationStatusProp = "odsign.verification.success"; -constexpr const char* kOdsignVerificationStatusValid = "1"; -constexpr const char* kOdsignVerificationStatusError = "0"; +static const bool kForceCompilation = false; -constexpr const char* kStopServiceProp = "ctl.stop"; +static const char* kOdsignVerificationDoneProp = "odsign.verification.done"; +static const char* kOdsignKeyDoneProp = "odsign.key.done"; -enum class CompOsInstance { kCurrent, kPending }; +static const char* kOdsignVerificationStatusProp = "odsign.verification.success"; +static const char* kOdsignVerificationStatusValid = "1"; +static const char* kOdsignVerificationStatusError = "0"; -namespace { - -bool rename(const std::string& from, const std::string& to) { - std::error_code ec; - std::filesystem::rename(from, to, ec); - if (ec) { - LOG(ERROR) << "Can't rename " << from << " to " << to << ": " << ec.message(); - return false; - } - return true; -} +static const char* kStopServiceProp = "ctl.stop"; -int removeDirectory(const std::string& directory) { +static int removeDirectory(const std::string& directory) { std::error_code ec; auto num_removed = std::filesystem::remove_all(directory, ec); if (ec) { @@ -109,46 +80,13 @@ int removeDirectory(const std::string& directory) { } } -bool directoryHasContent(const std::string& directory) { - std::error_code ec; - return std::filesystem::is_directory(directory, ec) && - !std::filesystem::is_empty(directory, ec); -} - -art::odrefresh::ExitCode compileArtifacts(bool force) { - const char* const argv[] = {kOdrefreshPath, force ? "--force-compile" : "--compile"}; - const int exit_code = - logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr); - return static_cast<art::odrefresh::ExitCode>(exit_code); -} - -art::odrefresh::ExitCode checkArtifacts() { - const char* const argv[] = {kOdrefreshPath, "--check"}; - const int exit_code = - logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr); - return static_cast<art::odrefresh::ExitCode>(exit_code); -} - -std::string toHex(const std::vector<uint8_t>& digest) { - std::stringstream ss; - for (auto it = digest.begin(); it != digest.end(); ++it) { - ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it); - } - return ss.str(); -} - -bool compOsPresent() { - // We must have the CompOS APEX - return access(kCompOsVerifyPath, X_OK) == 0; -} - -Result<void> verifyExistingRootCert(const SigningKey& key) { +Result<void> verifyExistingCert(const SigningKey& key) { if (access(kSigningKeyCert.c_str(), F_OK) < 0) { return ErrnoError() << "Key certificate not found: " << kSigningKeyCert; } auto trustedPublicKey = key.getPublicKey(); if (!trustedPublicKey.ok()) { - return Error() << "Failed to retrieve signing public key: " << trustedPublicKey.error(); + return Error() << "Failed to retrieve signing public key."; } auto publicKeyFromExistingCert = extractPublicKeyFromX509(kSigningKeyCert); @@ -160,12 +98,11 @@ Result<void> verifyExistingRootCert(const SigningKey& key) { << " does not match signing public key."; } - // At this point, we know the cert is for our key; it's unimportant whether it's - // actually self-signed. + // At this point, we know the cert matches return {}; } -Result<void> createX509RootCert(const SigningKey& key, const std::string& outPath) { +Result<void> createX509Cert(const SigningKey& key, const std::string& outPath) { auto publicKey = key.getPublicKey(); if (!publicKey.ok()) { @@ -173,7 +110,30 @@ Result<void> createX509RootCert(const SigningKey& key, const std::string& outPat } auto keySignFunction = [&](const std::string& to_be_signed) { return key.sign(to_be_signed); }; - return createSelfSignedCertificate(*publicKey, keySignFunction, outPath); + createSelfSignedCertificate(*publicKey, keySignFunction, outPath); + return {}; +} + +art::odrefresh::ExitCode checkArtifacts() { + const char* const argv[] = {kOdrefreshPath, "--check"}; + const int exit_code = + logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr); + return static_cast<art::odrefresh::ExitCode>(exit_code); +} + +art::odrefresh::ExitCode compileArtifacts(bool force) { + const char* const argv[] = {kOdrefreshPath, force ? "--force-compile" : "--compile"}; + const int exit_code = + logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr); + return static_cast<art::odrefresh::ExitCode>(exit_code); +} + +static std::string toHex(const std::vector<uint8_t>& digest) { + std::stringstream ss; + for (auto it = digest.begin(); it != digest.end(); ++it) { + ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it); + } + return ss.str(); } Result<std::map<std::string, std::string>> computeDigests(const std::string& path) { @@ -187,8 +147,7 @@ Result<std::map<std::string, std::string>> computeDigests(const std::string& pat if (it->is_regular_file()) { auto digest = createDigest(it->path()); if (!digest.ok()) { - return Error() << "Failed to compute digest for " << it->path() << ": " - << digest.error(); + return Error() << "Failed to compute digest for " << it->path(); } digests[it->path()] = toHex(*digest); } @@ -322,8 +281,22 @@ Result<void> persistDigests(const std::map<std::string, std::string>& digests, return {}; } -Result<void> verifyArtifactsIntegrity(const std::map<std::string, std::string>& trusted_digests, - bool supportsFsVerity) { +static int removeArtifacts() { + std::error_code ec; + auto num_removed = std::filesystem::remove_all(kArtArtifactsDir, ec); + if (ec) { + LOG(ERROR) << "Can't remove " << kArtArtifactsDir << ": " << ec.message(); + return 0; + } else { + if (num_removed > 0) { + LOG(INFO) << "Removed " << num_removed << " entries from " << kArtArtifactsDir; + } + return num_removed; + } +} + +static Result<void> verifyArtifacts(const std::map<std::string, std::string>& trusted_digests, + bool supportsFsVerity) { Result<void> integrityStatus; if (supportsFsVerity) { @@ -332,152 +305,16 @@ Result<void> verifyArtifactsIntegrity(const std::map<std::string, std::string>& integrityStatus = verifyIntegrityNoFsVerity(trusted_digests); } if (!integrityStatus.ok()) { - return integrityStatus.error(); + return Error() << integrityStatus.error().message(); } return {}; } -Result<OdsignInfo> getComposInfo() { - const char* const argv[] = {kCompOsVerifyPath, "--instance", "current"}; - int result = - logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr); - if (result != 0) { - return Error() << kCompOsVerifyPath << " returned " << result; - } - - std::string compos_info_str; - if (!android::base::ReadFileToString(kCompOsInfo, &compos_info_str)) { - return ErrnoError() << "Failed to read " << kCompOsInfo; - } - - // Delete the files - we don't need them any more, and they'd confuse - // artifact verification - if (unlink(kCompOsInfo.c_str()) != 0 || unlink(kCompOsInfoSignature.c_str()) != 0) { - return ErrnoError() << "Unable to delete CompOS info/signature file"; - } - - OdsignInfo compos_info; - if (!compos_info.ParseFromString(compos_info_str)) { - return Error() << "Failed to parse " << kCompOsInfo; - } - - LOG(INFO) << "Loaded " << kCompOsInfo; - return compos_info; -} - -art::odrefresh::ExitCode CheckCompOsPendingArtifacts(const SigningKey& signing_key, - bool* digests_verified, - StatsReporter* stats_reporter) { - StatsReporter::CompOsArtifactsCheckRecord* compos_check_record = - stats_reporter->GetComposArtifactsCheckRecord(); - - if (!directoryHasContent(kCompOsPendingArtifactsDir)) { - // No pending CompOS artifacts, all that matters is the current ones. - art::odrefresh::ExitCode odrefresh_status = checkArtifacts(); - if (odrefresh_status == art::odrefresh::ExitCode::kOkay) { - compos_check_record->current_artifacts_ok = true; - } - return odrefresh_status; - } - - compos_check_record->comp_os_pending_artifacts_exists = true; - - // CompOS has generated some artifacts that may, or may not, match the - // current state. But if there are already valid artifacts present the - // CompOS ones are redundant. - art::odrefresh::ExitCode odrefresh_status = checkArtifacts(); - if (odrefresh_status != art::odrefresh::ExitCode::kCompilationRequired) { - if (odrefresh_status == art::odrefresh::ExitCode::kOkay) { - compos_check_record->current_artifacts_ok = true; - LOG(INFO) << "Current artifacts are OK, deleting pending artifacts"; - removeDirectory(kCompOsPendingArtifactsDir); - } - return odrefresh_status; - } - - // No useful current artifacts, lets see if the CompOS ones are ok - if (access(kCompOsPendingInfoPath, R_OK) != 0 || - access(kCompOsPendingInfoSignaturePath, R_OK) != 0) { - LOG(INFO) << "Missing CompOS info/signature, deleting pending artifacts"; - removeDirectory(kCompOsPendingArtifactsDir); - return art::odrefresh::ExitCode::kCompilationRequired; - } - - LOG(INFO) << "Current artifacts are out of date, switching to pending artifacts"; - removeDirectory(kArtArtifactsDir); - if (!rename(kCompOsPendingArtifactsDir, kArtArtifactsDir)) { - removeDirectory(kCompOsPendingArtifactsDir); - return art::odrefresh::ExitCode::kCompilationRequired; - } - - // Make sure the artifacts we have are genuinely produced by the current - // instance of CompOS. - auto compos_info = getComposInfo(); - if (!compos_info.ok()) { - LOG(WARNING) << compos_info.error(); - } else { - std::map<std::string, std::string> compos_digests(compos_info->file_hashes().begin(), - compos_info->file_hashes().end()); - - auto status = verifyAllFilesUsingCompOs(kArtArtifactsDir, compos_digests, signing_key); - if (!status.ok()) { - LOG(WARNING) << "Faild to verify CompOS artifacts: " << status.error(); - } else { - LOG(INFO) << "CompOS artifacts successfully verified."; - odrefresh_status = checkArtifacts(); - switch (odrefresh_status) { - case art::odrefresh::ExitCode::kCompilationRequired: - // We have verified all the files, and we need to make sure - // we don't check them against odsign.info which will be out - // of date. - *digests_verified = true; - return odrefresh_status; - case art::odrefresh::ExitCode::kOkay: { - // We have digests of all the files, so we can just sign them & save them now. - // We need to make sure we don't check them against odsign.info which will - // be out of date. - auto persisted = persistDigests(compos_digests, signing_key); - if (!persisted.ok()) { - LOG(ERROR) << persisted.error(); - // Don't try to compile again - if we can't write the digests, things - // are pretty bad. - return art::odrefresh::ExitCode::kCleanupFailed; - } - compos_check_record->use_comp_os_generated_artifacts = true; - LOG(INFO) << "Persisted CompOS digests."; - *digests_verified = true; - return odrefresh_status; - } - default: - return odrefresh_status; - } - } - } - - // We can't use the existing artifacts, so we will need to generate new - // ones. - if (removeDirectory(kArtArtifactsDir) == 0) { - // We have unsigned artifacts that we can't delete, so it's not safe to continue. - LOG(ERROR) << "Unable to delete invalid CompOS artifacts"; - return art::odrefresh::ExitCode::kCleanupFailed; - } - - return art::odrefresh::ExitCode::kCompilationRequired; -} -} // namespace - -int main(int /* argc */, char** argv) { - // stats_reporter is a pointer so that we can explicitly delete it - // instead of waiting for the program to die & its destrcutor be called - auto stats_reporter = std::make_unique<StatsReporter>(); - android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM)); - +int main(int /* argc */, char** /* argv */) { auto errorScopeGuard = []() { - // In case we hit any error, remove the artifacts and tell Zygote not to use - // anything - removeDirectory(kArtArtifactsDir); - removeDirectory(kCompOsPendingArtifactsDir); + // In case we hit any error, remove the artifacts and tell Zygote not to use anything + removeArtifacts(); // Tell init we don't need to use our key anymore SetProperty(kOdsignKeyDoneProp, "1"); // Tell init we're done with verification, and that it was an error @@ -492,54 +329,43 @@ int main(int /* argc */, char** argv) { LOG(INFO) << "Device doesn't support updatable APEX, exiting."; return 0; } - auto keystoreResult = - KeystoreKey::getInstance(kPublicKeySignature, kKeyAlias, kKeyNspace, kKeyBootLevel); + + auto keystoreResult = KeystoreKey::getInstance(); if (!keystoreResult.ok()) { - LOG(ERROR) << "Could not create keystore key: " << keystoreResult.error(); + LOG(ERROR) << "Could not create keystore key: " << keystoreResult.error().message(); return -1; } SigningKey* key = keystoreResult.value(); - bool supportsFsVerity = SupportsFsVerity(); + bool supportsFsVerity = access(kFsVerityProcPath, F_OK) == 0; if (!supportsFsVerity) { LOG(INFO) << "Device doesn't support fsverity. Falling back to full verification."; } - bool useCompOs = kUseCompOs && supportsFsVerity && compOsPresent(); - if (supportsFsVerity) { - auto existing_cert = verifyExistingRootCert(*key); + auto existing_cert = verifyExistingCert(*key); if (!existing_cert.ok()) { - LOG(WARNING) << existing_cert.error(); + LOG(WARNING) << existing_cert.error().message(); // Try to create a new cert - auto new_cert = createX509RootCert(*key, kSigningKeyCert); + auto new_cert = createX509Cert(*key, kSigningKeyCert); if (!new_cert.ok()) { - LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error(); + LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error().message(); // TODO apparently the key become invalid - delete the blob / cert return -1; } } else { LOG(INFO) << "Found and verified existing public key certificate: " << kSigningKeyCert; } - auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert, "fsv_ods"); + auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert); if (!cert_add_result.ok()) { LOG(ERROR) << "Failed to add certificate to fs-verity keyring: " - << cert_add_result.error(); + << cert_add_result.error().message(); return -1; } } - bool digests_verified = false; - art::odrefresh::ExitCode odrefresh_status = - useCompOs ? CheckCompOsPendingArtifacts(*key, &digests_verified, stats_reporter.get()) - : checkArtifacts(); - - // Explicitly reset the pointer - We rely on stats_reporter's - // destructor for actually writing the buffered metrics. This will otherwise not be called - // if the program doesn't exit normally (for ex, killed by init, which actually happens - // because odsign (after it finishes) sets kStopServiceProp instructing init to kill it). - stats_reporter.reset(); + art::odrefresh::ExitCode odrefresh_status = checkArtifacts(); // The artifacts dir doesn't necessarily need to exist; if the existing // artifacts on the system partition are valid, those can be used. @@ -547,9 +373,8 @@ int main(int /* argc */, char** argv) { // If we receive any error other than ENOENT, be suspicious bool artifactsPresent = (err == 0) || (err < 0 && errno != ENOENT); - if (artifactsPresent && !digests_verified && - (odrefresh_status == art::odrefresh::ExitCode::kOkay || - odrefresh_status == art::odrefresh::ExitCode::kCompilationRequired)) { + if (artifactsPresent && (odrefresh_status == art::odrefresh::ExitCode::kOkay || + odrefresh_status == art::odrefresh::ExitCode::kCompilationRequired)) { // If we haven't verified the digests yet, we need to validate them. We // need to do this both in case the existing artifacts are okay, but // also if odrefresh said that a recompile is required. In the latter @@ -570,7 +395,7 @@ int main(int /* argc */, char** argv) { SetProperty(kOdsignKeyDoneProp, "1"); } - auto verificationResult = verifyArtifactsIntegrity(trusted_digests, supportsFsVerity); + auto verificationResult = verifyArtifacts(trusted_digests, supportsFsVerity); if (!verificationResult.ok()) { int num_removed = removeDirectory(kArtArtifactsDir); if (num_removed == 0) { @@ -605,12 +430,12 @@ int main(int /* argc */, char** argv) { digests = computeDigests(kArtArtifactsDir); } if (!digests.ok()) { - LOG(ERROR) << digests.error(); + LOG(ERROR) << digests.error().message(); return -1; } auto persistStatus = persistDigests(*digests, *key); if (!persistStatus.ok()) { - LOG(ERROR) << persistStatus.error(); + LOG(ERROR) << persistStatus.error().message(); return -1; } } else if (odrefresh_status == art::odrefresh::ExitCode::kCleanupFailed) { diff --git a/ondevice-signing/proto/Android.bp b/ondevice-signing/proto/Android.bp index 356e6619..fd48f314 100644 --- a/ondevice-signing/proto/Android.bp +++ b/ondevice-signing/proto/Android.bp @@ -23,22 +23,7 @@ cc_library_static { host_supported: true, proto: { export_proto_headers: true, - type: "lite", + type: "full", }, srcs: ["odsign_info.proto"], - apex_available: [ - "//apex_available:platform", - "com.android.compos", - ], -} - -rust_protobuf { - name: "libodsign_proto_rust", - crate_name: "odsign_proto", - protos: ["odsign_info.proto"], - source_stem: "odsign_proto", - host_supported: true, - apex_available: [ - "com.android.compos", - ], } diff --git a/ondevice-signing/tests/Android.bp b/ondevice-signing/tests/Android.bp deleted file mode 100644 index 40272200..00000000 --- a/ondevice-signing/tests/Android.bp +++ /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. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "system_security_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["system_security_license"], -} - -cc_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", - ], -} diff --git a/ondevice-signing/tests/SigningUtils.cert.der b/ondevice-signing/tests/SigningUtils.cert.der Binary files differdeleted file mode 100644 index 0703d596..00000000 --- a/ondevice-signing/tests/SigningUtils.cert.der +++ /dev/null diff --git a/ondevice-signing/tests/SigningUtils.pem b/ondevice-signing/tests/SigningUtils.pem deleted file mode 100644 index 01dfa5ed..00000000 --- a/ondevice-signing/tests/SigningUtils.pem +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEAwyOUvcTpPuPxw2I9E28uhXT9pbJAgNE0dc1emOWRfapZ5iow -6PC7am47DfHyyZ/dwtw0vvi3QWCLIpsyFQbspO5XfmJH7mHBTvG8SkV40rFjptf8 -iwMl5i1ZUc5LChxuRh6DIS1fT2tFiXQ8iX3FUNNqjj/tOOtp45JEsgFK7WDW8YU3 -UseUn2BpkccUHMbe78EkEb4F6/AJWISk2INMOMPzRPUwdE3eUPVrufkp6jVJs69I -c6skRvAAqpiOx4rYpVzs3V5PuzhmCr7jt8hTQ0DA6q501UT06og6gIKPdqLjugKK -jt88XNXhhofckSbIH7edwa20LhvE4vtM/x7E0u+sCJozr0uyNgRXWs6imsVrC7Hb -cclecBUFqETVzP6RG49cCXVkuOkSNBm3HLt4291+O821gn3yygTE9bpf9uuDHSb1 -l13MOMbVeT3ipaRGMwD1bp7kBW3cEZ2zrQ4WSwwtADeWI6Z0k5SpcHfLxp+6dhPN -JtqBmgUTHuycmvoAPZK6XMnV0wCo2gUYS2q3Vc9yqsJRG0q2CKhOtKnuEmSqcjcC -52OnP06M5pLfvms+qoOLVUHMhidMtYs3yRPZ1ZPzScQhjg64QDsRiG+PC0lL/NFz -XPFcgzSsoIJ8UNZNjw7l5hh9NehJam2pqMizIcECl3gwrqGtq7Qo/AIXc4sCAwEA -AQKCAgBqkCqw+zBYvMgQ57vsugGQtdOyQcaB0j0wu6cWHf+2vWl8jLvK6XOfanTr -Z54rRxcmS3SueUox9JPmoRPXccGXS+URyn/3iQC0qMQnVwrlHCQMP9TU4TI4Ibmu -N9a4vc/mkNERNCLhTvZZWtWYS8uOGPYOmpBkTgK0WPMUtioBuamHmTUeCol6A3+D -MVElaeDi0vlsivXW421nHoCbEBB2y2M03CTKzp9CXNOoao3eLZ2C94y8RdB4wKXM -g6UtCQDIRRfAx7kIx4LKCXZ3rXjyuBDh18VLle2diilQdnv70HZF5Q9feD8Rf2c6 -PUVRKvmMgIww8Tf9GgMJ5SwmAdp/VblgcKsdjr8tnT7V9ljkYCL4K3H4FT5LKXjI -FiGyqBie8jvLYCLx6DlVTIi+Q/kvxaFT9twazVlz9jfgufQ4ICBKlNp4A6kpwrzb -9QnMrHI+gTOrCCEeklg90M+xk5UERueLPnXYbvAG8cv1FeVpi9ldSQds5VHN1ZJ4 -hWWeWfGgIDiNeZuKL141NLS/sX9rCGEsQyVLTKkSDIgh5ncepVcXhB40GZFfggml -A3HfNRN5lHLwH5+JKWlVx7PfUGPOTgx62i7HF+qcV3bQJfqnAMLPA7hh1bo8xIPp -hiAbaZkwCGlNCwadzOq6U99Dx7eorwfAgGDKWnAr/rMaK9n+OQKCAQEA8UItzBLq -yyJ+Xj7n/M3x/+v2E6dcDYH64oeMpVEEdH6cSSNcdUm0vgX4p25W0uY5kgj1RsXV -gOk+9W6cM84p+2+DIGI8fydnuIv8q6TFiouDCY+T3FVdj4MasNZuAeHR7c6Coc5N -Eckv3F1sfoGlH4z2AzppY0T3VX5TkKkh719X7A/cpIirZBRLfddDA3Hr6pm/vgSo -mX1X5BhjrujasRoZhU2RsXnAflXp18aP67zvlRlzGrpMGeueRjp2NXF3pItiMAjN -EOSoY3elEi6Um+WomFGLsY5SAuId+TJy8SqsGKWNHN+UPx4tGYQmEPA1e76Nu5Ex -xDxpHIWyZ4Hz/wKCAQEAzw/9bPFgihzSnrBbJiFZVGPhFxw39aeuB5/3ZMPjknfK -fyWonbhrJF14/86JSd16Xime6J4a3OsXKzTo36sLBDsYfYXof7bwfKk/GUryMFOG -0aZqiZbiRQ8uCfzd+MtnNxO0WxiyZvj8i7hMjK50yBhRs/5YAHSoLxFOfVVSLNAi -nhIDqtzeLA2W05PGusgz/0w8FHI6J64jZY4EQLgr43K6DXoFLQjtsl8JZfMO9fEc -0j5vRytXtYlTSlQEtKeQ8cvpcqbY0gmrEasZ4v1jEzemXzvFkx+ck+Ayl/vHx60A -AZCom66BudFArAnuVdrMAWUH+78Xf2l5en7kz40QdQKCAQBWxkran+M7dQimtVGT -qC9msWQs5YFCioHGgKKhw2Yq0G8+Dy3uMbiEsHkjH5iy+oOydu5hqj6Ew2AVvtcH -+xs2iIFNYIgJ5A52XkNfKUCz+EIFalLwaPPh7nHnMPkYTDTJqAFsWVt3DjnctO2V -AuR1WKoTtyq4vdGIOour+GlwQ4bILVxbAZ1Dvdj5RjegQZVtKCfDHMHXkzHNpMgV -3ULreEu9mozQnM4ToqsdJRoW3DoAEstHzcIZgJnJALYLuughktCaHlBDxzqZrCr/ -QynIeO4O+yWXk20EBHhrbS3SeFq18rWysOgNW7k0+EcIyJ00CPHJiQuxXVkhHSVx -/VfZAoIBAGA1isg642No/wgS41c1OZ93hRfK2cl/ruIGFtowFqZwmJs5cT5PeSD9 -eYJKggnbKcdkyVxGUi8B4NMHk4iRnd3KY5e3R49H/je+H/5tj1ibBtKU432oqNvz -sK2dW7oFMKEru6p0MDieSiHVcWQQj1yFyDi83kDf82FjRjgAE92Um/EcZ63VUDnh -2onWaQlSiq59ypCpfpH/XJ0MPrefm2zkWsR2RL9nHaK6e9Bt/i6SaJTbw7Kq1ecY -tqWbolAaZ8OhvoeyNJ5rNZxRBwcsOwOr4NbxG90/W+5txrRNnccOgCk6AM3NaKNh -Mg590sr7jby8J9h2MsHVzUb4fPJfFh0CggEBANS+aqEzWflHMOR4knhM7QHHaTfS -4wwR3zL3/tSaV/cxNGehtjyEg85/aKklt/ude3/sm/Z0m6jQEMWgAt5iwuBovPA+ -1/rGkWTHL+dQr0i81C3b/Ymx7izL7BobaYV0o00EoKotC0NP5qR0fBKSkTfFqAYG -SxnHtw/vduxu2H6TyIrdtvNNqc1PbHdzDI/FwzcWZFNyHBzSWwZxB5w+21uRUayv -Iz3zcytrZZbAuOjCnhxNL/6XgcttqWSVFB4Ul1xiXrXDx2Xq+FfM40UF7oKGd+Kt -B0wMqoZJj+0CdFfRZxHA6/n8v1Al+8lYo8smp+R9fR6qZKcugEFgdVkIl7E= ------END RSA PRIVATE KEY----- diff --git a/ondevice-signing/tests/SigningUtilsTest.cpp b/ondevice-signing/tests/SigningUtilsTest.cpp deleted file mode 100644 index 10f7629e..00000000 --- a/ondevice-signing/tests/SigningUtilsTest.cpp +++ /dev/null @@ -1,48 +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 <android-base/file.h> -#include <gtest/gtest.h> - -#include "CertUtils.h" -#include "VerityUtils.h" - -// These files were created using the following commands: -// openssl genrsa -out SigningUtils.pem 4096 -// openssl req -new -x509 -key SigningUtils.pem -out SigningUtils.cert.pem -// openssl x509 -in SigningUtils.cert.pem -out SigningUtils.cert.der -outform DER -// head -c 4096 </dev/urandom >test_file -// openssl dgst -sign SigningUtils.pem -keyform PEM -sha256 -out test_file.sig -binary test_file -const std::string kTestCert = "SigningUtils.cert.der"; -const std::string kTestFile = "test_file"; -const std::string kTestFileSignature = "test_file.sig"; - -TEST(SigningUtilsTest, CheckVerifySignature) { - std::string signature; - std::string sigFile = android::base::GetExecutableDirectory() + "/" + kTestFileSignature; - ASSERT_TRUE(android::base::ReadFileToString(sigFile, &signature)); - - std::string data; - std::string testFile = android::base::GetExecutableDirectory() + "/" + kTestFile; - ASSERT_TRUE(android::base::ReadFileToString(testFile, &data)); - - std::string testCert = android::base::GetExecutableDirectory() + "/" + kTestCert; - auto trustedKey = extractPublicKeyFromX509(testCert.c_str()); - ASSERT_TRUE(trustedKey.ok()); - - auto result = verifySignature(data, signature, *trustedKey); - ASSERT_TRUE(result.ok()); -} diff --git a/ondevice-signing/tests/test_file b/ondevice-signing/tests/test_file Binary files differdeleted file mode 100644 index 8a121bea..00000000 --- a/ondevice-signing/tests/test_file +++ /dev/null diff --git a/ondevice-signing/tests/test_file.sig b/ondevice-signing/tests/test_file.sig Binary files differdeleted file mode 100644 index ffd95dcd..00000000 --- a/ondevice-signing/tests/test_file.sig +++ /dev/null diff --git a/provisioner/Android.bp b/provisioner/Android.bp index 665a9e71..afbc4055 100644 --- a/provisioner/Android.bp +++ b/provisioner/Android.bp @@ -47,10 +47,8 @@ cc_binary { name: "rkp_factory_extraction_tool", vendor: true, srcs: ["rkp_factory_extraction_tool.cpp"], - defaults: [ - "keymint_use_latest_hal_aidl_ndk_shared", - ], shared_libs: [ + "android.hardware.security.keymint-V1-ndk_platform", "libbinder", "libbinder_ndk", "libcrypto", diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp index 0f455310..c439b990 100644 --- a/provisioner/rkp_factory_extraction_tool.cpp +++ b/provisioner/rkp_factory_extraction_tool.cpp @@ -22,7 +22,6 @@ #include <cppbor.h> #include <gflags/gflags.h> #include <keymaster/cppcose/cppcose.h> -#include <openssl/base64.h> #include <remote_prov/remote_prov_utils.h> #include <sys/random.h> @@ -30,7 +29,6 @@ using aidl::android::hardware::security::keymint::DeviceInfo; using aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent; using aidl::android::hardware::security::keymint::MacedPublicKey; using aidl::android::hardware::security::keymint::ProtectedData; -using aidl::android::hardware::security::keymint::RpcHardwareInfo; using aidl::android::hardware::security::keymint::remote_prov::generateEekChain; using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain; using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild; @@ -51,26 +49,6 @@ constexpr std::string_view kBuildPlusCsr = "build+csr"; // Text-encoded (JSON) constexpr size_t kChallengeSize = 16; -std::string toBase64(const std::vector<uint8_t>& buffer) { - size_t base64Length; - int rc = EVP_EncodedLength(&base64Length, buffer.size()); - if (!rc) { - std::cerr << "Error getting base64 length. Size overflow?" << std::endl; - exit(-1); - } - - std::string base64(base64Length, ' '); - rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size()); - ++rc; // Account for NUL, which BoringSSL does not for some reason. - if (rc != base64Length) { - std::cerr << "Error writing base64. Expected " << base64Length - << " bytes to be written, but " << rc << " bytes were actually written." - << std::endl; - exit(-1); - } - return base64; -} - std::vector<uint8_t> generateChallenge() { std::vector<uint8_t> challenge(kChallengeSize); @@ -78,13 +56,9 @@ std::vector<uint8_t> generateChallenge() { uint8_t* writePtr = challenge.data(); while (bytesRemaining > 0) { int bytesRead = getrandom(writePtr, bytesRemaining, /*flags=*/0); - if (bytesRead < 0) { - if (errno == EINTR) { - continue; - } else { - std::cerr << errno << ": " << strerror(errno) << std::endl; - exit(-1); - } + if (bytesRead < 0 && errno != EINTR) { + std::cerr << errno << ": " << strerror(errno) << std::endl; + exit(-1); } bytesRemaining -= bytesRead; writePtr += bytesRead; @@ -114,30 +88,27 @@ Array composeCertificateRequest(const ProtectedData& protectedData, return certificateRequest; } -std::vector<uint8_t> getEekChain(uint32_t curve) { +std::vector<uint8_t> getEekChain() { if (FLAGS_test_mode) { const std::vector<uint8_t> kFakeEekId = {'f', 'a', 'k', 'e', 0}; - auto eekOrErr = generateEekChain(curve, 3 /* chainlength */, kFakeEekId); + auto eekOrErr = generateEekChain(3 /* chainlength */, kFakeEekId); if (!eekOrErr) { std::cerr << "Failed to generate test EEK somehow: " << eekOrErr.message() << std::endl; exit(-1); } - auto [eek, pubkey, privkey] = eekOrErr.moveValue(); - std::cout << "EEK raw keypair:" << std::endl; - std::cout << " pub: " << toBase64(pubkey) << std::endl; - std::cout << " priv: " << toBase64(privkey) << std::endl; + auto [eek, ignored_pubkey, ignored_privkey] = eekOrErr.moveValue(); return eek; } - return getProdEekChain(curve); + return getProdEekChain(); } -void writeOutput(const std::string instance_name, const Array& csr) { +void writeOutput(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(csr); if (!error.empty()) { std::cerr << "Error JSON encoding the output: " << error; exit(1); @@ -163,23 +134,16 @@ void getCsrForInstance(const char* name, void* /*context*/) { auto rkp_service = IRemotelyProvisionedComponent::fromBinder(rkp_binder); if (!rkp_service) { std::cerr << "Unable to get binder object for '" << fullName << "', skipping."; - exit(-1); + return; } std::vector<uint8_t> keysToSignMac; std::vector<MacedPublicKey> emptyKeys; DeviceInfo verifiedDeviceInfo; ProtectedData protectedData; - RpcHardwareInfo hwInfo; - ::ndk::ScopedAStatus status = rkp_service->getHardwareInfo(&hwInfo); - if (!status.isOk()) { - std::cerr << "Failed to get hardware info for '" << fullName - << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; - exit(-1); - } - status = rkp_service->generateCertificateRequest( - FLAGS_test_mode, emptyKeys, getEekChain(hwInfo.supportedEekCurve), challenge, - &verifiedDeviceInfo, &protectedData, &keysToSignMac); + ::ndk::ScopedAStatus status = rkp_service->generateCertificateRequest( + FLAGS_test_mode, emptyKeys, getEekChain(), challenge, &verifiedDeviceInfo, &protectedData, + &keysToSignMac); if (!status.isOk()) { std::cerr << "Bundle extraction failed for '" << fullName << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl; @@ -187,7 +151,7 @@ void getCsrForInstance(const char* name, void* /*context*/) { } auto request = composeCertificateRequest(protectedData, verifiedDeviceInfo, challenge, keysToSignMac); - writeOutput(std::string(name), request); + writeOutput(request); } } // namespace |