Merge "Revert "Keystore 2.0 km_compat: Cuttlefish does not return a vendor patch level.""
diff --git a/OWNERS b/OWNERS
index 93c024d..03e5769 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,10 @@
+alanstokes@google.com
cbrubaker@google.com
+drysdale@google.com
+eranm@google.com
hasinitg@google.com
jbires@google.com
-jdanis@google.com
+jeffv@google.com
kroot@google.com
sethmo@google.com
swillden@google.com
diff --git a/diced/Android.bp b/diced/Android.bp
new file mode 100644
index 0000000..e13d863
--- /dev/null
+++ b/diced/Android.bp
@@ -0,0 +1,228 @@
+// 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/TEST_MAPPING b/diced/TEST_MAPPING
new file mode 100644
index 0000000..d81efdd
--- /dev/null
+++ b/diced/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+ "presubmit": [
+ {
+ "name": "diced_utils_test"
+ },
+ {
+ "name": "diced_sample_inputs_test"
+ },
+ {
+ "name": "diced_test"
+ },
+ {
+ "name": "diced_vendor_test"
+ }
+ ]
+}
diff --git a/diced/aidl/Android.bp b/diced/aidl/Android.bp
new file mode 100644
index 0000000..57dad53
--- /dev/null
+++ b/diced/aidl/Android.bp
@@ -0,0 +1,51 @@
+// 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",
+ "com.android.virt",
+ ],
+ },
+ 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
new file mode 100644
index 0000000..c81fdea
--- /dev/null
+++ b/diced/aidl/android/security/dice/IDiceMaintenance.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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
new file mode 100644
index 0000000..2b3ef76
--- /dev/null
+++ b/diced/aidl/android/security/dice/IDiceNode.aidl
@@ -0,0 +1,96 @@
+/*
+ * 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
new file mode 100644
index 0000000..7c66058
--- /dev/null
+++ b/diced/aidl/android/security/dice/ResponseCode.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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
new file mode 100644
index 0000000..2226f47
--- /dev/null
+++ b/diced/diced.microdroid.rc
@@ -0,0 +1,13 @@
+# 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
new file mode 100644
index 0000000..8c43fa5
--- /dev/null
+++ b/diced/diced.rc
@@ -0,0 +1,13 @@
+# 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
new file mode 100644
index 0000000..a84190a
--- /dev/null
+++ b/diced/open_dice_cbor/Android.bp
@@ -0,0 +1,59 @@
+// 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,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
+
+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
new file mode 100644
index 0000000..ffb8a48
--- /dev/null
+++ b/diced/open_dice_cbor/lib.rs
@@ -0,0 +1,1037 @@
+// 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, Eq)]
+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, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x3e,
+ 0x85, 0xe5, 0x72, 0x75, 0x55, 0xe5, 0x1e, 0xe7, 0xf3, 0x35, 0x94, 0x8e, 0xbb, 0xbd, 0x74,
+ 0x1e, 0x1d, 0xca, 0x49, 0x9c, 0x97, 0x39, 0x77, 0x06, 0xd3, 0xc8, 0x6e, 0x8b, 0xd7, 0x33,
+ 0xf9, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28, 0x34,
+ 0x32, 0x64, 0x38, 0x38, 0x36, 0x34, 0x66, 0x39, 0x37, 0x62, 0x36, 0x35, 0x34, 0x37, 0x61,
+ 0x35, 0x30, 0x63, 0x31, 0x65, 0x30, 0x61, 0x37, 0x34, 0x39, 0x66, 0x38, 0x65, 0x66, 0x38,
+ 0x62, 0x38, 0x31, 0x65, 0x63, 0x36, 0x32, 0x61, 0x66, 0x02, 0x78, 0x28, 0x31, 0x66, 0x36,
+ 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66,
+ 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31, 0x64,
+ 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x16,
+ 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26,
+ 0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe,
+ 0x25, 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d,
+ 0xd3, 0xfb, 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7,
+ 0x15, 0x98, 0x14, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71,
+ 0x63, 0x41, 0x42, 0x4c, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01, 0x11, 0x73,
+ 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x47, 0xae, 0x42, 0x27, 0x4c, 0xcb, 0x65,
+ 0x4d, 0xee, 0x74, 0x2d, 0x05, 0x78, 0x2a, 0x08, 0x2a, 0xa5, 0xf0, 0xcf, 0xea, 0x3e, 0x60,
+ 0xee, 0x97, 0x11, 0x4b, 0x5b, 0xe6, 0x05, 0x0c, 0xe8, 0x90, 0xf5, 0x22, 0xc4, 0xc6, 0x67,
+ 0x7a, 0x22, 0x27, 0x17, 0xb3, 0x79, 0xcc, 0x37, 0x64, 0x5e, 0x19, 0x4f, 0x96, 0x37, 0x67,
+ 0x3c, 0xd0, 0xc5, 0xed, 0x0f, 0xdd, 0xe7, 0x2e, 0x4f, 0x70, 0x97, 0x30, 0x3a, 0x00, 0x47,
+ 0x44, 0x54, 0x58, 0x40, 0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3,
+ 0x97, 0x4a, 0xcb, 0x3c, 0xe7, 0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59,
+ 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf, 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc,
+ 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd, 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6,
+ 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01,
+ 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02,
+ 0x20, 0x06, 0x21, 0x58, 0x20, 0xb1, 0x02, 0xcc, 0x2c, 0xb2, 0x6a, 0x3b, 0xe9, 0xc1, 0xd3,
+ 0x95, 0x10, 0xa0, 0xe1, 0xff, 0x51, 0xde, 0x57, 0xd5, 0x65, 0x28, 0xfd, 0x7f, 0xeb, 0xd4,
+ 0xca, 0x15, 0xf3, 0xca, 0xdf, 0x37, 0x88, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58,
+ 0x40, 0x58, 0xd8, 0x03, 0x24, 0x53, 0x60, 0x57, 0xa9, 0x09, 0xfa, 0xab, 0xdc, 0x57, 0x1e,
+ 0xf0, 0xe5, 0x1e, 0x51, 0x6f, 0x9e, 0xa3, 0x42, 0xe6, 0x6a, 0x8c, 0xaa, 0xad, 0x08, 0x48,
+ 0xde, 0x7f, 0x4f, 0x6e, 0x2f, 0x7f, 0x39, 0x6c, 0xa1, 0xf8, 0x42, 0x71, 0xfe, 0x17, 0x3d,
+ 0xca, 0x31, 0x83, 0x92, 0xed, 0xbb, 0x40, 0xb8, 0x10, 0xe0, 0xf2, 0x5a, 0x99, 0x53, 0x38,
+ 0x46, 0x33, 0x97, 0x78, 0x05, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9,
+ 0x01, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66,
+ 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33,
+ 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x02, 0x78,
+ 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34, 0x38, 0x37, 0x30,
+ 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36, 0x37, 0x65, 0x61, 0x34,
+ 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, 0x35, 0x3a, 0x00, 0x47, 0x44,
+ 0x50, 0x58, 0x40, 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43,
+ 0x83, 0x7f, 0x46, 0x8d, 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9,
+ 0x56, 0xb3, 0xbf, 0x2f, 0xfa, 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18,
+ 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, 0xd2,
+ 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a,
+ 0x00, 0x01, 0x11, 0x71, 0x63, 0x41, 0x56, 0x42, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a,
+ 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x93, 0x17, 0xe1,
+ 0x11, 0x27, 0x59, 0xd0, 0xef, 0x75, 0x0b, 0x2b, 0x1c, 0x0f, 0x5f, 0x52, 0xc3, 0x29, 0x23,
+ 0xb5, 0x2a, 0xe6, 0x12, 0x72, 0x6f, 0x39, 0x86, 0x65, 0x2d, 0xf2, 0xe4, 0xe7, 0xd0, 0xaf,
+ 0x0e, 0xa7, 0x99, 0x16, 0x89, 0x97, 0x21, 0xf7, 0xdc, 0x89, 0xdc, 0xde, 0xbb, 0x94, 0x88,
+ 0x1f, 0xda, 0xe2, 0xf3, 0xe0, 0x54, 0xf9, 0x0e, 0x29, 0xb1, 0xbd, 0xe1, 0x0c, 0x0b, 0xd7,
+ 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa,
+ 0x55, 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa, 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a,
+ 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43, 0x13, 0xbf, 0xfe, 0xd3, 0x36,
+ 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab, 0x3d, 0x0f, 0x89, 0xa4,
+ 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98, 0x3a, 0x00, 0x47,
+ 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03,
+ 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x96, 0x6d, 0x96, 0x42, 0xda, 0x64,
+ 0x51, 0xad, 0xfa, 0x00, 0xbc, 0xbc, 0x95, 0x8a, 0xb0, 0xb9, 0x76, 0x01, 0xe6, 0xbd, 0xc0,
+ 0x26, 0x79, 0x26, 0xfc, 0x0f, 0x1d, 0x87, 0x65, 0xf1, 0xf3, 0x99, 0x3a, 0x00, 0x47, 0x44,
+ 0x58, 0x41, 0x20, 0x58, 0x40, 0x10, 0x7f, 0x77, 0xad, 0x70, 0xbd, 0x52, 0x81, 0x28, 0x8d,
+ 0x24, 0x81, 0xb4, 0x3f, 0x21, 0x68, 0x9f, 0xc3, 0x80, 0x68, 0x86, 0x55, 0xfb, 0x2e, 0x6d,
+ 0x96, 0xe1, 0xe1, 0xb7, 0x28, 0x8d, 0x63, 0x85, 0xba, 0x2a, 0x01, 0x33, 0x87, 0x60, 0x63,
+ 0xbb, 0x16, 0x3f, 0x2f, 0x3d, 0xf4, 0x2d, 0x48, 0x5b, 0x87, 0xed, 0xda, 0x34, 0xeb, 0x9c,
+ 0x4d, 0x14, 0xac, 0x65, 0xf4, 0xfa, 0xef, 0x45, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0,
+ 0x59, 0x01, 0x8f, 0xa9, 0x01, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36,
+ 0x39, 0x37, 0x34, 0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34,
+ 0x32, 0x36, 0x37, 0x65, 0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37,
+ 0x32, 0x35, 0x02, 0x78, 0x28, 0x35, 0x64, 0x34, 0x65, 0x64, 0x37, 0x66, 0x34, 0x31, 0x37,
+ 0x61, 0x39, 0x35, 0x34, 0x61, 0x31, 0x38, 0x31, 0x34, 0x30, 0x37, 0x62, 0x35, 0x38, 0x38,
+ 0x35, 0x61, 0x66, 0x64, 0x37, 0x32, 0x61, 0x35, 0x62, 0x66, 0x34, 0x30, 0x64, 0x61, 0x36,
+ 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x47, 0x44,
+ 0x53, 0x58, 0x1a, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x67, 0x41, 0x6e, 0x64, 0x72, 0x6f,
+ 0x69, 0x64, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x0c, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a,
+ 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x26, 0x1a, 0xbd, 0x26, 0xd8, 0x37, 0x8f, 0x4a, 0xf2,
+ 0x9e, 0x49, 0x4d, 0x93, 0x23, 0xc4, 0x6e, 0x02, 0xda, 0xe0, 0x00, 0x02, 0xe7, 0xed, 0x29,
+ 0xdf, 0x2b, 0xb3, 0x69, 0xf3, 0x55, 0x0e, 0x4c, 0x22, 0xdc, 0xcf, 0xf5, 0x92, 0xc9, 0xfa,
+ 0x78, 0x98, 0xf1, 0x0e, 0x55, 0x5f, 0xf4, 0x45, 0xed, 0xc0, 0x0a, 0x72, 0x2a, 0x7a, 0x3a,
+ 0xd2, 0xb1, 0xf7, 0x76, 0xfe, 0x2a, 0x6b, 0x7b, 0x2a, 0x53, 0x3a, 0x00, 0x47, 0x44, 0x54,
+ 0x58, 0x40, 0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30,
+ 0x03, 0xb8, 0xd6, 0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37,
+ 0x68, 0x4e, 0x1d, 0xc0, 0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, 0xd2,
+ 0x9c, 0xfc, 0x12, 0x9e, 0x77, 0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, 0x10, 0xd5,
+ 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00,
+ 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06,
+ 0x21, 0x58, 0x20, 0xdb, 0xe7, 0x5b, 0x3f, 0xa3, 0x42, 0xb0, 0x9c, 0xf8, 0x40, 0x8c, 0xb0,
+ 0x9c, 0xf0, 0x0a, 0xaf, 0xdf, 0x6f, 0xe5, 0x09, 0x21, 0x11, 0x92, 0xe1, 0xf8, 0xc5, 0x09,
+ 0x02, 0x3d, 0x1f, 0xb7, 0xc5, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xc4,
+ 0xc1, 0xd7, 0x1c, 0x2d, 0x26, 0x89, 0x22, 0xcf, 0xa6, 0x99, 0x77, 0x30, 0x84, 0x86, 0x27,
+ 0x59, 0x8f, 0xd8, 0x08, 0x75, 0xe0, 0xb2, 0xef, 0xf9, 0xfa, 0xa5, 0x40, 0x8c, 0xd3, 0xeb,
+ 0xbb, 0xda, 0xf2, 0xc8, 0xae, 0x41, 0x22, 0x50, 0x9c, 0xe8, 0xb2, 0x9c, 0x9b, 0x3f, 0x8a,
+ 0x78, 0x76, 0xab, 0xd0, 0xbe, 0xfc, 0xe4, 0x79, 0xcb, 0x1b, 0x2b, 0xaa, 0x4d, 0xdd, 0x15,
+ 0x61, 0x42, 0x06,
+ ];
+
+ // This test invokes make_sample_bcc_and_cdis and compares the result bitwise to the target
+ // vectors. The function uses main_flow, bcc_main_flow, format_config_descriptor,
+ // derive_cdi_private_key_seed, and keypair_from_seed. This test is sensitive to errors
+ // and changes in any of those functions.
+ #[test]
+ fn main_flow_and_bcc_main_flow() {
+ let (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
new file mode 100644
index 0000000..3915508
--- /dev/null
+++ b/diced/src/diced_client_test.rs
@@ -0,0 +1,188 @@
+// 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
new file mode 100644
index 0000000..c2cf02c
--- /dev/null
+++ b/diced/src/diced_main.rs
@@ -0,0 +1,76 @@
+// 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
new file mode 100644
index 0000000..3e230e4
--- /dev/null
+++ b/diced/src/error.rs
@@ -0,0 +1,123 @@
+// 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
new file mode 100644
index 0000000..e8657e0
--- /dev/null
+++ b/diced/src/error_vendor.rs
@@ -0,0 +1,119 @@
+// 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
new file mode 100644
index 0000000..69cf4ac
--- /dev/null
+++ b/diced/src/hal_node.rs
@@ -0,0 +1,725 @@
+// 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, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20,
+ 0x3e, 0x85, 0xe5, 0x72, 0x75, 0x55, 0xe5, 0x1e, 0xe7, 0xf3, 0x35, 0x94, 0x8e, 0xbb,
+ 0xbd, 0x74, 0x1e, 0x1d, 0xca, 0x49, 0x9c, 0x97, 0x39, 0x77, 0x06, 0xd3, 0xc8, 0x6e,
+ 0x8b, 0xd7, 0x33, 0xf9, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9,
+ 0x01, 0x78, 0x28, 0x34, 0x32, 0x64, 0x38, 0x38, 0x36, 0x34, 0x66, 0x39, 0x37, 0x62,
+ 0x36, 0x35, 0x34, 0x37, 0x61, 0x35, 0x30, 0x63, 0x31, 0x65, 0x30, 0x61, 0x37, 0x34,
+ 0x39, 0x66, 0x38, 0x65, 0x66, 0x38, 0x62, 0x38, 0x31, 0x65, 0x63, 0x36, 0x32, 0x61,
+ 0x66, 0x02, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35,
+ 0x32, 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39,
+ 0x65, 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, 0x63, 0x34, 0x30, 0x34, 0x65,
+ 0x37, 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x16, 0x48, 0xf2, 0x55, 0x53,
+ 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26, 0x0f, 0xcf, 0x5b,
+ 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, 0x25, 0x1c,
+ 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, 0xd3,
+ 0xfb, 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7,
+ 0x15, 0x98, 0x14, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11,
+ 0x71, 0x63, 0x41, 0x42, 0x4c, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01,
+ 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x47, 0xae, 0x42, 0x27,
+ 0x4c, 0xcb, 0x65, 0x4d, 0xee, 0x74, 0x2d, 0x05, 0x78, 0x2a, 0x08, 0x2a, 0xa5, 0xf0,
+ 0xcf, 0xea, 0x3e, 0x60, 0xee, 0x97, 0x11, 0x4b, 0x5b, 0xe6, 0x05, 0x0c, 0xe8, 0x90,
+ 0xf5, 0x22, 0xc4, 0xc6, 0x67, 0x7a, 0x22, 0x27, 0x17, 0xb3, 0x79, 0xcc, 0x37, 0x64,
+ 0x5e, 0x19, 0x4f, 0x96, 0x37, 0x67, 0x3c, 0xd0, 0xc5, 0xed, 0x0f, 0xdd, 0xe7, 0x2e,
+ 0x4f, 0x70, 0x97, 0x30, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xf9, 0x00, 0x9d,
+ 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7, 0x6b,
+ 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8,
+ 0xdf, 0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12,
+ 0x12, 0xb2, 0xfd, 0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99,
+ 0xea, 0xae, 0xfd, 0xaa, 0x0d, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00,
+ 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20,
+ 0x06, 0x21, 0x58, 0x20, 0xb1, 0x02, 0xcc, 0x2c, 0xb2, 0x6a, 0x3b, 0xe9, 0xc1, 0xd3,
+ 0x95, 0x10, 0xa0, 0xe1, 0xff, 0x51, 0xde, 0x57, 0xd5, 0x65, 0x28, 0xfd, 0x7f, 0xeb,
+ 0xd4, 0xca, 0x15, 0xf3, 0xca, 0xdf, 0x37, 0x88, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41,
+ 0x20, 0x58, 0x40, 0x58, 0xd8, 0x03, 0x24, 0x53, 0x60, 0x57, 0xa9, 0x09, 0xfa, 0xab,
+ 0xdc, 0x57, 0x1e, 0xf0, 0xe5, 0x1e, 0x51, 0x6f, 0x9e, 0xa3, 0x42, 0xe6, 0x6a, 0x8c,
+ 0xaa, 0xad, 0x08, 0x48, 0xde, 0x7f, 0x4f, 0x6e, 0x2f, 0x7f, 0x39, 0x6c, 0xa1, 0xf8,
+ 0x42, 0x71, 0xfe, 0x17, 0x3d, 0xca, 0x31, 0x83, 0x92, 0xed, 0xbb, 0x40, 0xb8, 0x10,
+ 0xe0, 0xf2, 0x5a, 0x99, 0x53, 0x38, 0x46, 0x33, 0x97, 0x78, 0x05, 0x84, 0x43, 0xa1,
+ 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39,
+ 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66,
+ 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31,
+ 0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x02, 0x78, 0x28, 0x32, 0x35, 0x39,
+ 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34, 0x38, 0x37, 0x30, 0x35, 0x64, 0x65,
+ 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36, 0x37, 0x65, 0x61, 0x34, 0x39, 0x33,
+ 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x50,
+ 0x58, 0x40, 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43,
+ 0x83, 0x7f, 0x46, 0x8d, 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0,
+ 0xd9, 0x56, 0xb3, 0xbf, 0x2f, 0xfa, 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9,
+ 0x6b, 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54,
+ 0xe9, 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, 0x3a, 0x00, 0x47, 0x44,
+ 0x53, 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x63, 0x41, 0x56, 0x42, 0x3a, 0x00,
+ 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44,
+ 0x52, 0x58, 0x40, 0x93, 0x17, 0xe1, 0x11, 0x27, 0x59, 0xd0, 0xef, 0x75, 0x0b, 0x2b,
+ 0x1c, 0x0f, 0x5f, 0x52, 0xc3, 0x29, 0x23, 0xb5, 0x2a, 0xe6, 0x12, 0x72, 0x6f, 0x39,
+ 0x86, 0x65, 0x2d, 0xf2, 0xe4, 0xe7, 0xd0, 0xaf, 0x0e, 0xa7, 0x99, 0x16, 0x89, 0x97,
+ 0x21, 0xf7, 0xdc, 0x89, 0xdc, 0xde, 0xbb, 0x94, 0x88, 0x1f, 0xda, 0xe2, 0xf3, 0xe0,
+ 0x54, 0xf9, 0x0e, 0x29, 0xb1, 0xbd, 0xe1, 0x0c, 0x0b, 0xd7, 0xf6, 0x3a, 0x00, 0x47,
+ 0x44, 0x54, 0x58, 0x40, 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac,
+ 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa, 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5,
+ 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43, 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8,
+ 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab, 0x3d, 0x0f, 0x89, 0xa4,
+ 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98, 0x3a, 0x00,
+ 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01,
+ 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x96, 0x6d, 0x96,
+ 0x42, 0xda, 0x64, 0x51, 0xad, 0xfa, 0x00, 0xbc, 0xbc, 0x95, 0x8a, 0xb0, 0xb9, 0x76,
+ 0x01, 0xe6, 0xbd, 0xc0, 0x26, 0x79, 0x26, 0xfc, 0x0f, 0x1d, 0x87, 0x65, 0xf1, 0xf3,
+ 0x99, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0x10, 0x7f, 0x77, 0xad,
+ 0x70, 0xbd, 0x52, 0x81, 0x28, 0x8d, 0x24, 0x81, 0xb4, 0x3f, 0x21, 0x68, 0x9f, 0xc3,
+ 0x80, 0x68, 0x86, 0x55, 0xfb, 0x2e, 0x6d, 0x96, 0xe1, 0xe1, 0xb7, 0x28, 0x8d, 0x63,
+ 0x85, 0xba, 0x2a, 0x01, 0x33, 0x87, 0x60, 0x63, 0xbb, 0x16, 0x3f, 0x2f, 0x3d, 0xf4,
+ 0x2d, 0x48, 0x5b, 0x87, 0xed, 0xda, 0x34, 0xeb, 0x9c, 0x4d, 0x14, 0xac, 0x65, 0xf4,
+ 0xfa, 0xef, 0x45, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8f, 0xa9,
+ 0x01, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34,
+ 0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36,
+ 0x37, 0x65, 0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32,
+ 0x35, 0x02, 0x78, 0x28, 0x35, 0x64, 0x34, 0x65, 0x64, 0x37, 0x66, 0x34, 0x31, 0x37,
+ 0x61, 0x39, 0x35, 0x34, 0x61, 0x31, 0x38, 0x31, 0x34, 0x30, 0x37, 0x62, 0x35, 0x38,
+ 0x38, 0x35, 0x61, 0x66, 0x64, 0x37, 0x32, 0x61, 0x35, 0x62, 0x66, 0x34, 0x30, 0x64,
+ 0x61, 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x58, 0x1a, 0xa3, 0x3a, 0x00, 0x01,
+ 0x11, 0x71, 0x67, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x3a, 0x00, 0x01, 0x11,
+ 0x72, 0x0c, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58,
+ 0x40, 0x26, 0x1a, 0xbd, 0x26, 0xd8, 0x37, 0x8f, 0x4a, 0xf2, 0x9e, 0x49, 0x4d, 0x93,
+ 0x23, 0xc4, 0x6e, 0x02, 0xda, 0xe0, 0x00, 0x02, 0xe7, 0xed, 0x29, 0xdf, 0x2b, 0xb3,
+ 0x69, 0xf3, 0x55, 0x0e, 0x4c, 0x22, 0xdc, 0xcf, 0xf5, 0x92, 0xc9, 0xfa, 0x78, 0x98,
+ 0xf1, 0x0e, 0x55, 0x5f, 0xf4, 0x45, 0xed, 0xc0, 0x0a, 0x72, 0x2a, 0x7a, 0x3a, 0xd2,
+ 0xb1, 0xf7, 0x76, 0xfe, 0x2a, 0x6b, 0x7b, 0x2a, 0x53, 0x3a, 0x00, 0x47, 0x44, 0x54,
+ 0x58, 0x40, 0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99,
+ 0x30, 0x03, 0xb8, 0xd6, 0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79,
+ 0x1c, 0x37, 0x68, 0x4e, 0x1d, 0xc0, 0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44,
+ 0xb1, 0x43, 0xd2, 0x9c, 0xfc, 0x12, 0x9e, 0x77, 0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e,
+ 0xfa, 0xc7, 0x10, 0xd5, 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, 0x3a, 0x00, 0x47, 0x44,
+ 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03,
+ 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xdb, 0xe7, 0x5b, 0x3f, 0xa3,
+ 0x42, 0xb0, 0x9c, 0xf8, 0x40, 0x8c, 0xb0, 0x9c, 0xf0, 0x0a, 0xaf, 0xdf, 0x6f, 0xe5,
+ 0x09, 0x21, 0x11, 0x92, 0xe1, 0xf8, 0xc5, 0x09, 0x02, 0x3d, 0x1f, 0xb7, 0xc5, 0x3a,
+ 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xc4, 0xc1, 0xd7, 0x1c, 0x2d, 0x26,
+ 0x89, 0x22, 0xcf, 0xa6, 0x99, 0x77, 0x30, 0x84, 0x86, 0x27, 0x59, 0x8f, 0xd8, 0x08,
+ 0x75, 0xe0, 0xb2, 0xef, 0xf9, 0xfa, 0xa5, 0x40, 0x8c, 0xd3, 0xeb, 0xbb, 0xda, 0xf2,
+ 0xc8, 0xae, 0x41, 0x22, 0x50, 0x9c, 0xe8, 0xb2, 0x9c, 0x9b, 0x3f, 0x8a, 0x78, 0x76,
+ 0xab, 0xd0, 0xbe, 0xfc, 0xe4, 0x79, 0xcb, 0x1b, 0x2b, 0xaa, 0x4d, 0xdd, 0x15, 0x61,
+ 0x42, 0x06, 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
new file mode 100644
index 0000000..50e0e96
--- /dev/null
+++ b/diced/src/lib.rs
@@ -0,0 +1,203 @@
+// 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/keystore2/src/fuzzers/legacy_blob_fuzzer.rs b/diced/src/lib_vendor.rs
similarity index 63%
rename from keystore2/src/fuzzers/legacy_blob_fuzzer.rs
rename to diced/src/lib_vendor.rs
index 7e3e848..01c804b 100644
--- a/keystore2/src/fuzzers/legacy_blob_fuzzer.rs
+++ b/diced/src/lib_vendor.rs
@@ -12,15 +12,9 @@
// 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;
+//! This crate implements the android.hardware.security.dice.IDiceDevice interface
+//! and provides support for implementing a DICE HAL service.
-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);
- }
-});
+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
new file mode 100644
index 0000000..62ca653
--- /dev/null
+++ b/diced/src/permission.rs
@@ -0,0 +1,46 @@
+// 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, Eq)]
+ 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
new file mode 100644
index 0000000..8d883d2
--- /dev/null
+++ b/diced/src/proxy_node_hal.rs
@@ -0,0 +1,119 @@
+// 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
new file mode 100644
index 0000000..99a6dc9
--- /dev/null
+++ b/diced/src/resident_node.rs
@@ -0,0 +1,191 @@
+// 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
new file mode 100644
index 0000000..ff239ed
--- /dev/null
+++ b/diced/src/sample_inputs.rs
@@ -0,0 +1,258 @@
+// 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.")?;
+ // Encoding a single-element array for key ops
+ cbor::encode_header(4 /* CBOR ARRAY */, 1, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode ops array header.")?;
+ // 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
new file mode 100644
index 0000000..03e8969
--- /dev/null
+++ b/diced/src/utils.rs
@@ -0,0 +1,381 @@
+// 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
new file mode 100644
index 0000000..ce3b499
--- /dev/null
+++ b/fsverity/Android.bp
@@ -0,0 +1,64 @@
+// 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,
+ },
+}
+
+python_binary_host {
+ name: "fsverity_manifest_generator",
+ srcs: ["fsverity_manifest_generator.py"],
+ libs: ["fsverity_digests_proto_python"],
+}
+
+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
new file mode 100644
index 0000000..434955c
--- /dev/null
+++ b/fsverity/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<?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
new file mode 100644
index 0000000..f9e7b25
--- /dev/null
+++ b/fsverity/OWNERS
@@ -0,0 +1,5 @@
+alanstokes@google.com
+ebiggers@google.com
+jeffv@google.com
+jiyong@google.com
+victorhsieh@google.com
diff --git a/fsverity/TEST_MAPPING b/fsverity/TEST_MAPPING
new file mode 100644
index 0000000..b327cb8
--- /dev/null
+++ b/fsverity/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "ComposHostTestCases"
+ }
+ ]
+}
diff --git a/ondevice-signing/proto/compos_signature.proto b/fsverity/fsverity_digests.proto
similarity index 63%
rename from ondevice-signing/proto/compos_signature.proto
rename to fsverity/fsverity_digests.proto
index 2f7d09f..816ae61 100644
--- a/ondevice-signing/proto/compos_signature.proto
+++ b/fsverity/fsverity_digests.proto
@@ -16,15 +16,12 @@
syntax = "proto3";
-package compos.proto;
+package android.security.fsverity;
-// Data provided by CompOS to allow validation of a file it generated.
-message Signature {
- // The fs-verity digest (which is derived from the root hash of
- // the Merkle tree) of the file contents.
+message FSVerityDigests {
+ message Digest {
bytes digest = 1;
-
- // Signature of a fsverity_formatted_digest structure containing
- // the digest, signed using CompOS's private key.
- bytes signature = 2;
+ string hash_alg = 2;
+ }
+ map<string, Digest> digests = 1;
}
diff --git a/fsverity/fsverity_manifest_generator.py b/fsverity/fsverity_manifest_generator.py
new file mode 100644
index 0000000..181758a
--- /dev/null
+++ b/fsverity/fsverity_manifest_generator.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 Google Inc. All rights reserved.
+#
+# 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.
+
+"""
+`fsverity_manifest_generator` generates the a manifest file containing digests
+of target files.
+"""
+
+import argparse
+import os
+import subprocess
+import sys
+from fsverity_digests_pb2 import FSVerityDigests
+
+HASH_ALGORITHM = 'sha256'
+
+def _digest(fsverity_path, input_file):
+ cmd = [fsverity_path, 'digest', input_file]
+ cmd.extend(['--compact'])
+ cmd.extend(['--hash-alg', HASH_ALGORITHM])
+ out = subprocess.check_output(cmd, universal_newlines=True).strip()
+ return bytes(bytearray.fromhex(out))
+
+if __name__ == '__main__':
+ p = argparse.ArgumentParser()
+ p.add_argument(
+ '--output',
+ help='Path to the output manifest',
+ required=True)
+ p.add_argument(
+ '--fsverity-path',
+ help='path to the fsverity program',
+ required=True)
+ p.add_argument(
+ '--base-dir',
+ help='directory to use as a relative root for the inputs',
+ required=True)
+ p.add_argument(
+ 'inputs',
+ nargs='*',
+ help='input file for the build manifest')
+ args = p.parse_args(sys.argv[1:])
+
+ digests = FSVerityDigests()
+ for f in sorted(args.inputs):
+ # f is a full path for now; make it relative so it starts with {mount_point}/
+ digest = digests.digests[os.path.relpath(f, args.base_dir)]
+ digest.digest = _digest(args.fsverity_path, f)
+ digest.hash_alg = HASH_ALGORITHM
+
+ manifest = digests.SerializeToString()
+
+ with open(args.output, "wb") as f:
+ f.write(manifest)
diff --git a/fsverity_init/Android.bp b/fsverity_init/Android.bp
index 39d4e6b..83c5945 100644
--- a/fsverity_init/Android.bp
+++ b/fsverity_init/Android.bp
@@ -10,17 +10,34 @@
cc_binary {
name: "fsverity_init",
srcs: [
- "fsverity_init.cpp",
+ "main.cpp",
],
static_libs: [
"libc++fs",
+ "libfsverity_init",
+ "libmini_keyctl_static",
+ ],
+ shared_libs: [
+ "libbase",
+ "libkeyutils",
+ "liblog",
+ ],
+ 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",
- "liblogwrap",
],
cflags: ["-Werror", "-Wall", "-Wextra"],
+ export_include_dirs: ["include"],
+ recovery_available: true,
}
diff --git a/fsverity_init/OWNERS b/fsverity_init/OWNERS
new file mode 100644
index 0000000..f9e7b25
--- /dev/null
+++ b/fsverity_init/OWNERS
@@ -0,0 +1,5 @@
+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 7bc6022..61f84dd 100644
--- a/fsverity_init/fsverity_init.cpp
+++ b/fsverity_init/fsverity_init.cpp
@@ -81,47 +81,3 @@
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;
- }
- 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/fsverity_init/include/fsverity_init.h b/fsverity_init/include/fsverity_init.h
new file mode 100644
index 0000000..c3bc93b
--- /dev/null
+++ b/fsverity_init/include/fsverity_init.h
@@ -0,0 +1,21 @@
+/*
+ * 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
new file mode 100644
index 0000000..3f75dca
--- /dev/null
+++ b/fsverity_init/main.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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 ecdf9a4..9117b7f 100644
--- a/identity/Android.bp
+++ b/identity/Android.bp
@@ -19,12 +19,17 @@
sanitize: {
misc_undefined : ["integer"],
},
- clang : true,
+
}
cc_binary {
name: "credstore",
- defaults: ["identity_defaults"],
+ defaults: [
+ "identity_defaults",
+ "identity_use_latest_hal_aidl_cpp_static",
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ "keymint_use_latest_hal_aidl_cpp_static",
+ ],
srcs: [
"main.cpp",
@@ -33,6 +38,7 @@
"WritableCredential.cpp",
"Credential.cpp",
"CredentialData.cpp",
+ "Session.cpp",
"Util.cpp",
],
init_rc: ["credstore.rc"],
@@ -48,14 +54,15 @@
"android.hardware.identity-support-lib",
"libkeymaster4support",
"libkeystore-attestation-application-id",
- "android.hardware.security.keymint-V1-ndk",
"android.security.authorization-ndk",
+ "android.security.remoteprovisioning-cpp",
+ "libutilscallstack",
],
static_libs: [
- "android.hardware.identity-V3-cpp",
+ "android.hardware.security.rkp-V3-cpp",
"android.hardware.keymaster-V3-cpp",
"libcppbor_external",
- ]
+ ],
}
filegroup {
@@ -75,6 +82,7 @@
"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 7c75d8a..c67fe4a 100644
--- a/identity/Credential.cpp
+++ b/identity/Credential.cpp
@@ -70,10 +70,10 @@
Credential::Credential(CipherSuite cipherSuite, const std::string& dataPath,
const std::string& credentialName, uid_t callingUid,
HardwareInformation hwInfo, sp<IIdentityCredentialStore> halStoreBinder,
- int halApiVersion)
+ sp<IPresentationSession> halSessionBinder, int halApiVersion)
: cipherSuite_(cipherSuite), dataPath_(dataPath), credentialName_(credentialName),
callingUid_(callingUid), hwInfo_(std::move(hwInfo)), halStoreBinder_(halStoreBinder),
- halApiVersion_(halApiVersion) {}
+ halSessionBinder_(halSessionBinder), halApiVersion_(halApiVersion) {}
Credential::~Credential() {}
@@ -85,25 +85,40 @@
"Error loading data for credential");
}
- 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 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);
+ }
+ 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";
@@ -116,7 +131,11 @@
// Returns operation handle
Status Credential::selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys,
- int64_t* _aidl_return) {
+ bool incrementUsageCount, int64_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";
@@ -127,7 +146,7 @@
// 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);
+ data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys, incrementUsageCount);
if (authKey == nullptr) {
return Status::fromServiceSpecificError(
ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE,
@@ -148,10 +167,19 @@
}
int64_t challenge;
- Status status = halBinder_->createAuthChallenge(&challenge);
- if (!status.isOk()) {
- LOG(ERROR) << "Error getting challenge: " << status.exceptionMessage();
- return false;
+ // 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;
+ }
}
if (challenge == 0) {
LOG(ERROR) << "Returned challenge is 0 (bug in HAL or TA)";
@@ -218,7 +246,8 @@
const vector<RequestNamespaceParcel>& requestNamespaces,
const vector<uint8_t>& sessionTranscript,
const vector<uint8_t>& readerSignature, bool allowUsingExhaustedKeys,
- bool allowUsingExpiredKeys, GetEntriesResultParcel* _aidl_return) {
+ bool allowUsingExpiredKeys, bool incrementUsageCount,
+ GetEntriesResultParcel* _aidl_return) {
GetEntriesResultParcel ret;
sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
@@ -228,6 +257,28 @@
"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.
//
@@ -354,33 +405,40 @@
}
}
- // 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.
+ // Reuse the same AuthKey over multiple getEntries() calls.
//
- 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.
+ 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.
//
- // 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");
+ 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;
}
- }
- vector<uint8_t> signingKeyBlob;
- if (authKey != nullptr) {
- signingKeyBlob = authKey->keyBlob;
+ selectedAuthKey_ = true;
}
// Pass the HAL enough information to allow calculating the size of
@@ -405,22 +463,22 @@
}
// 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, signingKeyBlob,
- sessionTranscript, readerSignature, requestCounts);
+ status = halBinder->startRetrieval(selectedProfiles, aidlAuthToken, requestMessage,
+ selectedAuthKeySigningKeyBlob_, 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) {
@@ -453,8 +511,8 @@
}
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) {
@@ -482,7 +540,7 @@
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);
}
@@ -496,16 +554,14 @@
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);
}
- if (authKey != nullptr) {
- ret.staticAuthenticationData = authKey->staticAuthenticationData;
- }
+ ret.staticAuthenticationData = selectedAuthKeyStaticAuthData_;
// Ensure useCount is updated on disk.
- if (authKey != nullptr) {
+ if (updateUseCountOnDisk) {
if (!data->saveToDisk()) {
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error saving data");
@@ -517,6 +573,11 @@
}
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_);
@@ -544,6 +605,12 @@
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_);
@@ -570,6 +637,12 @@
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()) {
@@ -580,19 +653,26 @@
}
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
- 0, // Validity Not Before
- 24 * 60 * 60); // Validity Not After
+ validityNotBefore, validityNotAfter);
if (!pkcs12Bytes) {
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error creating PKCS#12 structure for key pair");
@@ -602,6 +682,11 @@
}
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);
@@ -610,6 +695,11 @@
}
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";
@@ -625,6 +715,11 @@
}
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";
@@ -653,6 +748,11 @@
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";
@@ -681,6 +781,12 @@
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";
@@ -702,6 +808,11 @@
}
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";
@@ -741,6 +852,12 @@
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 a76f3cc..0906fea 100644
--- a/identity/Credential.h
+++ b/identity/Credential.h
@@ -39,6 +39,7 @@
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;
@@ -46,7 +47,8 @@
public:
Credential(CipherSuite cipherSuite, const string& dataPath, const string& credentialName,
uid_t callingUid, HardwareInformation hwInfo,
- sp<IIdentityCredentialStore> halStoreBinder, int halApiVersion);
+ sp<IIdentityCredentialStore> halStoreBinder,
+ sp<IPresentationSession> halSessionBinder, int halApiVersion);
~Credential();
Status ensureOrReplaceHalBinder();
@@ -67,13 +69,14 @@
Status getCredentialKeyCertificateChain(vector<uint8_t>* _aidl_return) override;
Status selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys,
- int64_t* _aidl_return) override;
+ bool incrementUsageCount, 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, GetEntriesResultParcel* _aidl_return) override;
+ bool allowUsingExpiredKeys, bool incrementUsageCount,
+ GetEntriesResultParcel* _aidl_return) override;
Status setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) override;
Status getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_return) override;
@@ -94,12 +97,20 @@
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 74b995d..2189f90 100644
--- a/identity/CredentialData.cpp
+++ b/identity/CredentialData.cpp
@@ -538,7 +538,8 @@
}
const AuthKeyData* CredentialData::selectAuthKey(bool allowUsingExhaustedKeys,
- bool allowUsingExpiredKeys) {
+ bool allowUsingExpiredKeys,
+ bool incrementUsageCount) {
AuthKeyData* candidate;
// First try to find a un-expired key..
@@ -556,7 +557,9 @@
}
}
- candidate->useCount += 1;
+ if (incrementUsageCount) {
+ candidate->useCount += 1;
+ }
return candidate;
}
diff --git a/identity/CredentialData.h b/identity/CredentialData.h
index 24b55d3..e240e47 100644
--- a/identity/CredentialData.h
+++ b/identity/CredentialData.h
@@ -111,7 +111,8 @@
// 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);
+ const AuthKeyData* selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys,
+ bool incrementUsageCount);
optional<vector<vector<uint8_t>>>
getAuthKeysNeedingCertification(const sp<IIdentityCredential>& halBinder);
diff --git a/identity/CredentialStore.cpp b/identity/CredentialStore.cpp
index 071cf24..c5c429b 100644
--- a/identity/CredentialStore.cpp
+++ b/identity/CredentialStore.cpp
@@ -17,20 +17,66 @@
#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) {}
@@ -43,6 +89,16 @@
}
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
@@ -89,13 +145,21 @@
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::getCredentialByName(const std::string& credentialName, int32_t cipherSuite,
+Status CredentialStore::getCredentialCommon(const std::string& credentialName, int32_t cipherSuite,
+ sp<IPresentationSession> halSessionBinder,
sp<ICredential>* _aidl_return) {
*_aidl_return = nullptr;
@@ -113,8 +177,9 @@
// 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_, halApiVersion_);
+ sp<Credential> credential =
+ new Credential(CipherSuite(cipherSuite), dataPath_, credentialName, callingUid, hwInfo_,
+ hal_, halSessionBinder, halApiVersion_);
Status loadStatus = credential->ensureOrReplaceHalBinder();
if (!loadStatus.isOk()) {
@@ -125,6 +190,50 @@
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 15da4eb..df7928e 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,12 +30,16 @@
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:
@@ -44,6 +48,12 @@
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;
@@ -53,12 +63,18 @@
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
new file mode 100644
index 0000000..98ba3d3
--- /dev/null
+++ b/identity/Session.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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
new file mode 100644
index 0000000..116c2fd
--- /dev/null
+++ b/identity/Session.h
@@ -0,0 +1,77 @@
+/*
+ * 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/binder/android/security/identity/ICredential.aidl b/identity/binder/android/security/identity/ICredential.aidl
index 2165810..e6a9fae 100644
--- a/identity/binder/android/security/identity/ICredential.aidl
+++ b/identity/binder/android/security/identity/ICredential.aidl
@@ -49,14 +49,16 @@
byte[] getCredentialKeyCertificateChain();
long selectAuthKey(in boolean allowUsingExhaustedKeys,
- in boolean allowUsingExpiredKeys);
+ in boolean allowUsingExpiredKeys,
+ in boolean incrementUsageCount);
GetEntriesResultParcel getEntries(in byte[] requestMessage,
in RequestNamespaceParcel[] requestNamespaces,
in byte[] sessionTranscript,
in byte[] readerSignature,
in boolean allowUsingExhaustedKeys,
- in boolean allowUsingExpiredKeys);
+ in boolean allowUsingExpiredKeys,
+ in boolean incrementUsageCount);
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 8357f47..39b5e5f 100644
--- a/identity/binder/android/security/identity/ICredentialStore.aidl
+++ b/identity/binder/android/security/identity/ICredentialStore.aidl
@@ -19,6 +19,7 @@
import android.security.identity.IWritableCredential;
import android.security.identity.ICredential;
import android.security.identity.SecurityHardwareInfoParcel;
+import android.security.identity.ISession;
/**
* @hide
@@ -45,6 +46,9 @@
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
new file mode 100644
index 0000000..2139ec1
--- /dev/null
+++ b/identity/binder/android/security/identity/ISession.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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/src/java/com/android/security/identity/internal/Iso18013.java b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java
index 6da90e5..b47009b 100644
--- a/identity/util/src/java/com/android/security/identity/internal/Iso18013.java
+++ b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java
@@ -145,14 +145,11 @@
// 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);
+
+ ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
+ baos.write(Util.convertP256PublicKeyToDERFormat(w));
+
baos.write(new byte[]{42, 44});
} catch (IOException e) {
e.printStackTrace();
@@ -279,18 +276,4 @@
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
index b74efb7..ee12cd0 100644
--- a/identity/util/src/java/com/android/security/identity/internal/Util.java
+++ b/identity/util/src/java/com/android/security/identity/internal/Util.java
@@ -401,8 +401,10 @@
if (signature.length != 64) {
throw new RuntimeException("signature.length is " + signature.length + ", expected 64");
}
- BigInteger r = new BigInteger(Arrays.copyOfRange(signature, 0, 32));
- BigInteger s = new BigInteger(Arrays.copyOfRange(signature, 32, 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();
@@ -1128,6 +1130,48 @@
Log.e(TAG, name + ": dumping " + data.length + " bytes\n" + fmt.toString());
}
+ // Convert EC P256 public key to DER format binary format
+ public static byte[] convertP256PublicKeyToDERFormat(ECPoint w) {
+ byte[] ret = new byte[64];
+
+ // Each coordinate may be encoded in 33*, 32, or fewer bytes.
+ //
+ // * : it can be 33 bytes because toByteArray() guarantees "The array will contain the
+ // minimum number of bytes required to represent this BigInteger, including at
+ // least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that
+ // the MSB is always 0x00. This is taken care of by calling calling
+ // stripLeadingZeroes().
+ //
+ // We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2
+ // and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y
+ // where X and Y are encoded in exactly 32 byte, big endian integer values each.
+ //
+ byte[] xBytes = stripLeadingZeroes(w.getAffineX().toByteArray());
+ if (xBytes.length > 32) {
+ throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected");
+ }
+ int numLeadingZeroBytes = 32 - xBytes.length;
+ for (int n = 0; n < numLeadingZeroBytes; n++) {
+ ret[n] = 0x00;
+ }
+ for (int n = 0; n < xBytes.length; n++) {
+ ret[numLeadingZeroBytes + n] = xBytes[n];
+ }
+
+ byte[] yBytes = stripLeadingZeroes(w.getAffineY().toByteArray());
+ if (yBytes.length > 32) {
+ throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected");
+ }
+ numLeadingZeroBytes = 32 - yBytes.length;
+ for (int n = 0; n < numLeadingZeroBytes; n++) {
+ ret[32 + n] = 0x00;
+ }
+ for (int n = 0; n < yBytes.length; n++) {
+ ret[32 + numLeadingZeroBytes + n] = yBytes[n];
+ }
+
+ return ret;
+ }
// This returns a SessionTranscript which satisfy the requirement
// that the uncompressed X and Y coordinates of the public key for the
@@ -1139,14 +1183,11 @@
// 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);
+
+ ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
+ baos.write(convertP256PublicKeyToDERFormat(w));
+
baos.write(new byte[]{43, 44});
} catch (IOException e) {
e.printStackTrace();
diff --git a/keystore-engine/keystore2_engine.cpp b/keystore-engine/keystore2_engine.cpp
index ee550ca..69caf51 100644
--- a/keystore-engine/keystore2_engine.cpp
+++ b/keystore-engine/keystore2_engine.cpp
@@ -23,11 +23,13 @@
#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>
@@ -327,6 +329,31 @@
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
@@ -383,13 +410,7 @@
return nullptr;
}
- 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()));
+ auto pkey = extractPubKey(*response.metadata.certificate);
if (!pkey) {
LOG(ERROR) << AT << "Failed to extract public key.";
return nullptr;
diff --git a/keystore/Android.bp b/keystore/Android.bp
index ad4b4b1..221ead9 100644
--- a/keystore/Android.bp
+++ b/keystore/Android.bp
@@ -31,12 +31,14 @@
],
},
- clang: true,
}
cc_binary {
name: "keystore_cli_v2",
- defaults: ["keystore_defaults"],
+ defaults: [
+ "keystore_defaults",
+ "keystore2_use_latest_aidl_ndk_shared",
+ ],
cflags: [
"-DKEYMASTER_NAME_TAGS",
@@ -48,7 +50,6 @@
],
shared_libs: [
"android.security.apc-ndk",
- "android.system.keystore2-V1-ndk",
"libbinder",
"libbinder_ndk",
"libchrome",
@@ -63,7 +64,7 @@
// Library used by both keystore and credstore for generating the ASN.1 stored
// in Tag::ATTESTATION_APPLICATION_ID
-cc_library_shared {
+cc_library {
name: "libkeystore-attestation-application-id",
defaults: ["keystore_defaults"],
diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp
index 43f72a9..d01c67d 100644
--- a/keystore/keystore_cli_v2.cpp
+++ b/keystore/keystore_cli_v2.cpp
@@ -19,6 +19,7 @@
#include <iostream>
#include <memory>
#include <string>
+#include <variant>
#include <vector>
#include <base/command_line.h>
@@ -616,9 +617,9 @@
return std::move(parameters);
}
-keymint::AuthorizationSet GetECDSAParameters(uint32_t key_size, bool sha256_only) {
+keymint::AuthorizationSet GetECDSAParameters(keymint::EcCurve curve, bool sha256_only) {
keymint::AuthorizationSetBuilder parameters;
- parameters.EcdsaSigningKey(key_size)
+ parameters.EcdsaSigningKey(curve)
.Digest(keymint::Digest::SHA_2_256)
.Authorization(keymint::TAG_NO_AUTH_REQUIRED);
if (!sha256_only) {
@@ -662,11 +663,12 @@
{"RSA-2048 Encrypt", true, GetRSAEncryptParameters(2048)},
{"RSA-3072 Encrypt", false, GetRSAEncryptParameters(3072)},
{"RSA-4096 Encrypt", false, GetRSAEncryptParameters(4096)},
- {"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)},
+ {"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)},
{"AES-128", true, GetAESParameters(128, false)},
{"AES-256", true, GetAESParameters(256, false)},
{"AES-128-GCM", false, GetAESParameters(128, true)},
@@ -1023,7 +1025,7 @@
return 1;
}
- auto listener = std::make_shared<ConfirmationListener>();
+ auto listener = ndk::SharedRefBase::make<ConfirmationListener>();
auto future = listener->get_future();
auto rc = apcService->presentPrompt(listener, promptText, extraData, locale, uiOptionsAsFlags);
diff --git a/keystore/tests/confirmationui_invocation_test.cpp b/keystore/tests/confirmationui_invocation_test.cpp
index 7f8a373..822e6a4 100644
--- a/keystore/tests/confirmationui_invocation_test.cpp
+++ b/keystore/tests/confirmationui_invocation_test.cpp
@@ -55,7 +55,7 @@
std::string locale("en");
std::vector<uint8_t> extraData{0xaa, 0xff, 0x00, 0x55};
- auto listener = std::make_shared<ConfirmationListener>();
+ auto listener = ndk::SharedRefBase::make<ConfirmationListener>();
auto future = listener->get_future();
diff --git a/keystore/tests/fuzzer/Android.bp b/keystore/tests/fuzzer/Android.bp
new file mode 100644
index 0000000..4116ae1
--- /dev/null
+++ b/keystore/tests/fuzzer/Android.bp
@@ -0,0 +1,108 @@
+/*
+ * 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
new file mode 100644
index 0000000..25d53ab
--- /dev/null
+++ b/keystore/tests/fuzzer/README.md
@@ -0,0 +1,103 @@
+# 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
new file mode 100644
index 0000000..0eddb9a
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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
new file mode 100644
index 0000000..581da46
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreAttestationId_fuzzer.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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
new file mode 100644
index 0000000..7af3ba8
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreCommon.h
@@ -0,0 +1,77 @@
+/*
+ * 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
new file mode 100644
index 0000000..1e033c8
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreGetWifiHidl_fuzzer.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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
new file mode 100644
index 0000000..63899ff
--- /dev/null
+++ b/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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
new file mode 100644
index 0000000..b8f8a73
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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 7c4f61b..51ce9d1 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -25,9 +25,13 @@
name: "libkeystore2_defaults",
crate_name: "keystore2",
srcs: ["src/lib.rs"],
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ "keystore2_use_latest_aidl_rust",
+ ],
rustlibs: [
- "android.hardware.security.keymint-V1-rust",
+ "android.hardware.security.rkp-V3-rust",
"android.hardware.security.secureclock-V1-rust",
"android.hardware.security.sharedsecret-V1-rust",
"android.os.permissions_aidl-rust",
@@ -37,7 +41,6 @@
"android.security.maintenance-rust",
"android.security.metrics-rust",
"android.security.remoteprovisioning-rust",
- "android.system.keystore2-V1-rust",
"libanyhow",
"libbinder_rs",
"libkeystore2_aaid-rust",
@@ -48,12 +51,12 @@
"libkeystore2_vintf_rust",
"liblazy_static",
"liblibc",
- "liblibsqlite3_sys",
"liblog_event_list",
"liblog_rust",
"librand",
- "librusqlite",
"librustutils",
+ "libserde",
+ "libserde_cbor",
"libthiserror",
],
shared_libs: [
@@ -67,39 +70,69 @@
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",
+ "keystore2_use_latest_aidl_rust",
+ ],
rustlibs: [
+ "android.hardware.security.rkp-V3-rust",
+ "libbinder_rs",
"libkeystore2_selinux",
"liblog_rust",
"libnix",
"librand",
"libserde",
"libserde_cbor",
+ "libthiserror",
+ "libanyhow",
+ ],
+}
+
+rust_library {
+ name: "libkeystore2_with_test_utils",
+ defaults: ["libkeystore2_defaults"],
+ features: [
+ "keystore2_blob_test_utils",
+ ],
+ rustlibs: [
+ "liblibsqlite3_sys",
+ "librusqlite",
+ "libkeystore2_test_utils",
],
}
rust_test {
name: "keystore2_test_utils_test",
srcs: ["test_utils/lib.rs"],
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ "keystore2_use_latest_aidl_rust",
+ ],
test_suites: ["general-tests"],
- // TODO Remove custom test_config and enable the following two lines when
- // b/200602232 was resolved.
- // require_root: true,
- // auto_gen_config: true,
- test_config: "test_utils/AndroidTest.xml",
+ require_root: true,
+ auto_gen_config: true,
compile_multilib: "first",
rustlibs: [
+ "android.hardware.security.rkp-V3-rust",
+ "libbinder_rs",
"libkeystore2_selinux",
"liblog_rust",
"libnix",
"librand",
"libserde",
"libserde_cbor",
+ "libthiserror",
+ "libanyhow",
],
}
@@ -113,24 +146,25 @@
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_binary {
- name: "keystore2",
+rust_defaults {
+ name: "keystore2_defaults",
srcs: ["src/keystore2_main.rs"],
rustlibs: [
"libandroid_logger",
"libbinder_rs",
- "libkeystore2",
"liblog_rust",
- "liblegacykeystore-rust",
- "librusqlite",
],
init_rc: ["keystore2.rc"],
@@ -142,30 +176,18 @@
// 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",
- "android.security.compat-ndk",
- "libc",
- "libdl_android",
- "libdl",
- "libandroidicu",
- "libkeymint",
- "libkeystore2_aaid",
- "libkeystore2_apc_compat",
- "libkeystore2_crypto",
- "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 127ff1e..5d0a7dd 100644
--- a/keystore2/TEST_MAPPING
+++ b/keystore2/TEST_MAPPING
@@ -13,7 +13,23 @@
"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/aidl/Android.bp b/keystore2/aidl/Android.bp
index 4a7b7b4..e3961da 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -24,12 +24,11 @@
aidl_interface {
name: "android.security.attestationmanager",
srcs: [ "android/security/attestationmanager/*.aidl", ],
- imports: [ "android.hardware.security.keymint-V1" ],
+ imports: [ "android.hardware.security.keymint-V3" ],
unstable: true,
backend: {
java: {
platform_apis: true,
- srcs_available: true,
},
rust: {
enabled: true,
@@ -45,14 +44,13 @@
name: "android.security.authorization",
srcs: [ "android/security/authorization/*.aidl" ],
imports: [
- "android.hardware.security.keymint-V1",
+ "android.hardware.security.keymint-V3",
"android.hardware.security.secureclock-V1",
],
unstable: true,
backend: {
java: {
platform_apis: true,
- srcs_available: true,
},
rust: {
enabled: true,
@@ -71,7 +69,6 @@
backend: {
java: {
enabled: true,
- srcs_available: true,
},
rust: {
enabled: true,
@@ -86,7 +83,7 @@
name: "android.security.compat",
srcs: [ "android/security/compat/*.aidl" ],
imports: [
- "android.hardware.security.keymint-V1",
+ "android.hardware.security.keymint-V3",
"android.hardware.security.secureclock-V1",
"android.hardware.security.sharedsecret-V1",
],
@@ -94,7 +91,6 @@
backend: {
java: {
platform_apis: true,
- srcs_available: true,
},
rust: {
enabled: true,
@@ -110,13 +106,13 @@
name: "android.security.remoteprovisioning",
srcs: [ "android/security/remoteprovisioning/*.aidl" ],
imports: [
- "android.hardware.security.keymint-V1",
+ "android.hardware.security.keymint-V3",
+ "android.hardware.security.rkp-V3",
],
unstable: true,
backend: {
java: {
platform_apis: true,
- srcs_available: true,
},
ndk: {
enabled: true,
@@ -132,13 +128,12 @@
name: "android.security.maintenance",
srcs: [ "android/security/maintenance/*.aidl" ],
imports: [
- "android.system.keystore2-V1",
+ "android.system.keystore2-V3",
],
unstable: true,
backend: {
java: {
platform_apis: true,
- srcs_available: true,
},
rust: {
enabled: true,
@@ -157,7 +152,6 @@
backend: {
java: {
platform_apis: true,
- srcs_available: true,
},
rust: {
enabled: true,
@@ -173,13 +167,12 @@
name: "android.security.metrics",
srcs: [ "android/security/metrics/*.aidl" ],
imports: [
- "android.system.keystore2-V1",
+ "android.system.keystore2-V3",
],
unstable: true,
backend: {
java: {
platform_apis: true,
- srcs_available: true,
},
rust: {
enabled: true,
@@ -191,3 +184,68 @@
},
}
+// java_defaults that includes the latest Keystore2 AIDL library.
+// Modules that depend on KeyMint directly can include this java_defaults to avoid
+// managing dependency versions explicitly.
+java_defaults {
+ name: "keystore2_use_latest_aidl_java_static",
+ static_libs: [
+ "android.system.keystore2-V3-java-source"
+ ],
+}
+
+java_defaults {
+ name: "keystore2_use_latest_aidl_java_shared",
+ libs: [
+ "android.system.keystore2-V3-java-source"
+ ],
+}
+
+java_defaults {
+ name: "keystore2_use_latest_aidl_java",
+ libs: [
+ "android.system.keystore2-V3-java"
+ ],
+}
+
+// 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-V3-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "keystore2_use_latest_aidl_ndk_shared",
+ shared_libs: [
+ "android.system.keystore2-V3-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "keystore2_use_latest_aidl_cpp_shared",
+ shared_libs: [
+ "android.system.keystore2-V3-cpp",
+ ],
+}
+
+cc_defaults {
+ name: "keystore2_use_latest_aidl_cpp_static",
+ static_libs: [
+ "android.system.keystore2-V3-cpp",
+ ],
+}
+
+
+// A rust_defaults that includes the latest Keystore2 AIDL library.
+// Modules that depend on Keystore2 directly can include this rust_defaults to avoid
+// managing dependency versions explicitly.
+rust_defaults {
+ name: "keystore2_use_latest_aidl_rust",
+ rustlibs: [
+ "android.system.keystore2-V3-rust",
+ ],
+}
diff --git a/keystore2/aidl/android/security/compat/IKeystoreCompatService.aidl b/keystore2/aidl/android/security/compat/IKeystoreCompatService.aidl
index 50bfa19..8e347f0 100644
--- a/keystore2/aidl/android/security/compat/IKeystoreCompatService.aidl
+++ b/keystore2/aidl/android/security/compat/IKeystoreCompatService.aidl
@@ -29,8 +29,17 @@
*/
interface IKeystoreCompatService {
/**
- * Return an implementation of IKeyMintDevice, that it implemented by Keystore 2.0 itself
- * by means of Keymaster 4.1 or lower.
+ * 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.
*/
IKeyMintDevice getKeyMintDevice (SecurityLevel securityLevel);
diff --git a/keystore2/aidl/android/security/legacykeystore/ILegacyKeystore.aidl b/keystore2/aidl/android/security/legacykeystore/ILegacyKeystore.aidl
index fe93673..e65efaa 100644
--- a/keystore2/aidl/android/security/legacykeystore/ILegacyKeystore.aidl
+++ b/keystore2/aidl/android/security/legacykeystore/ILegacyKeystore.aidl
@@ -95,4 +95,4 @@
* @param uid legacy namespace to list. Specify UID_SELF for caller's namespace.
*/
String[] list(in String prefix, int uid);
-}
+}
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/metrics/EcCurve.aidl b/keystore2/aidl/android/security/metrics/EcCurve.aidl
index b190d83..7b1a5a2 100644
--- a/keystore2/aidl/android/security/metrics/EcCurve.aidl
+++ b/keystore2/aidl/android/security/metrics/EcCurve.aidl
@@ -29,4 +29,5 @@
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 616d129..dcd5122 100644
--- a/keystore2/aidl/android/security/metrics/RkpErrorStats.aidl
+++ b/keystore2/aidl/android/security/metrics/RkpErrorStats.aidl
@@ -17,6 +17,7 @@
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
@@ -24,4 +25,5 @@
@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
new file mode 100644
index 0000000..7d45e52
--- /dev/null
+++ b/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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
new file mode 100644
index 0000000..ae21855
--- /dev/null
+++ b/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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 6b8d0cb..45f995c 100644
--- a/keystore2/android.system.keystore2-service.xml
+++ b/keystore2/android.system.keystore2-service.xml
@@ -1,6 +1,7 @@
<manifest version="1.0" type="framework">
<hal format="aidl">
<name>android.system.keystore2</name>
+ <version>3</version>
<interface>
<name>IKeystoreService</name>
<instance>default</instance>
diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp
index df7521e..61697a8 100644
--- a/keystore2/apc_compat/Android.bp
+++ b/keystore2/apc_compat/Android.bp
@@ -27,7 +27,9 @@
"apc_compat.cpp",
],
shared_libs: [
+ "libbinder_ndk",
"android.hardware.confirmationui@1.0",
+ "android.hardware.confirmationui-V1-ndk",
"libbase",
"libhidlbase",
"libutils",
diff --git a/keystore2/apc_compat/apc_compat.cpp b/keystore2/apc_compat/apc_compat.cpp
index 08a8e45..9f60db2 100644
--- a/keystore2/apc_compat/apc_compat.cpp
+++ b/keystore2/apc_compat/apc_compat.cpp
@@ -19,6 +19,12 @@
#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
#include <hwbinder/IBinder.h>
+#include <aidl/android/hardware/confirmationui/BnConfirmationResultCallback.h>
+#include <aidl/android/hardware/confirmationui/IConfirmationResultCallback.h>
+#include <aidl/android/hardware/confirmationui/IConfirmationUI.h>
+#include <aidl/android/hardware/confirmationui/UIOption.h>
+#include <android/binder_manager.h>
+
#include <memory>
#include <string>
#include <thread>
@@ -33,41 +39,52 @@
using android::hardware::hidl_vec;
using android::hardware::Return;
using android::hardware::Status;
-using android::hardware::confirmationui::V1_0::IConfirmationResultCallback;
-using android::hardware::confirmationui::V1_0::IConfirmationUI;
+using HidlConfirmationResultCb =
+ android::hardware::confirmationui::V1_0::IConfirmationResultCallback;
+using HidlConfirmationUI = android::hardware::confirmationui::V1_0::IConfirmationUI;
using android::hardware::confirmationui::V1_0::ResponseCode;
-using android::hardware::confirmationui::V1_0::UIOption;
+using HidlUIOptions = android::hardware::confirmationui::V1_0::UIOption;
-static uint32_t responseCode2Compat(ResponseCode rc) {
- switch (rc) {
- case ResponseCode::OK:
- return APC_COMPAT_ERROR_OK;
- case ResponseCode::Canceled:
- return APC_COMPAT_ERROR_CANCELLED;
- case ResponseCode::Aborted:
- return APC_COMPAT_ERROR_ABORTED;
- case ResponseCode::OperationPending:
- return APC_COMPAT_ERROR_OPERATION_PENDING;
- case ResponseCode::Ignored:
- return APC_COMPAT_ERROR_IGNORED;
- case ResponseCode::SystemError:
- case ResponseCode::Unimplemented:
- case ResponseCode::Unexpected:
- case ResponseCode::UIError:
- case ResponseCode::UIErrorMissingGlyph:
- case ResponseCode::UIErrorMessageTooLong:
- case ResponseCode::UIErrorMalformedUTF8Encoding:
- default:
- return APC_COMPAT_ERROR_SYSTEM_ERROR;
- }
-}
+using AidlConfirmationUI = ::aidl::android::hardware::confirmationui::IConfirmationUI;
+using AidlBnConfirmationResultCb =
+ ::aidl::android::hardware::confirmationui::BnConfirmationResultCallback;
+using AidlUIOptions = ::aidl::android::hardware::confirmationui::UIOption;
-class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_death_recipient {
+class CompatSessionCB {
public:
- static sp<ConfuiCompatSession>* tryGetService() {
- sp<IConfirmationUI> service = IConfirmationUI::tryGetService();
+ void
+ finalize(uint32_t responseCode, ApcCompatCallback callback,
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> dataConfirmed,
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> confirmationToken) {
+ if (callback.result != nullptr) {
+ size_t dataConfirmedSize = 0;
+ const uint8_t* dataConfirmedPtr = nullptr;
+ size_t confirmationTokenSize = 0;
+ const uint8_t* confirmationTokenPtr = nullptr;
+ if (responseCode == APC_COMPAT_ERROR_OK) {
+ if (dataConfirmed) {
+ dataConfirmedPtr = dataConfirmed->get().data();
+ dataConfirmedSize = dataConfirmed->get().size();
+ }
+ if (confirmationToken) {
+ confirmationTokenPtr = confirmationToken->get().data();
+ confirmationTokenSize = confirmationToken->get().size();
+ }
+ }
+ callback.result(callback.data, responseCode, dataConfirmedPtr, dataConfirmedSize,
+ confirmationTokenPtr, confirmationTokenSize);
+ }
+ }
+};
+
+class ConfuiHidlCompatSession : public HidlConfirmationResultCb,
+ public hidl_death_recipient,
+ public CompatSessionCB {
+ public:
+ static sp<ConfuiHidlCompatSession> tryGetService() {
+ sp<HidlConfirmationUI> service = HidlConfirmationUI::tryGetService();
if (service) {
- return new sp(new ConfuiCompatSession(std::move(service)));
+ return sp<ConfuiHidlCompatSession>(new ConfuiHidlCompatSession(std::move(service)));
} else {
return nullptr;
}
@@ -78,13 +95,12 @@
const char* locale, ApcCompatUiOptions ui_options) {
std::string hidl_prompt(prompt_text);
std::vector<uint8_t> hidl_extra(extra_data, extra_data + extra_data_size);
- std::string hidl_locale(locale);
- std::vector<UIOption> hidl_ui_options;
+ std::vector<HidlUIOptions> hidl_ui_options;
if (ui_options.inverted) {
- hidl_ui_options.push_back(UIOption::AccessibilityInverted);
+ hidl_ui_options.push_back(HidlUIOptions::AccessibilityInverted);
}
if (ui_options.magnified) {
- hidl_ui_options.push_back(UIOption::AccessibilityMagnified);
+ hidl_ui_options.push_back(HidlUIOptions::AccessibilityMagnified);
}
auto lock = std::lock_guard(callback_lock_);
if (callback_.result != nullptr) {
@@ -98,7 +114,7 @@
return APC_COMPAT_ERROR_SYSTEM_ERROR;
}
- auto rc = service_->promptUserConfirmation(sp(this), hidl_prompt, hidl_extra, hidl_locale,
+ auto rc = service_->promptUserConfirmation(sp(this), hidl_prompt, hidl_extra, locale,
hidl_ui_options);
if (!rc.isOk()) {
LOG(ERROR) << "Communication error: promptUserConfirmation: " << rc.description();
@@ -111,10 +127,8 @@
void abort() { service_->abort(); }
- void
- finalize(ResponseCode responseCode,
- std::optional<std::reference_wrapper<const hidl_vec<uint8_t>>> dataConfirmed,
- std::optional<std::reference_wrapper<const hidl_vec<uint8_t>>> confirmationToken) {
+ void finalize(ResponseCode responseCode, const hidl_vec<uint8_t>& dataConfirmed,
+ const hidl_vec<uint8_t>& confirmationToken) {
ApcCompatCallback callback;
{
auto lock = std::lock_guard(callback_lock_);
@@ -128,26 +142,14 @@
if (callback.result != nullptr) {
service_->unlinkToDeath(sp(this));
- size_t dataConfirmedSize = 0;
- const uint8_t* dataConfirmedPtr = nullptr;
- size_t confirmationTokenSize = 0;
- const uint8_t* confirmationTokenPtr = nullptr;
- if (responseCode == ResponseCode::OK) {
- if (dataConfirmed) {
- dataConfirmedPtr = dataConfirmed->get().data();
- dataConfirmedSize = dataConfirmed->get().size();
- }
- if (dataConfirmed) {
- confirmationTokenPtr = confirmationToken->get().data();
- confirmationTokenSize = confirmationToken->get().size();
- }
- }
- callback.result(callback.data, responseCode2Compat(responseCode), dataConfirmedPtr,
- dataConfirmedSize, confirmationTokenPtr, confirmationTokenSize);
+ std::vector<uint8_t> data = dataConfirmed;
+ std::vector<uint8_t> token = confirmationToken;
+
+ CompatSessionCB::finalize(responseCode2Compat(responseCode), callback, data, token);
}
}
- // IConfirmationResultCallback overrides:
+ // HidlConfirmationResultCb overrides:
android::hardware::Return<void> result(ResponseCode responseCode,
const hidl_vec<uint8_t>& dataConfirmed,
const hidl_vec<uint8_t>& confirmationToken) override {
@@ -160,10 +162,34 @@
finalize(ResponseCode::SystemError, {}, {});
}
+ static uint32_t responseCode2Compat(ResponseCode rc) {
+ switch (rc) {
+ case ResponseCode::OK:
+ return APC_COMPAT_ERROR_OK;
+ case ResponseCode::Canceled:
+ return APC_COMPAT_ERROR_CANCELLED;
+ case ResponseCode::Aborted:
+ return APC_COMPAT_ERROR_ABORTED;
+ case ResponseCode::OperationPending:
+ return APC_COMPAT_ERROR_OPERATION_PENDING;
+ case ResponseCode::Ignored:
+ return APC_COMPAT_ERROR_IGNORED;
+ case ResponseCode::SystemError:
+ case ResponseCode::Unimplemented:
+ case ResponseCode::Unexpected:
+ case ResponseCode::UIError:
+ case ResponseCode::UIErrorMissingGlyph:
+ case ResponseCode::UIErrorMessageTooLong:
+ case ResponseCode::UIErrorMalformedUTF8Encoding:
+ default:
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+ }
+
private:
- ConfuiCompatSession(sp<IConfirmationUI> service)
+ ConfuiHidlCompatSession(sp<HidlConfirmationUI> service)
: service_(service), callback_{nullptr, nullptr} {}
- sp<IConfirmationUI> service_;
+ sp<HidlConfirmationUI> service_;
// The callback_lock_ protects the callback_ field against concurrent modification.
// IMPORTANT: It must never be held while calling the call back.
@@ -171,34 +197,248 @@
ApcCompatCallback callback_;
};
+class ConfuiAidlCompatSession : public AidlBnConfirmationResultCb, public CompatSessionCB {
+ public:
+ static std::shared_ptr<ConfuiAidlCompatSession> tryGetService() {
+ constexpr const char confirmationUIServiceName[] =
+ "android.hardware.confirmationui.IConfirmationUI/default";
+ if (!AServiceManager_isDeclared(confirmationUIServiceName)) {
+ LOG(INFO) << confirmationUIServiceName << " is not declared in VINTF";
+ return nullptr;
+ }
+ std::shared_ptr<AidlConfirmationUI> aidlService = AidlConfirmationUI::fromBinder(
+ ndk::SpAIBinder(AServiceManager_waitForService(confirmationUIServiceName)));
+ if (aidlService) {
+ return ::ndk::SharedRefBase::make<ConfuiAidlCompatSession>(aidlService);
+ }
+
+ return nullptr;
+ }
+
+ uint32_t promptUserConfirmation(ApcCompatCallback callback, const char* prompt_text,
+ const uint8_t* extra_data, size_t extra_data_size,
+ const char* locale, ApcCompatUiOptions ui_options) {
+ std::vector<uint8_t> aidl_prompt(prompt_text, prompt_text + strlen(prompt_text));
+ std::vector<uint8_t> aidl_extra(extra_data, extra_data + extra_data_size);
+ std::vector<AidlUIOptions> aidl_ui_options;
+ if (ui_options.inverted) {
+ aidl_ui_options.push_back(AidlUIOptions::ACCESSIBILITY_INVERTED);
+ }
+ if (ui_options.magnified) {
+ aidl_ui_options.push_back(AidlUIOptions::ACCESSIBILITY_MAGNIFIED);
+ }
+ auto lock = std::lock_guard(callback_lock_);
+ if (callback_.result != nullptr) {
+ return APC_COMPAT_ERROR_OPERATION_PENDING;
+ }
+
+ if (!aidlService_) {
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+ auto linkRet =
+ AIBinder_linkToDeath(aidlService_->asBinder().get(), death_recipient_.get(), this);
+ if (linkRet != STATUS_OK) {
+ LOG(ERROR) << "Communication error: promptUserConfirmation: "
+ "Trying to register death recipient: ";
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+
+ auto rc = aidlService_->promptUserConfirmation(ref<ConfuiAidlCompatSession>(), aidl_prompt,
+ aidl_extra, locale, aidl_ui_options);
+ int ret = getReturnCode(rc);
+ if (ret == AidlConfirmationUI::OK) {
+ callback_ = callback;
+ } else {
+ LOG(ERROR) << "Communication error: promptUserConfirmation: " << rc.getDescription();
+ }
+ return responseCode2Compat(ret);
+ }
+
+ void abort() {
+ if (aidlService_) {
+ aidlService_->abort();
+ }
+ }
+
+ void
+ finalize(int32_t responseCode,
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> dataConfirmed,
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> confirmationToken) {
+ ApcCompatCallback callback;
+ {
+ auto lock = std::lock_guard(callback_lock_);
+ // Calling the callback consumes the callback data structure. We have to make
+ // sure that it can only be called once.
+ callback = callback_;
+ callback_ = {nullptr, nullptr};
+ // Unlock the callback_lock_ here. It must never be held while calling the callback.
+ }
+
+ if (callback.result != nullptr) {
+ if (aidlService_) {
+ AIBinder_unlinkToDeath(aidlService_->asBinder().get(), death_recipient_.get(),
+ this);
+ }
+ CompatSessionCB::finalize(responseCode2Compat(responseCode), callback, dataConfirmed,
+ confirmationToken);
+ }
+ }
+
+ // AidlBnConfirmationResultCb overrides:
+ ::ndk::ScopedAStatus result(int32_t responseCode, const std::vector<uint8_t>& dataConfirmed,
+ const std::vector<uint8_t>& confirmationToken) override {
+ finalize(responseCode, dataConfirmed, confirmationToken);
+ return ::ndk::ScopedAStatus::ok();
+ };
+
+ void serviceDied() {
+ aidlService_.reset();
+ aidlService_ = nullptr;
+ finalize(AidlConfirmationUI::SYSTEM_ERROR, {}, {});
+ }
+
+ static void binderDiedCallbackAidl(void* ptr) {
+ LOG(ERROR) << __func__ << " : ConfuiAidlCompatSession Service died.";
+ auto aidlSession = static_cast<ConfuiAidlCompatSession*>(ptr);
+ if (aidlSession == nullptr) {
+ LOG(ERROR) << __func__ << ": Null ConfuiAidlCompatSession HAL died.";
+ return;
+ }
+ aidlSession->serviceDied();
+ }
+
+ int getReturnCode(const ::ndk::ScopedAStatus& result) {
+ if (result.isOk()) return AidlConfirmationUI::OK;
+
+ if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+ return static_cast<int>(result.getServiceSpecificError());
+ }
+ return result.getStatus();
+ }
+
+ uint32_t responseCode2Compat(int32_t rc) {
+ switch (rc) {
+ case AidlConfirmationUI::OK:
+ return APC_COMPAT_ERROR_OK;
+ case AidlConfirmationUI::CANCELED:
+ return APC_COMPAT_ERROR_CANCELLED;
+ case AidlConfirmationUI::ABORTED:
+ return APC_COMPAT_ERROR_ABORTED;
+ case AidlConfirmationUI::OPERATION_PENDING:
+ return APC_COMPAT_ERROR_OPERATION_PENDING;
+ case AidlConfirmationUI::IGNORED:
+ return APC_COMPAT_ERROR_IGNORED;
+ case AidlConfirmationUI::SYSTEM_ERROR:
+ case AidlConfirmationUI::UNIMPLEMENTED:
+ case AidlConfirmationUI::UNEXPECTED:
+ case AidlConfirmationUI::UI_ERROR:
+ case AidlConfirmationUI::UI_ERROR_MISSING_GLYPH:
+ case AidlConfirmationUI::UI_ERROR_MESSAGE_TOO_LONG:
+ case AidlConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING:
+ default:
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+ }
+
+ ConfuiAidlCompatSession(std::shared_ptr<AidlConfirmationUI> service)
+ : aidlService_(service), callback_{nullptr, nullptr} {
+ death_recipient_ = ::ndk::ScopedAIBinder_DeathRecipient(
+ AIBinder_DeathRecipient_new(binderDiedCallbackAidl));
+ }
+
+ virtual ~ConfuiAidlCompatSession() = default;
+ ConfuiAidlCompatSession(const ConfuiAidlCompatSession&) = delete;
+ ConfuiAidlCompatSession& operator=(const ConfuiAidlCompatSession&) = delete;
+
+ private:
+ std::shared_ptr<AidlConfirmationUI> aidlService_;
+
+ // The callback_lock_ protects the callback_ field against concurrent modification.
+ // IMPORTANT: It must never be held while calling the call back.
+ std::mutex callback_lock_;
+ ApcCompatCallback callback_;
+
+ ::ndk::ScopedAIBinder_DeathRecipient death_recipient_;
+};
+
+class ApcCompatSession {
+ public:
+ static ApcCompatServiceHandle getApcCompatSession() {
+ auto aidlCompatSession = ConfuiAidlCompatSession::tryGetService();
+ if (aidlCompatSession) {
+ return new ApcCompatSession(std::move(aidlCompatSession), nullptr);
+ }
+
+ sp<ConfuiHidlCompatSession> hidlCompatSession = ConfuiHidlCompatSession::tryGetService();
+ if (hidlCompatSession) {
+ return new ApcCompatSession(nullptr, std::move(hidlCompatSession));
+ }
+
+ LOG(ERROR) << "ConfirmationUI: Not found Service";
+ return nullptr;
+ }
+
+ uint32_t promptUserConfirmation(ApcCompatCallback callback, const char* prompt_text,
+ const uint8_t* extra_data, size_t extra_data_size,
+ char const* locale, ApcCompatUiOptions ui_options) {
+ if (aidlCompatSession_) {
+ return aidlCompatSession_->promptUserConfirmation(callback, prompt_text, extra_data,
+ extra_data_size, locale, ui_options);
+ } else {
+ return hidlCompatSession_->promptUserConfirmation(callback, prompt_text, extra_data,
+ extra_data_size, locale, ui_options);
+ }
+ }
+
+ void abortUserConfirmation() {
+ if (aidlCompatSession_) {
+ return aidlCompatSession_->abort();
+ } else {
+ return hidlCompatSession_->abort();
+ }
+ }
+
+ void closeUserConfirmationService() {
+ // Closing the handle implicitly aborts an ongoing sessions.
+ // Note that a resulting callback is still safely conducted, because we only delete a
+ // StrongPointer below. libhwbinder still owns another StrongPointer to this session.
+ abortUserConfirmation();
+ }
+
+ ApcCompatSession(std::shared_ptr<ConfuiAidlCompatSession> aidlCompatSession,
+ sp<ConfuiHidlCompatSession> hidlCompatSession)
+ : aidlCompatSession_(aidlCompatSession), hidlCompatSession_(hidlCompatSession) {}
+
+ private:
+ std::shared_ptr<ConfuiAidlCompatSession> aidlCompatSession_;
+ sp<ConfuiHidlCompatSession> hidlCompatSession_;
+};
} // namespace keystore2
using namespace keystore2;
ApcCompatServiceHandle tryGetUserConfirmationService() {
- return reinterpret_cast<ApcCompatServiceHandle>(ConfuiCompatSession::tryGetService());
+ return reinterpret_cast<ApcCompatServiceHandle>(ApcCompatSession::getApcCompatSession());
}
uint32_t promptUserConfirmation(ApcCompatServiceHandle handle, ApcCompatCallback callback,
const char* prompt_text, const uint8_t* extra_data,
size_t extra_data_size, char const* locale,
ApcCompatUiOptions ui_options) {
- auto session = reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
- return (*session)->promptUserConfirmation(callback, prompt_text, extra_data, extra_data_size,
- locale, ui_options);
+ auto session = reinterpret_cast<ApcCompatSession*>(handle);
+ return session->promptUserConfirmation(callback, prompt_text, extra_data, extra_data_size,
+ locale, ui_options);
}
void abortUserConfirmation(ApcCompatServiceHandle handle) {
- auto session = reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
- (*session)->abort();
+ auto session = reinterpret_cast<ApcCompatSession*>(handle);
+ session->abortUserConfirmation();
}
void closeUserConfirmationService(ApcCompatServiceHandle handle) {
- // Closing the handle implicitly aborts an ongoing sessions.
- // Note that a resulting callback is still safely conducted, because we only delete a
- // StrongPointer below. libhwbinder still owns another StrongPointer to this session.
- abortUserConfirmation(handle);
- delete reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
+ auto session = reinterpret_cast<ApcCompatSession*>(handle);
+ session->closeUserConfirmationService();
+ delete reinterpret_cast<ApcCompatSession*>(handle);
}
const ApcCompatServiceHandle INVALID_SERVICE_HANDLE = nullptr;
diff --git a/keystore2/keystore2.rc b/keystore2/keystore2.rc
index 82bf3b8..6f88dd3 100644
--- a/keystore2/keystore2.rc
+++ b/keystore2/keystore2.rc
@@ -10,4 +10,4 @@
class early_hal
user keystore
group keystore readproc log
- writepid /dev/cpuset/foreground/tasks
+ task_profiles ProcessCapacityHigh
diff --git a/keystore2/legacykeystore/Android.bp b/keystore2/legacykeystore/Android.bp
index da6aa8a..505b165 100644
--- a/keystore2/legacykeystore/Android.bp
+++ b/keystore2/legacykeystore/Android.bp
@@ -21,8 +21,8 @@
default_applicable_licenses: ["system_security_license"],
}
-rust_library {
- name: "liblegacykeystore-rust",
+rust_defaults {
+ name: "liblegacykeystore-rust_defaults",
crate_name: "legacykeystore",
srcs: [
"lib.rs",
@@ -31,7 +31,6 @@
"android.security.legacykeystore-rust",
"libanyhow",
"libbinder_rs",
- "libkeystore2",
"liblog_rust",
"librusqlite",
"librustutils",
@@ -39,6 +38,15 @@
],
}
+rust_library {
+ name: "liblegacykeystore-rust",
+ defaults: ["liblegacykeystore-rust_defaults"],
+ rustlibs: [
+ "libkeystore2",
+ "librusqlite",
+ ],
+}
+
rust_test {
name: "legacykeystore_test",
crate_name: "legacykeystore",
diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs
index da60297..ed5bd4f 100644
--- a/keystore2/legacykeystore/lib.rs
+++ b/keystore2/legacykeystore/lib.rs
@@ -25,8 +25,9 @@
};
use anyhow::{Context, Result};
use keystore2::{
- async_task::AsyncTask, legacy_blob::LegacyBlobLoader, maintenance::DeleteListener,
- maintenance::Domain, utils::watchdog as wd,
+ 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,
};
use rusqlite::{
params, Connection, OptionalExtension, Transaction, TransactionBehavior, NO_PARAMS,
@@ -107,6 +108,12 @@
.prepare("SELECT alias FROM profiles WHERE owner = ? ORDER BY alias ASC;")
.context("In list: Failed to prepare statement.")?;
+ // This allow is necessary to avoid the following error:
+ //
+ // error[E0597]: `stmt` does not live long enough
+ //
+ // See: https://github.com/rust-lang/rust-clippy/issues/8114
+ #[allow(clippy::let_and_return)]
let aliases = stmt
.query_map(params![caller_uid], |row| row.get(0))?
.collect::<rusqlite::Result<Vec<String>>>()
@@ -171,7 +178,7 @@
/// This is the main LegacyKeystore error type, it wraps binder exceptions and the
/// LegacyKeystore errors.
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// Wraps a LegacyKeystore error code.
#[error("Error::Error({0:?})")]
@@ -226,7 +233,10 @@
if log_error {
log::error!("{:?}", e);
}
- Err(BinderStatus::new_service_specific_error(rc, None))
+ Err(BinderStatus::new_service_specific_error(
+ rc,
+ anyhow_error_to_cstring(&e).as_deref(),
+ ))
},
handle_ok,
)
@@ -312,8 +322,8 @@
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 migrate legacy blob.")? {
- // If we were able to migrate a legacy blob try again.
+ 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 let Some(entry) =
db.get(uid, alias).context("In get: Trying to load entry from DB.")?
{
@@ -325,19 +335,20 @@
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.")
+ 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(())
}
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.")?;
- // 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);
+
+ if self.remove_legacy(uid, alias).context("In remove: trying to remove legacy entry")? {
+ return Ok(());
+ }
let removed =
db.remove(uid, alias).context("In remove: Trying to remove entry from DB.")?;
if removed {
@@ -382,7 +393,7 @@
let uid = Self::get_effective_uid(uid).context("In list.")?;
let mut result = self.list_legacy(uid).context("In list.")?;
result.append(&mut db.list(uid).context("In list: Trying to get list of entries.")?);
- result = result.into_iter().filter(|s| s.starts_with(prefix)).collect();
+ result.retain(|s| s.starts_with(prefix));
result.sort_unstable();
result.dedup();
Ok(result)
@@ -427,17 +438,30 @@
return Ok(true);
}
let mut db = DB::new(&state.db_path).context("In open_db: Failed to open db.")?;
- let migrated =
- Self::migrate_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db)
- .context("Trying to migrate legacy keystore entries.")?;
- if migrated {
+ let imported =
+ Self::import_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db)
+ .context("Trying to import legacy keystore entries.")?;
+ if imported {
state.recently_imported.insert((uid, alias));
}
- Ok(migrated)
+ Ok(imported)
})
.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
@@ -470,21 +494,31 @@
})
}
- fn migrate_one_legacy_entry(
+ fn import_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)
- .context("In migrate_one_legacy_entry: Trying to read legacy keystore entry.")?;
+ .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.")?;
if let Some(entry) = blob {
db.put(uid, alias, &entry)
- .context("In migrate_one_legacy_entry: Trying to insert entry into DB.")?;
+ .context("In import_one_legacy_entry: Trying to insert entry into DB.")?;
legacy_loader
.remove_legacy_keystore_entry(uid, alias)
- .context("In migrate_one_legacy_entry: Trying to delete legacy keystore entry.")?;
+ .context("In import_one_legacy_entry: Trying to delete legacy keystore entry.")?;
Ok(true)
} else {
Ok(false)
diff --git a/keystore2/rustfmt.toml b/keystore2/rustfmt.toml
new file mode 100644
index 0000000..4335d66
--- /dev/null
+++ b/keystore2/rustfmt.toml
@@ -0,0 +1,5 @@
+# Android Format Style
+
+edition = "2021"
+use_small_heuristics = "Max"
+newline_style = "Unix"
\ No newline at end of file
diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs
index 902e9a4..e5c3091 100644
--- a/keystore2/selinux/src/lib.rs
+++ b/keystore2/selinux/src/lib.rs
@@ -65,7 +65,7 @@
}
/// Selinux Error code.
-#[derive(thiserror::Error, Debug, PartialEq)]
+#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum Error {
/// Indicates that an access check yielded no access.
#[error("Permission Denied")]
@@ -333,6 +333,311 @@
}
}
+/// 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 0096686..5d2083d 100644
--- a/keystore2/src/apc.rs
+++ b/keystore2/src/apc.rs
@@ -21,6 +21,8 @@
sync::{mpsc::Sender, Arc, Mutex},
};
+use crate::error::anyhow_error_to_cstring;
+use crate::ks_err;
use crate::utils::{compat_2_response_code, ui_opts_2_compat, watchdog as wd};
use android_security_apc::aidl::android::security::apc::{
IConfirmationCallback::IConfirmationCallback,
@@ -38,7 +40,7 @@
/// This is the main APC error type, it wraps binder exceptions and the
/// APC ResponseCode.
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// Wraps an Android Protected Confirmation (APC) response code as defined by the
/// android.security.apc AIDL interface specification.
@@ -110,7 +112,10 @@
_ => ResponseCode::SYSTEM_ERROR.0,
},
};
- Err(BinderStatus::new_service_specific_error(rc, None))
+ Err(BinderStatus::new_service_specific_error(
+ rc,
+ anyhow_error_to_cstring(&e).as_deref(),
+ ))
},
handle_ok,
)
@@ -255,13 +260,10 @@
if let Ok(listener) = callback.into_interface::<dyn IConfirmationCallback>() {
if let Err(e) = listener.onCompleted(rc, data_confirmed) {
- log::error!(
- "In ApcManagerCallback::result: Reporting completion to client failed {:?}",
- e
- )
+ log::error!("Reporting completion to client failed {:?}", e)
}
} else {
- log::error!("In ApcManagerCallback::result: SpIBinder is not a IConfirmationCallback.");
+ log::error!("SpIBinder is not a IConfirmationCallback.");
}
}
@@ -275,8 +277,7 @@
) -> Result<()> {
let mut state = self.state.lock().unwrap();
if state.session.is_some() {
- return Err(Error::pending())
- .context("In ApcManager::present_prompt: Session pending.");
+ return Err(Error::pending()).context(ks_err!("APC Session pending."));
}
// Perform rate limiting.
@@ -285,8 +286,8 @@
None => {}
Some(rate_info) => {
if let Some(back_off) = rate_info.get_remaining_back_off() {
- return Err(Error::sys()).context(format!(
- "In ApcManager::present_prompt: Cooling down. Remaining back-off: {}s",
+ return Err(Error::sys()).context(ks_err!(
+ "APC Cooling down. Remaining back-off: {}s",
back_off.as_secs()
));
}
@@ -296,8 +297,7 @@
let hal = ApcHal::try_get_service();
let hal = match hal {
None => {
- return Err(Error::unimplemented())
- .context("In ApcManager::present_prompt: APC not supported.")
+ return Err(Error::unimplemented()).context(ks_err!("APC not supported."));
}
Some(h) => Arc::new(h),
};
@@ -315,7 +315,7 @@
},
)
.map_err(|rc| Error::Rc(compat_2_response_code(rc)))
- .context("In present_prompt: Failed to present prompt.")?;
+ .context(ks_err!("APC Failed to present prompt."))?;
state.session = Some(ApcSessionState {
hal,
cb: listener.as_binder(),
@@ -331,13 +331,12 @@
let hal = match &mut state.session {
None => {
return Err(Error::ignored())
- .context("In cancel_prompt: Attempt to cancel non existing session. Ignoring.")
+ .context(ks_err!("Attempt to cancel non existing session. Ignoring."));
}
Some(session) => {
if session.cb != listener.as_binder() {
- return Err(Error::ignored()).context(concat!(
- "In cancel_prompt: Attempt to cancel session not belonging to caller. ",
- "Ignoring."
+ return Err(Error::ignored()).context(ks_err!(
+ "Attempt to cancel session not belonging to caller. Ignoring."
));
}
session.client_aborted = true;
diff --git a/keystore2/src/attestation_key_utils.rs b/keystore2/src/attestation_key_utils.rs
index b6a8e31..94f3e4c 100644
--- a/keystore2/src/attestation_key_utils.rs
+++ b/keystore2/src/attestation_key_utils.rs
@@ -18,6 +18,7 @@
use crate::database::{BlobMetaData, KeyEntryLoadBits, KeyType};
use crate::database::{KeyIdGuard, KeystoreDB};
use crate::error::{Error, ErrorCode};
+use crate::ks_err;
use crate::permission::KeyPerm;
use crate::remote_provisioning::RemProvState;
use crate::utils::check_key_permission;
@@ -25,7 +26,7 @@
AttestationKey::AttestationKey, Certificate::Certificate, KeyParameter::KeyParameter, Tag::Tag,
};
use android_system_keystore2::aidl::android::system::keystore2::{
- Domain::Domain, KeyDescriptor::KeyDescriptor,
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
};
use anyhow::{Context, Result};
use keystore2_crypto::parse_subject_from_certificate;
@@ -35,6 +36,7 @@
/// handled quite differently, thus the different representations.
pub enum AttestationKeyInfo {
RemoteProvisioned {
+ key_id_guard: KeyIdGuard,
attestation_key: AttestationKey,
attestation_certs: Certificate,
},
@@ -58,21 +60,25 @@
db: &mut KeystoreDB,
) -> Result<Option<AttestationKeyInfo>> {
let challenge_present = params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE);
+ let is_device_unique_attestation =
+ params.iter().any(|kp| kp.tag == Tag::DEVICE_UNIQUE_ATTESTATION);
match attest_key_descriptor {
- None if challenge_present => rem_prov_state
+ // Do not select an RKP key if DEVICE_UNIQUE_ATTESTATION is present.
+ None if challenge_present && !is_device_unique_attestation => rem_prov_state
.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."
- ))
+ .context(ks_err!("Trying to get remotely provisioned attestation key."))
.map(|result| {
- result.map(|(attestation_key, attestation_certs)| {
- AttestationKeyInfo::RemoteProvisioned { attestation_key, attestation_certs }
+ result.map(|(key_id_guard, attestation_key, attestation_certs)| {
+ AttestationKeyInfo::RemoteProvisioned {
+ key_id_guard,
+ attestation_key,
+ attestation_certs,
+ }
})
}),
None => Ok(None),
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")
+ .context(ks_err!("Trying to load attest key"))
.map(Some),
}
}
@@ -84,11 +90,10 @@
) -> Result<AttestationKeyInfo> {
let (key_id_guard, blob, cert, blob_metadata) =
load_attest_key_blob_and_cert(key, caller_uid, db)
- .context("In get_user_generated_attestation_key: Failed to load blob and cert")?;
+ .context(ks_err!("Failed to load blob and cert"))?;
- let issuer_subject: Vec<u8> = parse_subject_from_certificate(&cert).context(
- "In get_user_generated_attestation_key: Failed to parse subject from certificate.",
- )?;
+ let issuer_subject: Vec<u8> = parse_subject_from_certificate(&cert)
+ .context(ks_err!("Failed to parse subject from certificate"))?;
Ok(AttestationKeyInfo::UserGenerated { key_id_guard, blob, issuer_subject, blob_metadata })
}
@@ -99,9 +104,8 @@
db: &mut KeystoreDB,
) -> Result<(KeyIdGuard, Vec<u8>, Vec<u8>, BlobMetaData)> {
match key.domain {
- Domain::BLOB => Err(Error::Km(ErrorCode::INVALID_ARGUMENT)).context(
- "In load_attest_key_blob_and_cert: Domain::BLOB attestation keys not supported",
- ),
+ Domain::BLOB => Err(Error::Km(ErrorCode::INVALID_ARGUMENT))
+ .context(ks_err!("Domain::BLOB attestation keys not supported")),
_ => {
let (key_id_guard, mut key_entry) = db
.load_key_entry(
@@ -109,19 +113,18 @@
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.")?;
+ .context(ks_err!("Failed to load key."))?;
- let (blob, blob_metadata) =
- key_entry.take_key_blob_info().ok_or_else(Error::sys).context(concat!(
- "In load_attest_key_blob_and_cert: Successfully loaded key entry,",
- " but KM blob was missing."
- ))?;
- let cert = key_entry.take_cert().ok_or_else(Error::sys).context(concat!(
- "In load_attest_key_blob_and_cert: Successfully loaded key entry,",
- " but cert was missing."
- ))?;
+ let (blob, blob_metadata) = key_entry
+ .take_key_blob_info()
+ .ok_or(Error::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context(ks_err!("Successfully loaded key entry, but KM blob was missing"))?;
+ let cert = key_entry
+ .take_cert()
+ .ok_or(Error::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context(ks_err!("Successfully loaded key entry, but cert was missing"))?;
Ok((key_id_guard, blob, cert, blob_metadata))
}
}
diff --git a/keystore2/src/audit_log.rs b/keystore2/src/audit_log.rs
index 3d7d26e..07509d3 100644
--- a/keystore2/src/audit_log.rs
+++ b/keystore2/src/audit_log.rs
@@ -67,7 +67,7 @@
fn log_key_event(tag: u32, key: &KeyDescriptor, calling_app: uid_t, success: bool) {
with_log_context(tag, |ctx| {
let owner = key_owner(key.domain, key.nspace, calling_app as i32);
- ctx.append_i32(if success { 1 } else { 0 })
+ ctx.append_i32(i32::from(success))
.append_str(key.alias.as_ref().map_or("none", String::as_str))
.append_i32(owner)
})
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index 777089f..1953920 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -14,30 +14,32 @@
//! This module implements IKeystoreAuthorization AIDL interface.
+use crate::ks_err;
use crate::error::Error as KeystoreError;
-use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_MIGRATOR};
+use crate::error::anyhow_error_to_cstring;
+use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_IMPORTER};
use crate::permission::KeystorePerm;
use crate::super_key::UserState;
use crate::utils::{check_keystore_permission, watchdog as wd};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken,
};
-use android_security_authorization::binder::{BinderFeatures,ExceptionCode, Interface, Result as BinderResult,
- Strong, Status as BinderStatus};
+use android_security_authorization::binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult,
+ Strong, Status as BinderStatus};
use android_security_authorization::aidl::android::security::authorization::{
IKeystoreAuthorization::BnKeystoreAuthorization, IKeystoreAuthorization::IKeystoreAuthorization,
LockScreenEvent::LockScreenEvent, AuthorizationTokens::AuthorizationTokens,
ResponseCode::ResponseCode,
};
use android_system_keystore2::aidl::android::system::keystore2::{
- ResponseCode::ResponseCode as KsResponseCode };
+ ResponseCode::ResponseCode as KsResponseCode};
use anyhow::{Context, Result};
use keystore2_crypto::Password;
use keystore2_selinux as selinux;
/// This is the Authorization error type, it wraps binder exceptions and the
/// Authorization ResponseCode
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// Wraps an IKeystoreAuthorization response code as defined by
/// android.security.authorization AIDL interface specification.
@@ -88,7 +90,10 @@
// as well.
_ => ResponseCode::SYSTEM_ERROR.0,
};
- return Err(BinderStatus::new_service_specific_error(rc, None));
+ return Err(BinderStatus::new_service_specific_error(
+ rc,
+ anyhow_error_to_cstring(&e).as_deref(),
+ ));
}
let rc = match root_cause.downcast_ref::<Error>() {
Some(Error::Rc(rcode)) => rcode.0,
@@ -98,7 +103,10 @@
_ => ResponseCode::SYSTEM_ERROR.0,
},
};
- Err(BinderStatus::new_service_specific_error(rc, None))
+ Err(BinderStatus::new_service_specific_error(
+ rc,
+ anyhow_error_to_cstring(&e).as_deref(),
+ ))
},
handle_ok,
)
@@ -119,7 +127,7 @@
fn add_auth_token(&self, auth_token: &HardwareAuthToken) -> Result<()> {
// Check keystore permission.
- check_keystore_permission(KeystorePerm::add_auth()).context("In add_auth_token.")?;
+ check_keystore_permission(KeystorePerm::AddAuth).context(ks_err!())?;
ENFORCEMENTS.add_auth_token(auth_token.clone());
Ok(())
@@ -143,31 +151,32 @@
(LockScreenEvent::UNLOCK, Some(password)) => {
// This corresponds to the unlock() method in legacy keystore API.
// check permission
- check_keystore_permission(KeystorePerm::unlock())
- .context("In on_lock_screen_event: Unlock with password.")?;
+ check_keystore_permission(KeystorePerm::Unlock)
+ .context(ks_err!("Unlock with password."))?;
ENFORCEMENTS.set_device_locked(user_id, false);
+ let mut skm = SUPER_KEY.write().unwrap();
+
DB.with(|db| {
- SUPER_KEY.unlock_screen_lock_bound_key(
+ skm.unlock_screen_lock_bound_key(
&mut db.borrow_mut(),
user_id as u32,
&password,
)
})
- .context("In on_lock_screen_event: unlock_screen_lock_bound_key failed")?;
+ .context(ks_err!("unlock_screen_lock_bound_key failed"))?;
// Unlock super key.
if let UserState::Uninitialized = DB
.with(|db| {
- UserState::get_with_password_unlock(
+ skm.unlock_and_get_user_state(
&mut db.borrow_mut(),
- &LEGACY_MIGRATOR,
- &SUPER_KEY,
+ &LEGACY_IMPORTER,
user_id as u32,
&password,
)
})
- .context("In on_lock_screen_event: Unlock with password.")?
+ .context(ks_err!("Unlock with password."))?
{
log::info!(
"In on_lock_screen_event. Trying to unlock when LSKF is uninitialized."
@@ -177,21 +186,21 @@
Ok(())
}
(LockScreenEvent::UNLOCK, None) => {
- check_keystore_permission(KeystorePerm::unlock())
- .context("In on_lock_screen_event: Unlock.")?;
+ check_keystore_permission(KeystorePerm::Unlock).context(ks_err!("Unlock."))?;
ENFORCEMENTS.set_device_locked(user_id, false);
+ let mut skm = SUPER_KEY.write().unwrap();
DB.with(|db| {
- SUPER_KEY.try_unlock_user_with_biometric(&mut db.borrow_mut(), user_id as u32)
+ skm.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")?;
+ .context(ks_err!("try_unlock_user_with_biometric failed"))?;
Ok(())
}
(LockScreenEvent::LOCK, None) => {
- check_keystore_permission(KeystorePerm::lock())
- .context("In on_lock_screen_event: Lock")?;
+ check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?;
ENFORCEMENTS.set_device_locked(user_id, true);
+ let mut skm = SUPER_KEY.write().unwrap();
DB.with(|db| {
- SUPER_KEY.lock_screen_lock_bound_key(
+ skm.lock_screen_lock_bound_key(
&mut db.borrow_mut(),
user_id as u32,
unlocking_sids.unwrap_or(&[]),
@@ -201,8 +210,7 @@
}
_ => {
// Any other combination is not supported.
- Err(Error::Rc(ResponseCode::INVALID_ARGUMENT))
- .context("In on_lock_screen_event: Unknown event.")
+ Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!("Unknown event."))
}
}
}
@@ -215,13 +223,12 @@
) -> Result<AuthorizationTokens> {
// Check permission. Function should return if this failed. Therefore having '?' at the end
// is very important.
- check_keystore_permission(KeystorePerm::get_auth_token())
- .context("In get_auth_tokens_for_credstore.")?;
+ check_keystore_permission(KeystorePerm::GetAuthToken).context(ks_err!("GetAuthToken"))?;
// If the challenge is zero, return error
if challenge == 0 {
return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT))
- .context("In get_auth_tokens_for_credstore. Challenge can not be zero.");
+ .context(ks_err!("Challenge can not be zero."));
}
// Obtain the auth token and the timestamp token from the enforcement module.
let (auth_token, ts_token) =
@@ -265,7 +272,7 @@
challenge: i64,
secure_user_id: i64,
auth_token_max_age_millis: i64,
- ) -> binder::public_api::Result<AuthorizationTokens> {
+ ) -> binder::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 08c52af..e2e67ff 100644
--- a/keystore2/src/boot_level_keys.rs
+++ b/keystore2/src/boot_level_keys.rs
@@ -14,6 +14,7 @@
//! Offer keys based on the "boot level" for superencryption.
+use crate::ks_err;
use crate::{
database::{KeyType, KeystoreDB},
key_parameter::KeyParameterValue,
@@ -21,26 +22,86 @@
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, Digest::Digest, KeyParameter::KeyParameter as KmKeyParameter,
- KeyParameterValue::KeyParameterValue as KmKeyParameterValue, KeyPurpose::KeyPurpose,
- SecurityLevel::SecurityLevel, Tag::Tag,
+ KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
};
use anyhow::{Context, Result};
use keystore2_crypto::{hkdf_expand, ZVec, AES_256_KEY_LENGTH};
use std::{collections::VecDeque, convert::TryFrom};
-fn get_preferred_km_instance_for_level_zero_key() -> Result<KeyMintDevice> {
- let tee = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
- .context("In get_preferred_km_instance_for_level_zero_key: Get TEE instance failed.")?;
- if tee.version() >= KeyMintDevice::KEY_MASTER_V4_1 {
- Ok(tee)
+/// Strategies used to prevent later boot stages from using the KM key that protects the level 0
+/// key
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum DenyLaterStrategy {
+ /// set MaxUsesPerBoot to 1. This is much less secure, since the attacker can replace the key
+ /// itself, and therefore create artifacts which appear to come from early boot.
+ MaxUsesPerBoot,
+ /// set the EarlyBootOnly property. This property is only supported in KM from 4.1 on, but
+ /// it ensures that the level 0 key was genuinely created in early boot
+ EarlyBootOnly,
+}
+
+/// Generally the L0 KM and strategy are chosen by probing KM versions in TEE and Strongbox.
+/// However, once a device is launched the KM and strategy must never change, even if the
+/// KM version in TEE or Strongbox is updated. Setting this property at build time using
+/// `PRODUCT_VENDOR_PROPERTIES` means that the strategy can be fixed no matter what versions
+/// of KM are present.
+const PROPERTY_NAME: &str = "ro.keystore.boot_level_key.strategy";
+
+fn lookup_level_zero_km_and_strategy() -> Result<Option<(SecurityLevel, DenyLaterStrategy)>> {
+ let property_val = rustutils::system_properties::read(PROPERTY_NAME)
+ .with_context(|| ks_err!("property read failed: {}", PROPERTY_NAME))?;
+ // TODO: use feature(let_else) when that's stabilized.
+ let property_val = if let Some(p) = property_val {
+ p
} else {
- match KeyMintDevice::get_or_none(SecurityLevel::STRONGBOX).context(
- "In get_preferred_km_instance_for_level_zero_key: Get Strongbox instance failed.",
- )? {
+ log::info!("{} not set, inferring from installed KM instances", PROPERTY_NAME);
+ return Ok(None);
+ };
+ let (level, strategy) = if let Some(c) = property_val.split_once(':') {
+ c
+ } else {
+ log::error!("Missing colon in {}: {:?}", PROPERTY_NAME, property_val);
+ return Ok(None);
+ };
+ let level = match level {
+ "TRUSTED_ENVIRONMENT" => SecurityLevel::TRUSTED_ENVIRONMENT,
+ "STRONGBOX" => SecurityLevel::STRONGBOX,
+ _ => {
+ log::error!("Unknown security level in {}: {:?}", PROPERTY_NAME, level);
+ return Ok(None);
+ }
+ };
+ let strategy = match strategy {
+ "EARLY_BOOT_ONLY" => DenyLaterStrategy::EarlyBootOnly,
+ "MAX_USES_PER_BOOT" => DenyLaterStrategy::MaxUsesPerBoot,
+ _ => {
+ log::error!("Unknown DenyLaterStrategy in {}: {:?}", PROPERTY_NAME, strategy);
+ return Ok(None);
+ }
+ };
+ log::info!("Set from {}: {}", PROPERTY_NAME, property_val);
+ Ok(Some((level, strategy)))
+}
+
+fn get_level_zero_key_km_and_strategy() -> Result<(KeyMintDevice, DenyLaterStrategy)> {
+ if let Some((level, strategy)) = lookup_level_zero_km_and_strategy()? {
+ return Ok((
+ KeyMintDevice::get(level).context(ks_err!("Get KM instance failed."))?,
+ strategy,
+ ));
+ }
+ let tee = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
+ .context(ks_err!("Get TEE instance failed."))?;
+ if tee.version() >= KeyMintDevice::KEY_MASTER_V4_1 {
+ Ok((tee, DenyLaterStrategy::EarlyBootOnly))
+ } else {
+ match KeyMintDevice::get_or_none(SecurityLevel::STRONGBOX)
+ .context(ks_err!("Get Strongbox instance failed."))?
+ {
Some(strongbox) if strongbox.version() >= KeyMintDevice::KEY_MASTER_V4_1 => {
- Ok(strongbox)
+ Ok((strongbox, DenyLaterStrategy::EarlyBootOnly))
}
- _ => Ok(tee),
+ _ => Ok((tee, DenyLaterStrategy::MaxUsesPerBoot)),
}
}
}
@@ -49,55 +110,52 @@
/// In practice the caller is SuperKeyManager and the lock is the
/// Mutex on its internal state.
pub fn get_level_zero_key(db: &mut KeystoreDB) -> Result<ZVec> {
- let km_dev = get_preferred_km_instance_for_level_zero_key()
- .context("In get_level_zero_key: get preferred KM instance failed")?;
-
- let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string());
- let mut params = vec![
+ let (km_dev, deny_later_strategy) = get_level_zero_key_km_and_strategy()
+ .context(ks_err!("get preferred KM instance failed"))?;
+ log::info!(
+ "In get_level_zero_key: security_level={:?}, deny_later_strategy={:?}",
+ km_dev.security_level(),
+ deny_later_strategy
+ );
+ let required_security_level = km_dev.security_level();
+ let required_param: KmKeyParameter = match deny_later_strategy {
+ DenyLaterStrategy::EarlyBootOnly => KeyParameterValue::EarlyBootOnly,
+ DenyLaterStrategy::MaxUsesPerBoot => KeyParameterValue::MaxUsesPerBoot(1),
+ }
+ .into();
+ let params = vec![
KeyParameterValue::Algorithm(Algorithm::HMAC).into(),
KeyParameterValue::Digest(Digest::SHA_2_256).into(),
KeyParameterValue::KeySize(256).into(),
KeyParameterValue::MinMacLength(256).into(),
KeyParameterValue::KeyPurpose(KeyPurpose::SIGN).into(),
KeyParameterValue::NoAuthRequired.into(),
+ required_param.clone(),
];
- let has_early_boot_only = km_dev.version() >= KeyMintDevice::KEY_MASTER_V4_1;
-
- if has_early_boot_only {
- params.push(KeyParameterValue::EarlyBootOnly.into());
- } else {
- params.push(KeyParameterValue::MaxUsesPerBoot(1).into())
- }
-
+ let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string());
let (key_id_guard, key_entry) = km_dev
.lookup_or_generate_key(db, &key_desc, KeyType::Client, ¶ms, |key_characteristics| {
key_characteristics.iter().any(|kc| {
- if kc.securityLevel == km_dev.security_level() {
- kc.authorizations.iter().any(|a| {
- matches!(
- (has_early_boot_only, a),
- (
- true,
- KmKeyParameter {
- tag: Tag::EARLY_BOOT_ONLY,
- value: KmKeyParameterValue::BoolValue(true)
- }
- ) | (
- false,
- KmKeyParameter {
- tag: Tag::MAX_USES_PER_BOOT,
- value: KmKeyParameterValue::Integer(1)
- }
- )
- )
- })
- } else {
- false
+ if kc.securityLevel != required_security_level {
+ log::error!(
+ "In get_level_zero_key: security level expected={:?} got={:?}",
+ required_security_level,
+ kc.securityLevel
+ );
+ return false;
}
+ if !kc.authorizations.iter().any(|a| a == &required_param) {
+ log::error!(
+ "In get_level_zero_key: required param absent {:?}",
+ required_param
+ );
+ return false;
+ }
+ true
})
})
- .context("In get_level_zero_key: lookup_or_generate_key failed")?;
+ .context(ks_err!("lookup_or_generate_key failed"))?;
let params = [
KeyParameterValue::MacLength(256).into(),
@@ -113,11 +171,11 @@
None,
b"Create boot level key",
)
- .context("In get_level_zero_key: use_key_in_one_step failed")?;
+ .context(ks_err!("use_key_in_one_step failed"))?;
// TODO: this is rather unsatisfactory, we need a better way to handle
// sensitive binder returns.
- let level_zero_key = ZVec::try_from(level_zero_key)
- .context("In get_level_zero_key: conversion to ZVec failed")?;
+ let level_zero_key =
+ ZVec::try_from(level_zero_key).context(ks_err!("conversion to ZVec failed"))?;
Ok(level_zero_key)
}
@@ -169,7 +227,7 @@
// so this must unwrap.
let highest_key = self.cache.back().unwrap();
let next_key = hkdf_expand(Self::HKDF_KEY_SIZE, highest_key, Self::HKDF_ADVANCE)
- .context("In BootLevelKeyCache::get_hkdf_key: Advancing key one step")?;
+ .context(ks_err!("Advancing key one step"))?;
self.cache.push_back(next_key);
}
@@ -182,10 +240,7 @@
pub fn advance_boot_level(&mut self, new_boot_level: usize) -> Result<()> {
if !self.level_accessible(new_boot_level) {
log::error!(
- concat!(
- "In BootLevelKeyCache::advance_boot_level: ",
- "Failed to advance boot level to {}, current is {}, cache size {}"
- ),
+ "Failed to advance boot level to {}, current is {}, cache size {}",
new_boot_level,
self.current,
self.cache.len()
@@ -195,8 +250,7 @@
// We `get` the new boot level for the side effect of advancing the cache to a point
// where the new boot level is present.
- self.get_hkdf_key(new_boot_level)
- .context("In BootLevelKeyCache::advance_boot_level: Advancing cache")?;
+ self.get_hkdf_key(new_boot_level).context(ks_err!("Advancing cache"))?;
// Then we split the queue at the index of the new boot level and discard the front,
// keeping only the keys with the current boot level or higher.
@@ -222,16 +276,16 @@
info: &[u8],
) -> Result<Option<ZVec>> {
self.get_hkdf_key(boot_level)
- .context("In BootLevelKeyCache::expand_key: Looking up HKDF key")?
+ .context(ks_err!("Looking up HKDF key"))?
.map(|k| hkdf_expand(out_len, k, info))
.transpose()
- .context("In BootLevelKeyCache::expand_key: Calling hkdf_expand")
+ .context(ks_err!("Calling hkdf_expand"))
}
/// Return the AES-256-GCM key for the current boot level.
pub fn aes_key(&mut self, boot_level: usize) -> Result<Option<ZVec>> {
self.expand_key(boot_level, AES_256_KEY_LENGTH, BootLevelKeyCache::HKDF_AES)
- .context("In BootLevelKeyCache::aes_key: expand_key failed")
+ .context(ks_err!("expand_key failed"))
}
}
diff --git a/keystore2/src/crypto/Android.bp b/keystore2/src/crypto/Android.bp
index 4e76507..1ac6467 100644
--- a/keystore2/src/crypto/Android.bp
+++ b/keystore2/src/crypto/Android.bp
@@ -35,6 +35,11 @@
"libkeystore2_crypto",
"libcrypto",
],
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
}
cc_library {
@@ -48,6 +53,11 @@
"libcrypto",
"liblog",
],
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
}
rust_bindgen {
@@ -56,9 +66,11 @@
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",
@@ -82,6 +94,10 @@
"--allowlist-var", "EVP_MAX_MD_SIZE",
],
cflags: ["-DBORINGSSL_NO_CXX"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
}
rust_test {
@@ -134,4 +150,8 @@
auto_gen_config: true,
clippy_lints: "none",
lints: "none",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
}
diff --git a/keystore2/src/crypto/crypto.cpp b/keystore2/src/crypto/crypto.cpp
index 5d360a1..7feeaff 100644
--- a/keystore2/src/crypto/crypto.cpp
+++ b/keystore2/src/crypto/crypto.cpp
@@ -18,6 +18,7 @@
#include "crypto.hpp"
+#include <assert.h>
#include <log/log.h>
#include <openssl/aes.h>
#include <openssl/ec.h>
@@ -25,6 +26,7 @@
#include <openssl/ecdh.h>
#include <openssl/evp.h>
#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
@@ -66,6 +68,14 @@
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);
}
@@ -183,16 +193,6 @@
void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw, size_t pw_len,
const uint8_t* salt) {
- size_t saltSize;
- if (salt != nullptr) {
- saltSize = SALT_SIZE;
- } else {
- // Pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found
- salt = reinterpret_cast<const uint8_t*>("keystore");
- // sizeof = 9, not strlen = 8
- saltSize = sizeof("keystore");
- }
-
const EVP_MD* digest = EVP_sha256();
// SHA1 was used prior to increasing the key size
@@ -200,7 +200,7 @@
digest = EVP_sha1();
}
- PKCS5_PBKDF2_HMAC(pw, pw_len, salt, saltSize, 8192, digest, key_len, key);
+ PKCS5_PBKDF2_HMAC(pw, pw_len, salt, SALT_SIZE, 8192, digest, key_len, key);
}
// New code.
diff --git a/keystore2/src/crypto/crypto.hpp b/keystore2/src/crypto/crypto.hpp
index f841eb3..4a161e6 100644
--- a/keystore2/src/crypto/crypto.hpp
+++ b/keystore2/src/crypto/crypto.hpp
@@ -22,6 +22,8 @@
#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);
@@ -34,6 +36,7 @@
bool CreateKeyId(const uint8_t* key_blob, size_t len, km_id_t* out_id);
+ // The salt parameter must be non-nullptr and point to 16 bytes of data.
void generateKeyFromPassword(uint8_t* key, size_t key_len, const char* pw,
size_t pw_len, const uint8_t* salt);
diff --git a/keystore2/src/crypto/error.rs b/keystore2/src/crypto/error.rs
index a369012..48a2d4c 100644
--- a/keystore2/src/crypto/error.rs
+++ b/keystore2/src/crypto/error.rs
@@ -13,6 +13,7 @@
// 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)]
@@ -93,4 +94,12 @@
/// 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 cad82b6..13d3ef0 100644
--- a/keystore2/src/crypto/include/certificate_utils.h
+++ b/keystore2/src/crypto/include/certificate_utils.h
@@ -20,6 +20,7 @@
#include <openssl/x509.h>
#include <stdint.h>
+#include <functional>
#include <memory>
#include <optional>
#include <variant>
diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs
index 5f8a2ef..7ba47c8 100644
--- a/keystore2/src/crypto/lib.rs
+++ b/keystore2/src/crypto/lib.rs
@@ -16,11 +16,11 @@
//! Keystore 2.0.
mod error;
-mod zvec;
+pub mod zvec;
pub use error::Error;
use keystore2_crypto_bindgen::{
- extractSubjectFromCertificate, generateKeyFromPassword, randomBytes, AES_gcm_decrypt,
- AES_gcm_encrypt, ECDHComputeKey, ECKEYGenerateKey, ECKEYMarshalPrivateKey,
+ extractSubjectFromCertificate, generateKeyFromPassword, hmacSha256, randomBytes,
+ AES_gcm_decrypt, AES_gcm_encrypt, ECDHComputeKey, ECKEYGenerateKey, ECKEYMarshalPrivateKey,
ECKEYParsePrivateKey, ECPOINTOct2Point, ECPOINTPoint2Oct, EC_KEY_free, EC_KEY_get0_public_key,
EC_POINT_free, HKDFExpand, HKDFExtract, EC_KEY, EC_MAX_BYTES, EC_POINT, EVP_MAX_MD_SIZE,
};
@@ -39,6 +39,8 @@
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.
@@ -72,6 +74,21 @@
}
}
+/// 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.
@@ -173,31 +190,23 @@
fn get_key(&'a self) -> &'a [u8] {
match self {
Self::Ref(b) => b,
- Self::Owned(z) => &*z,
+ Self::Owned(z) => z,
}
}
/// Generate a key from the given password and salt.
/// The salt must be exactly 16 bytes long.
/// Two key sizes are accepted: 16 and 32 bytes.
- pub fn derive_key(&self, salt: Option<&[u8]>, key_length: usize) -> Result<ZVec, Error> {
- let pw = self.get_key();
-
- let salt: *const u8 = match salt {
- Some(s) => {
- if s.len() != SALT_LENGTH {
- return Err(Error::InvalidSaltLength);
- }
- s.as_ptr()
- }
- None => std::ptr::null(),
- };
-
+ pub fn derive_key(&self, salt: &[u8], key_length: usize) -> Result<ZVec, Error> {
+ if salt.len() != SALT_LENGTH {
+ return Err(Error::InvalidSaltLength);
+ }
match key_length {
AES_128_KEY_LENGTH | AES_256_KEY_LENGTH => {}
_ => return Err(Error::InvalidKeyLength),
}
+ let pw = self.get_key();
let mut result = ZVec::new(key_length)?;
unsafe {
@@ -206,7 +215,7 @@
result.len(),
pw.as_ptr() as *const std::os::raw::c_char,
pw.len(),
- salt,
+ salt.as_ptr(),
)
};
@@ -524,9 +533,9 @@
fn test_generate_key_from_password() {
let mut key = vec![0; 16];
let pw = vec![0; 16];
- let mut salt = vec![0; 16];
+ let salt = vec![0; 16];
unsafe {
- generateKeyFromPassword(key.as_mut_ptr(), 16, pw.as_ptr(), 16, salt.as_mut_ptr());
+ generateKeyFromPassword(key.as_mut_ptr(), 16, pw.as_ptr(), 16, salt.as_ptr());
}
assert_ne!(key, vec![0; 16]);
}
@@ -565,4 +574,18 @@
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/zvec.rs b/keystore2/src/crypto/zvec.rs
index 78b474e..5a173c3 100644
--- a/keystore2/src/crypto/zvec.rs
+++ b/keystore2/src/crypto/zvec.rs
@@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::error::Error;
+//! Implements ZVec, a vector that is mlocked during its lifetime and zeroed
+//! when dropped.
+
use nix::sys::mman::{mlock, munlock};
use std::convert::TryFrom;
use std::fmt;
@@ -29,6 +31,14 @@
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> {
@@ -48,6 +58,14 @@
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 ae2875c..62fd579 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -45,8 +45,11 @@
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::ks_err;
use crate::metrics_store::log_rkp_error_stats;
use crate::permission::KeyPermSet;
use crate::utils::{get_current_time_in_milliseconds, watchdog as wd, AID_USER_OFFSET};
@@ -54,7 +57,6 @@
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;
@@ -132,12 +134,13 @@
"SELECT tag, data from persistent.keymetadata
WHERE keyentryid = ?;",
)
- .context("In KeyMetaData::load_from_db: prepare statement failed.")?;
+ .context(ks_err!("KeyMetaData::load_from_db: prepare statement failed."))?;
let mut metadata: HashMap<i64, KeyMetaEntry> = Default::default();
- let mut rows =
- stmt.query(params![key_id]).context("In KeyMetaData::load_from_db: query failed.")?;
+ let mut rows = stmt
+ .query(params![key_id])
+ .context(ks_err!("KeyMetaData::load_from_db: query failed."))?;
db_utils::with_rows_extract_all(&mut rows, |row| {
let db_tag: i64 = row.get(0).context("Failed to read tag.")?;
metadata.insert(
@@ -147,7 +150,7 @@
);
Ok(())
})
- .context("In KeyMetaData::load_from_db.")?;
+ .context(ks_err!("KeyMetaData::load_from_db."))?;
Ok(Self { data: metadata })
}
@@ -158,12 +161,12 @@
"INSERT or REPLACE INTO persistent.keymetadata (keyentryid, tag, data)
VALUES (?, ?, ?);",
)
- .context("In KeyMetaData::store_in_db: Failed to prepare statement.")?;
+ .context(ks_err!("KeyMetaData::store_in_db: Failed to prepare statement."))?;
let iter = self.data.iter();
for (tag, entry) in iter {
stmt.insert(params![key_id, tag, entry,]).with_context(|| {
- format!("In KeyMetaData::store_in_db: Failed to insert {:?}", entry)
+ ks_err!("KeyMetaData::store_in_db: Failed to insert {:?}", entry)
})?;
}
Ok(())
@@ -207,12 +210,11 @@
"SELECT tag, data from persistent.blobmetadata
WHERE blobentryid = ?;",
)
- .context("In BlobMetaData::load_from_db: prepare statement failed.")?;
+ .context(ks_err!("BlobMetaData::load_from_db: prepare statement failed."))?;
let mut metadata: HashMap<i64, BlobMetaEntry> = Default::default();
- let mut rows =
- stmt.query(params![blob_id]).context("In BlobMetaData::load_from_db: query failed.")?;
+ let mut rows = stmt.query(params![blob_id]).context(ks_err!("query failed."))?;
db_utils::with_rows_extract_all(&mut rows, |row| {
let db_tag: i64 = row.get(0).context("Failed to read tag.")?;
metadata.insert(
@@ -222,7 +224,7 @@
);
Ok(())
})
- .context("In BlobMetaData::load_from_db.")?;
+ .context(ks_err!("BlobMetaData::load_from_db"))?;
Ok(Self { data: metadata })
}
@@ -233,12 +235,12 @@
"INSERT or REPLACE INTO persistent.blobmetadata (blobentryid, tag, data)
VALUES (?, ?, ?);",
)
- .context("In BlobMetaData::store_in_db: Failed to prepare statement.")?;
+ .context(ks_err!("BlobMetaData::store_in_db: Failed to prepare statement.",))?;
let iter = self.data.iter();
for (tag, entry) in iter {
stmt.insert(params![blob_id, tag, entry,]).with_context(|| {
- format!("In BlobMetaData::store_in_db: Failed to insert {:?}", entry)
+ ks_err!("BlobMetaData::store_in_db: Failed to insert {:?}", entry)
})?;
}
Ok(())
@@ -323,6 +325,8 @@
0x41, 0xe3, 0xb9, 0xce, 0x27, 0x58, 0x4e, 0x91, 0xbc, 0xfd, 0xa5, 0x5d, 0x91, 0x85, 0xab, 0x11,
]);
+static EXPIRATION_BUFFER_MS: i64 = 12 * 60 * 60 * 1000;
+
/// Indicates how the sensitive part of this key blob is encrypted.
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum EncryptedBy {
@@ -578,6 +582,36 @@
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 {
@@ -848,7 +882,7 @@
let mut db = Self { conn, gc, perboot: perboot::PERBOOT_DB.clone() };
db.with_transaction(TransactionBehavior::Immediate, |tx| {
versioning::upgrade_database(tx, Self::CURRENT_DB_VERSION, Self::UPGRADERS)
- .context("In KeystoreDB::new: trying to upgrade database.")?;
+ .context(ks_err!("KeystoreDB::new: trying to upgrade database."))?;
Self::init_tables(tx).context("Trying to initialize tables.").no_gc()
})?;
Ok(db)
@@ -870,7 +904,7 @@
);",
params![KeyLifeCycle::Unreferenced, Tag::MAX_BOOT_LEVEL.0, BlobMetaData::MaxBootLevel],
)
- .context("In from_0_to_1: Failed to delete logical boot level keys.")?;
+ .context(ks_err!("Failed to delete logical boot level keys."))?;
Ok(1)
}
@@ -1031,7 +1065,7 @@
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)?)))
.with_context(|| {
- format!("get_storage_stat: Error size of storage type {}", storage_type.0)
+ ks_err!("get_storage_stat: Error size of storage type {}", storage_type.0)
})
.no_gc()
})?;
@@ -1206,7 +1240,7 @@
Ok(vec![]).no_gc()
})
- .context("In handle_next_superseded_blobs.")
+ .context(ks_err!())
}
/// This maintenance function should be called only once before the database is used for the
@@ -1228,7 +1262,7 @@
.context("Failed to execute query.")
.need_gc()
})
- .context("In cleanup_leftovers.")
+ .context(ks_err!())
}
/// Checks if a key exists with given key type and key descriptor properties.
@@ -1249,12 +1283,12 @@
Ok(_) => Ok(true),
Err(error) => match error.root_cause().downcast_ref::<KsError>() {
Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(false),
- _ => Err(error).context("In key_exists: Failed to find if the key exists."),
+ _ => Err(error).context(ks_err!("Failed to find if the key exists.")),
},
}
.no_gc()
})
- .context("In key_exists.")
+ .context(ks_err!())
}
/// Stores a super key in the database.
@@ -1302,7 +1336,7 @@
.context("Trying to load key components.")
.no_gc()
})
- .context("In store_super_key.")
+ .context(ks_err!())
}
/// Loads super key of a given user, if exists
@@ -1324,17 +1358,17 @@
match id {
Ok(id) => {
let key_entry = Self::load_key_components(tx, KeyEntryLoadBits::KM, id)
- .context("In load_super_key. Failed to load key entry.")?;
+ .context(ks_err!("Failed to load key entry."))?;
Ok(Some((KEY_ID_LOCK.get(id), key_entry)))
}
Err(error) => match error.root_cause().downcast_ref::<KsError>() {
Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None),
- _ => Err(error).context("In load_super_key."),
+ _ => Err(error).context(ks_err!()),
},
}
.no_gc()
})
- .context("In load_super_key.")
+ .context(ks_err!())
}
/// Atomically loads a key entry and associated metadata or creates it using the
@@ -1366,10 +1400,10 @@
AND alias = ?
AND state = ?;",
)
- .context("In get_or_create_key_with: Failed to select from keyentry table.")?;
+ .context(ks_err!("Failed to select from keyentry table."))?;
let mut rows = stmt
.query(params![KeyType::Super, domain.0, namespace, alias, KeyLifeCycle::Live])
- .context("In get_or_create_key_with: Failed to query from keyentry table.")?;
+ .context(ks_err!("Failed to query from keyentry table."))?;
db_utils::with_rows_extract_one(&mut rows, |row| {
Ok(match row {
@@ -1377,14 +1411,13 @@
None => None,
})
})
- .context("In get_or_create_key_with.")?
+ .context(ks_err!())?
};
let (id, entry) = match id {
Some(id) => (
id,
- Self::load_key_components(tx, KeyEntryLoadBits::KM, id)
- .context("In get_or_create_key_with.")?,
+ Self::load_key_components(tx, KeyEntryLoadBits::KM, id).context(ks_err!())?,
),
None => {
@@ -1404,10 +1437,9 @@
],
)
})
- .context("In get_or_create_key_with.")?;
+ .context(ks_err!())?;
- let (blob, metadata) =
- create_new_key().context("In get_or_create_key_with.")?;
+ let (blob, metadata) = create_new_key().context(ks_err!())?;
Self::set_blob_internal(
tx,
id,
@@ -1415,7 +1447,7 @@
Some(&blob),
Some(&metadata),
)
- .context("In get_or_create_key_with.")?;
+ .context(ks_err!())?;
(
id,
KeyEntry {
@@ -1429,7 +1461,7 @@
};
Ok((KEY_ID_LOCK.get(id), entry)).no_gc()
})
- .context("In get_or_create_key_with.")
+ .context(ks_err!())
}
/// Creates a transaction with the given behavior and executes f with the new transaction.
@@ -1443,10 +1475,10 @@
match self
.conn
.transaction_with_behavior(behavior)
- .context("In with_transaction.")
+ .context(ks_err!())
.and_then(|tx| f(&tx).map(|result| (result, tx)))
.and_then(|(result, tx)| {
- tx.commit().context("In with_transaction: Failed to commit transaction.")?;
+ tx.commit().context(ks_err!("Failed to commit transaction."))?;
Ok(result)
}) {
Ok(result) => break Ok(result),
@@ -1455,7 +1487,7 @@
std::thread::sleep(std::time::Duration::from_micros(500));
continue;
} else {
- return Err(e).context("In with_transaction.");
+ return Err(e).context(ks_err!());
}
}
}
@@ -1496,7 +1528,7 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
Self::create_key_entry_internal(tx, domain, namespace, key_type, km_uuid).no_gc()
})
- .context("In create_key_entry.")
+ .context(ks_err!())
}
fn create_key_entry_internal(
@@ -1510,7 +1542,7 @@
Domain::APP | Domain::SELINUX => {}
_ => {
return Err(KsError::sys())
- .context(format!("Domain {:?} must be either App or SELinux.", domain));
+ .context(ks_err!("Domain {:?} must be either App or SELinux.", domain));
}
}
Ok(KEY_ID_LOCK.get(
@@ -1529,7 +1561,7 @@
],
)
})
- .context("In create_key_entry_internal")?,
+ .context(ks_err!())?,
))
}
@@ -1557,7 +1589,7 @@
params![id, KeyType::Attestation, KeyLifeCycle::Live, km_uuid],
)
})
- .context("In create_key_entry")?,
+ .context(ks_err!())?,
);
Self::set_blob_internal(
tx,
@@ -1572,7 +1604,7 @@
metadata.store_in_db(key_id.0, tx)?;
Ok(()).no_gc()
})
- .context("In create_attestation_key_entry")
+ .context(ks_err!())
}
/// Set a new blob and associates it with the given key id. Each blob
@@ -1594,7 +1626,7 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
Self::set_blob_internal(tx, key_id.0, sc_type, blob, blob_metadata).need_gc()
})
- .context("In set_blob.")
+ .context(ks_err!())
}
/// Why would we insert a deleted blob? This weird function is for the purpose of legacy
@@ -1614,7 +1646,7 @@
)
.need_gc()
})
- .context("In set_deleted_blob.")
+ .context(ks_err!())
}
fn set_blob_internal(
@@ -1631,16 +1663,16 @@
(subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
params![sc_type, key_id, blob],
)
- .context("In set_blob_internal: Failed to insert blob.")?;
+ .context(ks_err!("Failed to insert blob."))?;
if let Some(blob_metadata) = blob_metadata {
let blob_id = tx
.query_row("SELECT MAX(id) FROM persistent.blobentry;", NO_PARAMS, |row| {
row.get(0)
})
- .context("In set_blob_internal: Failed to get new blob id.")?;
+ .context(ks_err!("Failed to get new blob id."))?;
blob_metadata
.store_in_db(blob_id, tx)
- .context("In set_blob_internal: Trying to store blob metadata.")?;
+ .context(ks_err!("Trying to store blob metadata."))?;
}
}
(None, SubComponentType::CERT) | (None, SubComponentType::CERT_CHAIN) => {
@@ -1649,11 +1681,11 @@
WHERE subcomponent_type = ? AND keyentryid = ?;",
params![sc_type, key_id],
)
- .context("In set_blob_internal: Failed to delete blob.")?;
+ .context(ks_err!("Failed to delete blob."))?;
}
(None, _) => {
return Err(KsError::sys())
- .context("In set_blob_internal: Other blobs cannot be deleted in this way.");
+ .context(ks_err!("Other blobs cannot be deleted in this way."));
}
}
Ok(())
@@ -1666,7 +1698,7 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
Self::insert_keyparameter_internal(tx, key_id, params).no_gc()
})
- .context("In insert_keyparameter.")
+ .context(ks_err!())
}
fn insert_keyparameter_internal(
@@ -1679,7 +1711,7 @@
"INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
VALUES (?, ?, ?, ?);",
)
- .context("In insert_keyparameter_internal: Failed to prepare statement.")?;
+ .context(ks_err!("Failed to prepare statement."))?;
for p in params.iter() {
stmt.insert(params![
@@ -1688,9 +1720,7 @@
p.key_parameter_value(),
p.security_level().0
])
- .with_context(|| {
- format!("In insert_keyparameter_internal: Failed to insert {:?}", p)
- })?;
+ .with_context(|| ks_err!("Failed to insert {:?}", p))?;
}
Ok(())
}
@@ -1701,7 +1731,7 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
metadata.store_in_db(key_id.0, tx).no_gc()
})
- .context("In insert_key_metadata.")
+ .context(ks_err!())
}
/// Stores a signed certificate chain signed by a remote provisioning server, keyed
@@ -1774,7 +1804,7 @@
.context("Failed to insert cert")?;
Ok(()).no_gc()
})
- .context("In store_signed_attestation_certificate_chain: ")
+ .context(ks_err!())
}
/// Assigns the next unassigned attestation key to a domain/namespace combo that does not
@@ -1790,13 +1820,8 @@
match domain {
Domain::APP | Domain::SELINUX => {}
_ => {
- return Err(KsError::sys()).context(format!(
- concat!(
- "In assign_attestation_key: Domain {:?} ",
- "must be either App or SELinux.",
- ),
- domain
- ));
+ return Err(KsError::sys())
+ .context(ks_err!("Domain {:?} must be either App or SELinux.", domain));
}
}
self.with_transaction(TransactionBehavior::Immediate, |tx| {
@@ -1831,7 +1856,9 @@
)
.context("Failed to assign attestation key")?;
if result == 0 {
- log_rkp_error_stats(MetricsRkpError::OUT_OF_KEYS);
+ let (_, hw_info) = get_keymint_dev_by_uuid(km_uuid)
+ .context("Error in retrieving keymint device by UUID.")?;
+ log_rkp_error_stats(MetricsRkpError::OUT_OF_KEYS, &hw_info.securityLevel);
return Err(KsError::Rc(ResponseCode::OUT_OF_KEYS)).context("Out of keys.");
} else if result > 1 {
return Err(KsError::sys())
@@ -1839,7 +1866,7 @@
}
Ok(()).no_gc()
})
- .context("In assign_attestation_key: ")
+ .context(ks_err!())
}
/// Retrieves num_keys number of attestation keys that have not yet been signed by a remote
@@ -1883,7 +1910,7 @@
.context("Failed to execute statement")?;
Ok(rows).no_gc()
})
- .context("In fetch_unsigned_attestation_keys")
+ .context(ks_err!())
}
/// Removes any keys that have expired as of the current time. Returns the number of keys
@@ -1909,8 +1936,11 @@
)?
.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,
+ SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64
+ + EXPIRATION_BUFFER_MS,
);
let mut num_deleted = 0;
for id in key_ids_to_check.iter().filter(|kt| kt.1 < curr_time).map(|kt| kt.0) {
@@ -1920,7 +1950,7 @@
}
Ok(num_deleted).do_gc(num_deleted != 0)
})
- .context("In delete_expired_attestation_keys: ")
+ .context(ks_err!())
}
/// Deletes all remotely provisioned attestation keys in the system, regardless of the state
@@ -1949,7 +1979,7 @@
.count() as i64;
Ok(num_deleted).do_gc(num_deleted != 0)
})
- .context("In delete_all_attestation_keys: ")
+ .context(ks_err!())
}
/// Counts the number of keys that will expire by the provided epoch date and the number of
@@ -2016,7 +2046,42 @@
}
Ok(AttestationPoolStatus { expiring, unassigned, attested, total }).no_gc()
})
- .context("In get_attestation_pool_status: ")
+ .context(ks_err!())
+ }
+
+ fn query_kid_for_attestation_key_and_cert_chain(
+ &self,
+ tx: &Transaction,
+ domain: Domain,
+ namespace: i64,
+ km_uuid: &Uuid,
+ ) -> Result<Option<i64>> {
+ let mut stmt = tx.prepare(
+ "SELECT id
+ FROM persistent.keyentry
+ WHERE key_type = ?
+ AND domain = ?
+ AND namespace = ?
+ AND state = ?
+ AND km_uuid = ?;",
+ )?;
+ let rows = stmt
+ .query_map(
+ params![
+ KeyType::Attestation,
+ domain.0 as u32,
+ namespace,
+ KeyLifeCycle::Live,
+ km_uuid
+ ],
+ |row| row.get(0),
+ )?
+ .collect::<rusqlite::Result<Vec<i64>>>()
+ .context("query failed.")?;
+ if rows.is_empty() {
+ return Ok(None);
+ }
+ Ok(Some(rows[0]))
}
/// Fetches the private key and corresponding certificate chain assigned to a
@@ -2027,7 +2092,7 @@
domain: Domain,
namespace: i64,
km_uuid: &Uuid,
- ) -> Result<Option<CertificateChain>> {
+ ) -> Result<Option<(KeyIdGuard, CertificateChain)>> {
let _wp = wd::watch_millis("KeystoreDB::retrieve_attestation_key_and_cert_chain", 500);
match domain {
@@ -2037,69 +2102,71 @@
.context(format!("Domain {:?} must be either App or SELinux.", domain));
}
}
- 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.")?,
+
+ self.delete_expired_attestation_keys()
+ .context(ks_err!("Failed to prune expired attestation keys",))?;
+ let tx = self
+ .conn
+ .unchecked_transaction()
+ .context(ks_err!("Failed to initialize transaction."))?;
+ let key_id: i64 = match self
+ .query_kid_for_attestation_key_and_cert_chain(&tx, domain, namespace, km_uuid)?
+ {
+ None => return Ok(None),
+ Some(kid) => kid,
+ };
+ tx.commit().context(ks_err!("Failed to commit keyid query"))?;
+ let key_id_guard = KEY_ID_LOCK.get(key_id);
+ let tx = self
+ .conn
+ .unchecked_transaction()
+ .context(ks_err!("Failed to initialize transaction."))?;
+ let mut stmt = tx.prepare(
+ "SELECT subcomponent_type, blob
+ FROM persistent.blobentry
+ WHERE keyentryid = ?;",
+ )?;
+ let rows = stmt
+ .query_map(params![key_id_guard.id()], |row| Ok((row.get(0)?, row.get(1)?)))?
+ .collect::<rusqlite::Result<Vec<(SubComponentType, Vec<u8>)>>>()
+ .context("query failed.")?;
+ if rows.is_empty() {
+ return Ok(None);
+ } else if rows.len() != 3 {
+ return Err(KsError::sys()).context(format!(
+ concat!(
+ "Expected to get a single attestation",
+ "key, cert, and cert chain for a total of 3 entries, but instead got {}."
+ ),
+ rows.len()
+ ));
+ }
+ let mut km_blob: Vec<u8> = Vec::new();
+ let mut cert_chain_blob: Vec<u8> = Vec::new();
+ let mut batch_cert_blob: Vec<u8> = Vec::new();
+ for row in rows {
+ let sub_type: SubComponentType = row.0;
+ match sub_type {
+ SubComponentType::KEY_BLOB => {
+ km_blob = row.1;
}
+ SubComponentType::CERT_CHAIN => {
+ cert_chain_blob = row.1;
+ }
+ SubComponentType::CERT => {
+ batch_cert_blob = row.1;
+ }
+ _ => Err(KsError::sys()).context("Unknown or incorrect subcomponent type.")?,
}
- Ok(Some(CertificateChain {
+ }
+ Ok(Some((
+ key_id_guard,
+ 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,
@@ -2118,10 +2185,8 @@
match *domain {
Domain::APP | Domain::SELINUX => {}
_ => {
- return Err(KsError::sys()).context(format!(
- "In rebind_alias: Domain {:?} must be either App or SELinux.",
- domain
- ));
+ return Err(KsError::sys())
+ .context(ks_err!("Domain {:?} must be either App or SELinux.", domain));
}
}
let updated = tx
@@ -2131,7 +2196,7 @@
WHERE alias = ? AND domain = ? AND namespace = ? AND key_type = ?;",
params![KeyLifeCycle::Unreferenced, alias, domain.0 as u32, namespace, key_type],
)
- .context("In rebind_alias: Failed to rebind existing entry.")?;
+ .context(ks_err!("Failed to rebind existing entry."))?;
let result = tx
.execute(
"UPDATE persistent.keyentry
@@ -2147,10 +2212,10 @@
key_type,
],
)
- .context("In rebind_alias: Failed to set alias.")?;
+ .context(ks_err!("Failed to set alias."))?;
if result != 1 {
- return Err(KsError::sys()).context(format!(
- "In rebind_alias: Expected to update a single entry but instead updated {}.",
+ return Err(KsError::sys()).context(ks_err!(
+ "Expected to update a single entry but instead updated {}.",
result
));
}
@@ -2178,14 +2243,13 @@
};
// Security critical: Must return immediately on failure. Do not remove the '?';
- check_permission(&destination)
- .context("In migrate_key_namespace: Trying to check permission.")?;
+ check_permission(&destination).context(ks_err!("Trying to check permission."))?;
let alias = destination
.alias
.as_ref()
.ok_or(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
- .context("In migrate_key_namespace: Alias must be specified.")?;
+ .context(ks_err!("Alias must be specified."))?;
self.with_transaction(TransactionBehavior::Immediate, |tx| {
// Query the destination location. If there is a key, the migration request fails.
@@ -2219,7 +2283,7 @@
}
Ok(()).no_gc()
})
- .context("In migrate_key_namespace:")
+ .context(ks_err!())
}
/// Store a new key in a single transaction.
@@ -2233,7 +2297,7 @@
key: &KeyDescriptor,
key_type: KeyType,
params: &[KeyParameter],
- blob_info: &(&[u8], &BlobMetaData),
+ blob_info: &BlobInfo,
cert_info: &CertificateInfo,
metadata: &KeyMetaData,
km_uuid: &Uuid,
@@ -2247,13 +2311,33 @@
}
_ => {
return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
- .context("In store_new_key: Need alias and domain must be APP or SELINUX.")
+ .context(ks_err!("Need alias and domain must be APP or SELINUX."));
}
};
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 (blob, blob_metadata) = *blob_info;
+ 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
+ };
+
Self::set_blob_internal(
tx,
key_id.id(),
@@ -2280,10 +2364,11 @@
.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.")?;
+ .context("Trying to rebind alias.")?
+ || need_gc;
Ok(key_id).do_gc(need_gc)
})
- .context("In store_new_key.")
+ .context(ks_err!())
}
/// Store a new certificate
@@ -2304,9 +2389,8 @@
(alias, key.domain, nspace)
}
_ => {
- return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)).context(
- "In store_new_certificate: Need alias and domain must be APP or SELINUX.",
- )
+ return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context(ks_err!("Need alias and domain must be APP or SELINUX."));
}
};
self.with_transaction(TransactionBehavior::Immediate, |tx| {
@@ -2333,7 +2417,7 @@
.context("Trying to rebind alias.")?;
Ok(key_id).do_gc(need_gc)
})
- .context("In store_new_certificate.")
+ .context(ks_err!())
}
// Helper function loading the key_id given the key descriptor
@@ -2364,7 +2448,7 @@
.get(0)
.context("Failed to unpack id.")
})
- .context("In load_key_entry_id.")
+ .context(ks_err!())
}
/// This helper function completes the access tuple of a key, which is required
@@ -2485,7 +2569,7 @@
Ok((key_id, access_key, access_vector))
}
- _ => Err(anyhow!(KsError::sys())),
+ _ => Err(anyhow!(KsError::Rc(ResponseCode::INVALID_ARGUMENT))),
}
}
@@ -2499,10 +2583,9 @@
"SELECT MAX(id), subcomponent_type, blob FROM persistent.blobentry
WHERE keyentryid = ? GROUP BY subcomponent_type;",
)
- .context("In load_blob_components: prepare statement failed.")?;
+ .context(ks_err!("prepare statement failed."))?;
- let mut rows =
- stmt.query(params![key_id]).context("In load_blob_components: query failed.")?;
+ let mut rows = stmt.query(params![key_id]).context(ks_err!("query failed."))?;
let mut key_blob: Option<(i64, Vec<u8>)> = None;
let mut cert_blob: Option<Vec<u8>> = None;
@@ -2534,13 +2617,13 @@
}
Ok(())
})
- .context("In load_blob_components.")?;
+ .context(ks_err!())?;
let blob_info = key_blob.map_or::<Result<_>, _>(Ok(None), |(blob_id, blob)| {
Ok(Some((
blob,
BlobMetaData::load_from_db(blob_id, tx)
- .context("In load_blob_components: Trying to load blob_metadata.")?,
+ .context(ks_err!("Trying to load blob_metadata."))?,
)))
})?;
@@ -2568,7 +2651,7 @@
);
Ok(())
})
- .context("In load_key_parameters.")?;
+ .context(ks_err!())?;
Ok(parameters)
}
@@ -2610,7 +2693,7 @@
_ => Ok(()).no_gc(),
}
})
- .context("In check_and_update_key_usage_count.")
+ .context(ks_err!())
}
/// Load a key entry by the given key descriptor.
@@ -2642,7 +2725,7 @@
std::thread::sleep(std::time::Duration::from_micros(500));
continue;
} else {
- return Err(e).context("In load_key_entry.");
+ return Err(e).context(ks_err!());
}
}
}
@@ -2668,16 +2751,15 @@
let tx = self
.conn
.unchecked_transaction()
- .context("In load_key_entry: Failed to initialize transaction.")?;
+ .context(ks_err!("Failed to initialize transaction."))?;
// Load the key_id and complete the access control tuple.
let (key_id, access_key_descriptor, access_vector) =
- Self::load_access_tuple(&tx, key, key_type, caller_uid)
- .context("In load_key_entry.")?;
+ Self::load_access_tuple(&tx, key, key_type, caller_uid).context(ks_err!())?;
// Perform access control. It is vital that we return here if the permission is denied.
// So do not touch that '?' at the end.
- check_permission(&access_key_descriptor, access_vector).context("In load_key_entry.")?;
+ check_permission(&access_key_descriptor, access_vector).context(ks_err!())?;
// KEY ID LOCK 2/2
// If we did not get a key id lock by now, it was because we got a key descriptor
@@ -2694,7 +2776,7 @@
None => match KEY_ID_LOCK.try_get(key_id) {
None => {
// Roll back the transaction.
- tx.rollback().context("In load_key_entry: Failed to roll back transaction.")?;
+ tx.rollback().context(ks_err!("Failed to roll back transaction."))?;
// Block until we have a key id lock.
let key_id_guard = KEY_ID_LOCK.get(key_id);
@@ -2703,7 +2785,7 @@
let tx = self
.conn
.unchecked_transaction()
- .context("In load_key_entry: Failed to initialize transaction.")?;
+ .context(ks_err!("Failed to initialize transaction."))?;
Self::load_access_tuple(
&tx,
@@ -2717,7 +2799,7 @@
key_type,
caller_uid,
)
- .context("In load_key_entry. (deferred key lock)")?;
+ .context(ks_err!("(deferred key lock)"))?;
(key_id_guard, tx)
}
Some(l) => (l, tx),
@@ -2725,10 +2807,10 @@
Some(key_id_guard) => (key_id_guard, tx),
};
- let key_entry = Self::load_key_components(&tx, load_bits, key_id_guard.id())
- .context("In load_key_entry.")?;
+ let key_entry =
+ Self::load_key_components(&tx, load_bits, key_id_guard.id()).context(ks_err!())?;
- tx.commit().context("In load_key_entry: Failed to commit transaction.")?;
+ tx.commit().context(ks_err!("Failed to commit transaction."))?;
Ok((key_id_guard, key_entry))
}
@@ -2771,7 +2853,7 @@
.map(|need_gc| (need_gc, ()))
.context("Trying to mark the key unreferenced.")
})
- .context("In unbind_key.")
+ .context(ks_err!())
}
fn get_key_km_uuid(tx: &Transaction, key_id: i64) -> Result<Uuid> {
@@ -2780,7 +2862,7 @@
params![key_id],
|row| row.get(0),
)
- .context("In get_key_km_uuid.")
+ .context(ks_err!())
}
/// Delete all artifacts belonging to the namespace given by the domain-namespace tuple.
@@ -2789,46 +2871,45 @@
let _wp = wd::watch_millis("KeystoreDB::unbind_keys_for_namespace", 500);
if !(domain == Domain::APP || domain == Domain::SELINUX) {
- return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
- .context("In unbind_keys_for_namespace.");
+ return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!());
}
self.with_transaction(TransactionBehavior::Immediate, |tx| {
tx.execute(
"DELETE FROM persistent.keymetadata
WHERE keyentryid IN (
SELECT id FROM persistent.keyentry
- WHERE domain = ? AND namespace = ? AND key_type = ?
+ WHERE domain = ? AND namespace = ? AND (key_type = ? OR key_type = ?)
);",
- params![domain.0, namespace, KeyType::Client],
+ params![domain.0, namespace, KeyType::Client, KeyType::Attestation],
)
.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 = ?
+ WHERE domain = ? AND namespace = ? AND (key_type = ? OR key_type = ?)
);",
- params![domain.0, namespace, KeyType::Client],
+ params![domain.0, namespace, KeyType::Client, KeyType::Attestation],
)
.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 = ?
+ WHERE domain = ? AND namespace = ? AND (key_type = ? OR key_type = ?)
);",
- params![domain.0, namespace, KeyType::Client],
+ params![domain.0, namespace, KeyType::Client, KeyType::Attestation],
)
.context("Trying to delete grants.")?;
tx.execute(
"DELETE FROM persistent.keyentry
- WHERE domain = ? AND namespace = ? AND key_type = ?;",
- params![domain.0, namespace, KeyType::Client],
+ WHERE domain = ? AND namespace = ? AND (key_type = ? OR key_type = ?);",
+ params![domain.0, namespace, KeyType::Client, KeyType::Attestation],
)
.context("Trying to delete keyentry.")?;
Ok(()).need_gc()
})
- .context("In unbind_keys_for_namespace")
+ .context(ks_err!())
}
fn cleanup_unreferenced(tx: &Transaction) -> Result<()> {
@@ -2869,7 +2950,7 @@
.context("Trying to delete keyentry.")?;
Result::<()>::Ok(())
}
- .context("In cleanup_unreferenced")
+ .context(ks_err!())
}
/// Delete the keys created on behalf of the user, denoted by the user id.
@@ -2895,7 +2976,6 @@
) OR (
key_type = ?
AND namespace = ?
- AND alias = ?
AND state = ?
);",
aid_user_offset = AID_USER_OFFSET
@@ -2915,10 +2995,9 @@
// 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.")?;
+ .context(ks_err!("Failed to query the keys created by apps."))?;
let mut key_ids: Vec<i64> = Vec::new();
db_utils::with_rows_extract_all(&mut rows, |row| {
@@ -2926,7 +3005,7 @@
.push(row.get(0).context("Failed to read key id of a key created by an app.")?);
Ok(())
})
- .context("In unbind_keys_for_user.")?;
+ .context(ks_err!())?;
let mut notify_gc = false;
for key_id in key_ids {
@@ -2934,7 +3013,7 @@
// Load metadata and filter out non-super-encrypted keys.
if let (_, Some((_, blob_metadata)), _, _) =
Self::load_blob_components(key_id, KeyEntryLoadBits::KM, tx)
- .context("In unbind_keys_for_user: Trying to load blob info.")?
+ .context(ks_err!("Trying to load blob info."))?
{
if blob_metadata.encrypted_by().is_none() {
continue;
@@ -2947,7 +3026,7 @@
}
Ok(()).do_gc(notify_gc)
})
- .context("In unbind_keys_for_user.")
+ .context(ks_err!())
}
fn load_key_components(
@@ -2999,11 +3078,11 @@
AND state = ?
AND key_type = ?;",
)
- .context("In list: Failed to prepare.")?;
+ .context(ks_err!("Failed to prepare."))?;
let mut rows = stmt
.query(params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type])
- .context("In list: Failed to query.")?;
+ .context(ks_err!("Failed to query."))?;
let mut descriptors: Vec<KeyDescriptor> = Vec::new();
db_utils::with_rows_extract_all(&mut rows, |row| {
@@ -3015,7 +3094,7 @@
});
Ok(())
})
- .context("In list: Failed to extract rows.")?;
+ .context(ks_err!("Failed to extract rows."))?;
Ok(descriptors).no_gc()
})
}
@@ -3047,8 +3126,7 @@
// 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)
- .context("In grant")?;
+ Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(ks_err!())?;
// Perform access control. It is vital that we return here if the permission
// was denied. So do not touch that '?' at the end of the line.
@@ -3056,7 +3134,7 @@
// for the given key and in addition to all of the permissions
// expressed in `access_vector`.
check_permission(&access_key_descriptor, &access_vector)
- .context("In grant: check_permission failed.")?;
+ .context(ks_err!("check_permission failed"))?;
let grant_id = if let Some(grant_id) = tx
.query_row(
@@ -3066,7 +3144,7 @@
|row| row.get(0),
)
.optional()
- .context("In grant: Failed get optional existing grant id.")?
+ .context(ks_err!("Failed get optional existing grant id."))?
{
tx.execute(
"UPDATE persistent.grant
@@ -3074,7 +3152,7 @@
WHERE id = ?;",
params![i32::from(access_vector), grant_id],
)
- .context("In grant: Failed to update existing grant.")?;
+ .context(ks_err!("Failed to update existing grant."))?;
grant_id
} else {
Self::insert_with_retry(|id| {
@@ -3084,7 +3162,7 @@
params![id, grantee_uid, key_id, i32::from(access_vector)],
)
})
- .context("In grant")?
+ .context(ks_err!())?
};
Ok(KeyDescriptor { domain: Domain::GRANT, nspace: grant_id, alias: None, blob: None })
@@ -3107,13 +3185,12 @@
// 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)
- .context("In ungrant.")?;
+ Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(ks_err!())?;
// Perform access control. We must return here if the permission
// was denied. So do not touch the '?' at the end of this line.
check_permission(&access_key_descriptor)
- .context("In grant: check_permission failed.")?;
+ .context(ks_err!("check_permission failed."))?;
tx.execute(
"DELETE FROM persistent.grant
@@ -3145,7 +3222,7 @@
_,
)) => (),
Err(e) => {
- return Err(e).context("In insert_with_retry: failed to insert into database.")
+ return Err(e).context(ks_err!("failed to insert into database."));
}
_ => return Ok(newid),
}
@@ -3204,12 +3281,12 @@
.context("Trying to load key descriptor")
.no_gc()
})
- .context("In load_key_descriptor.")
+ .context(ks_err!())
}
}
#[cfg(test)]
-mod tests {
+pub mod tests {
use super::*;
use crate::key_parameter::{
@@ -3218,7 +3295,7 @@
};
use crate::key_perm_set;
use crate::permission::{KeyPerm, KeyPermSet};
- use crate::super_key::SuperKeyManager;
+ use crate::super_key::{SuperKeyManager, USER_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType};
use keystore2_test_utils::TempDir;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken,
@@ -3233,13 +3310,14 @@
use std::collections::BTreeMap;
use std::fmt::Write;
use std::sync::atomic::{AtomicU8, Ordering};
- use std::sync::Arc;
+ use std::sync::{Arc, RwLock};
use std::thread;
use std::time::{Duration, SystemTime};
+ use crate::utils::AesGcm;
#[cfg(disabled)]
use std::time::Instant;
- fn new_test_db() -> Result<KeystoreDB> {
+ pub 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()) };
@@ -3253,7 +3331,7 @@
where
F: Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static,
{
- let super_key: Arc<SuperKeyManager> = Default::default();
+ let super_key: Arc<RwLock<SuperKeyManager>> = Default::default();
let gc_db = KeystoreDB::new(path, None).expect("Failed to open test gc db_connection.");
let gc = Gc::new_init_with(Default::default(), move || (Box::new(cb), gc_db, super_key));
@@ -3271,7 +3349,7 @@
db.with_transaction(TransactionBehavior::Immediate, |tx| {
KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace, KeyType::Client).no_gc()
})
- .context("In rebind_alias.")
+ .context(ks_err!())
}
#[test]
@@ -3423,15 +3501,15 @@
// Test that we must pass in a valid Domain.
check_result_is_error_containing_string(
db.create_key_entry(&Domain::GRANT, &102, KeyType::Client, &KEYSTORE_UUID),
- "Domain Domain(1) must be either App or SELinux.",
+ &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
);
check_result_is_error_containing_string(
db.create_key_entry(&Domain::BLOB, &103, KeyType::Client, &KEYSTORE_UUID),
- "Domain Domain(3) must be either App or SELinux.",
+ &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
);
check_result_is_error_containing_string(
db.create_key_entry(&Domain::KEY_ID, &104, KeyType::Client, &KEYSTORE_UUID),
- "Domain Domain(4) must be either App or SELinux.",
+ &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
);
Ok(())
@@ -3458,7 +3536,10 @@
#[test]
fn test_store_signed_attestation_certificate_chain() -> Result<()> {
let mut db = new_test_db()?;
- let expiration_date: i64 = 20;
+ let expiration_date: i64 =
+ SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64
+ + EXPIRATION_BUFFER_MS
+ + 10000;
let namespace: i64 = 30;
let base_byte: u8 = 1;
let loaded_values =
@@ -3466,7 +3547,7 @@
let chain =
db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace, &KEYSTORE_UUID)?;
assert!(chain.is_some());
- let cert_chain = chain.unwrap();
+ 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);
@@ -3535,7 +3616,9 @@
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 + 10000;
+ SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64
+ + EXPIRATION_BUFFER_MS
+ + 10000;
let namespace: i64 = 30;
let namespace_del1: i64 = 45;
let namespace_del2: i64 = 60;
@@ -3546,7 +3629,7 @@
0x01, /* base_byte */
)?;
load_attestation_key_pool(&mut db, 45, namespace_del1, 0x02)?;
- load_attestation_key_pool(&mut db, 60, namespace_del2, 0x03)?;
+ load_attestation_key_pool(&mut db, expiration_date - 10001, namespace_del2, 0x03)?;
let blob_entry_row_count: u32 = db
.conn
@@ -3561,7 +3644,7 @@
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());
@@ -3571,13 +3654,13 @@
namespace_del1,
&KEYSTORE_UUID,
)?;
- assert!(!cert_chain.is_some());
+ assert!(cert_chain.is_none());
cert_chain = db.retrieve_attestation_key_and_cert_chain(
Domain::APP,
namespace_del2,
&KEYSTORE_UUID,
)?;
- assert!(!cert_chain.is_some());
+ assert!(cert_chain.is_none());
// Give the garbage collector half a second to catch up.
std::thread::sleep(Duration::from_millis(500));
@@ -3593,6 +3676,73 @@
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()?;
@@ -3658,15 +3808,15 @@
// Test that we must pass in a valid Domain.
check_result_is_error_containing_string(
rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::GRANT, 42),
- "Domain Domain(1) must be either App or SELinux.",
+ &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
);
check_result_is_error_containing_string(
rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::BLOB, 42),
- "Domain Domain(3) must be either App or SELinux.",
+ &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
);
check_result_is_error_containing_string(
rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::KEY_ID, 42),
- "Domain Domain(4) must be either App or SELinux.",
+ &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
);
// Test that we correctly handle setting an alias for something that does not exist.
@@ -3704,8 +3854,8 @@
alias: Some("key".to_string()),
blob: None,
};
- const PVEC1: KeyPermSet = key_perm_set![KeyPerm::use_(), KeyPerm::get_info()];
- const PVEC2: KeyPermSet = key_perm_set![KeyPerm::use_()];
+ const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo];
+ 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.
@@ -4181,7 +4331,7 @@
},
1,
2,
- key_perm_set![KeyPerm::use_()],
+ key_perm_set![KeyPerm::Use],
|_k, _av| Ok(()),
)
.unwrap();
@@ -4191,7 +4341,7 @@
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();
@@ -4238,7 +4388,7 @@
},
OWNER_UID,
GRANTEE_UID,
- key_perm_set![KeyPerm::use_()],
+ key_perm_set![KeyPerm::Use],
|_k, _av| Ok(()),
)
.unwrap();
@@ -4257,7 +4407,7 @@
|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(())
},
)
@@ -4872,8 +5022,8 @@
let list_o_keys: Vec<(i64, i64)> = LIST_O_ENTRIES
.iter()
.map(|(domain, ns, alias)| {
- let entry = make_test_key_entry(&mut db, *domain, *ns, *alias, None)
- .unwrap_or_else(|e| {
+ let entry =
+ make_test_key_entry(&mut db, *domain, *ns, alias, None).unwrap_or_else(|e| {
panic!("Failed to insert {:?} {} {}. Error {:?}", domain, ns, alias, e)
});
(entry.id(), *ns)
@@ -5185,6 +5335,10 @@
SecurityLevel::TRUSTED_ENVIRONMENT,
),
KeyParameter::new(
+ KeyParameterValue::AttestationIdSecondIMEI(vec![4u8, 3u8, 1u8, 2u8]),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
KeyParameterValue::AttestationIdMEID(vec![4u8, 3u8, 1u8, 2u8]),
SecurityLevel::TRUSTED_ENVIRONMENT,
),
@@ -5456,6 +5610,80 @@
}
#[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();
@@ -5474,7 +5702,7 @@
&KeyMetaData::new(),
)?;
- //check if super key exists
+ // 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();
@@ -5485,9 +5713,9 @@
None,
)?;
- let decrypted_secret_bytes =
- loaded_super_key.aes_gcm_decrypt(&encrypted_secret, &iv, &tag)?;
+ let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?;
assert_eq!(secret_bytes, &*decrypted_secret_bytes);
+
Ok(())
}
@@ -5675,7 +5903,7 @@
},
OWNER as u32,
123,
- key_perm_set![KeyPerm::use_()],
+ key_perm_set![KeyPerm::Use],
|_, _| Ok(()),
)?;
diff --git a/keystore2/src/ec_crypto.rs b/keystore2/src/ec_crypto.rs
index 0425d4a..4fb3747 100644
--- a/keystore2/src/ec_crypto.rs
+++ b/keystore2/src/ec_crypto.rs
@@ -14,6 +14,7 @@
//! Implement ECDH-based encryption.
+use crate::ks_err;
use anyhow::{Context, Result};
use keystore2_crypto::{
aes_gcm_decrypt, aes_gcm_encrypt, ec_key_generate_key, ec_key_get0_public_key,
@@ -28,29 +29,23 @@
impl ECDHPrivateKey {
/// Randomly generate a fresh keypair.
pub fn generate() -> Result<ECDHPrivateKey> {
- ec_key_generate_key()
- .map(ECDHPrivateKey)
- .context("In ECDHPrivateKey::generate: generation failed")
+ ec_key_generate_key().map(ECDHPrivateKey).context(ks_err!("generation failed"))
}
/// Deserialize bytes into an ECDH keypair
pub fn from_private_key(buf: &[u8]) -> Result<ECDHPrivateKey> {
- ec_key_parse_private_key(buf)
- .map(ECDHPrivateKey)
- .context("In ECDHPrivateKey::from_private_key: parsing failed")
+ ec_key_parse_private_key(buf).map(ECDHPrivateKey).context(ks_err!("parsing failed"))
}
/// Serialize the ECDH key into bytes
pub fn private_key(&self) -> Result<ZVec> {
- ec_key_marshal_private_key(&self.0)
- .context("In ECDHPrivateKey::private_key: marshalling failed")
+ ec_key_marshal_private_key(&self.0).context(ks_err!("marshalling failed"))
}
/// Generate the serialization of the corresponding public key
pub fn public_key(&self) -> Result<Vec<u8>> {
let point = ec_key_get0_public_key(&self.0);
- ec_point_point_to_oct(point.get_point())
- .context("In ECDHPrivateKey::public_key: marshalling failed")
+ ec_point_point_to_oct(point.get_point()).context(ks_err!("marshalling failed"))
}
/// Use ECDH to agree an AES key with another party whose public key we have.
@@ -64,18 +59,17 @@
recipient_public_key: &[u8],
) -> Result<ZVec> {
let hkdf = hkdf_extract(sender_public_key, salt)
- .context("In ECDHPrivateKey::agree_key: hkdf_extract on sender_public_key failed")?;
+ .context(ks_err!("hkdf_extract on sender_public_key failed"))?;
let hkdf = hkdf_extract(recipient_public_key, &hkdf)
- .context("In ECDHPrivateKey::agree_key: hkdf_extract on recipient_public_key failed")?;
+ .context(ks_err!("hkdf_extract on recipient_public_key failed"))?;
let other_public_key = ec_point_oct_to_point(other_public_key)
- .context("In ECDHPrivateKey::agree_key: ec_point_oct_to_point failed")?;
+ .context(ks_err!("ec_point_oct_to_point failed"))?;
let secret = ecdh_compute_key(other_public_key.get_point(), &self.0)
- .context("In ECDHPrivateKey::agree_key: ecdh_compute_key failed")?;
- let prk = hkdf_extract(&secret, &hkdf)
- .context("In ECDHPrivateKey::agree_key: hkdf_extract on secret failed")?;
+ .context(ks_err!("ecdh_compute_key failed"))?;
+ let prk = hkdf_extract(&secret, &hkdf).context(ks_err!("hkdf_extract on secret failed"))?;
let aes_key = hkdf_expand(AES_256_KEY_LENGTH, &prk, b"AES-256-GCM key")
- .context("In ECDHPrivateKey::agree_key: hkdf_expand failed")?;
+ .context(ks_err!("hkdf_expand failed"))?;
Ok(aes_key)
}
@@ -84,18 +78,14 @@
recipient_public_key: &[u8],
message: &[u8],
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>)> {
- let sender_key =
- Self::generate().context("In ECDHPrivateKey::encrypt_message: generate failed")?;
- let sender_public_key = sender_key
- .public_key()
- .context("In ECDHPrivateKey::encrypt_message: public_key failed")?;
- let salt =
- generate_salt().context("In ECDHPrivateKey::encrypt_message: generate_salt failed")?;
+ let sender_key = Self::generate().context(ks_err!("generate failed"))?;
+ let sender_public_key = sender_key.public_key().context(ks_err!("public_key failed"))?;
+ let salt = generate_salt().context(ks_err!("generate_salt failed"))?;
let aes_key = sender_key
.agree_key(&salt, recipient_public_key, &sender_public_key, recipient_public_key)
- .context("In ECDHPrivateKey::encrypt_message: agree_key failed")?;
- let (ciphertext, iv, tag) = aes_gcm_encrypt(message, &aes_key)
- .context("In ECDHPrivateKey::encrypt_message: aes_gcm_encrypt failed")?;
+ .context(ks_err!("agree_key failed"))?;
+ let (ciphertext, iv, tag) =
+ aes_gcm_encrypt(message, &aes_key).context(ks_err!("aes_gcm_encrypt failed"))?;
Ok((sender_public_key, salt, iv, ciphertext, tag))
}
@@ -111,9 +101,8 @@
let recipient_public_key = self.public_key()?;
let aes_key = self
.agree_key(salt, sender_public_key, sender_public_key, &recipient_public_key)
- .context("In ECDHPrivateKey::decrypt_message: agree_key failed")?;
- aes_gcm_decrypt(ciphertext, iv, tag, &aes_key)
- .context("In ECDHPrivateKey::decrypt_message: aes_gcm_decrypt failed")
+ .context(ks_err!("agree_key failed"))?;
+ aes_gcm_decrypt(ciphertext, iv, tag, &aes_key).context(ks_err!("aes_gcm_decrypt failed"))
}
}
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 997e739..8d5e985 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -14,6 +14,7 @@
//! This is the Keystore 2.0 Enforcements module.
// TODO: more description to follow.
+use crate::ks_err;
use crate::error::{map_binder_status, Error, ErrorCode};
use crate::globals::{get_timestamp_service, ASYNC_TASK, DB, ENFORCEMENTS};
use crate::key_parameter::{KeyParameter, KeyParameterValue};
@@ -95,14 +96,14 @@
.unwrap()
.take()
.ok_or(Error::Km(ErrorCode::KEY_USER_NOT_AUTHENTICATED))
- .context("In get_auth_tokens: No operation auth token received.")?;
+ .context(ks_err!("No operation auth token received."))?;
let tst = match &self.state {
AuthRequestState::TimeStampedOpAuth(recv) | AuthRequestState::TimeStamp(recv) => {
let result = recv.recv().context("In get_auth_tokens: Sender disconnected.")?;
- Some(result.context(concat!(
- "In get_auth_tokens: Worker responded with error ",
- "from generating timestamp token."
+ Some(result.context(ks_err!(
+ "Worker responded with error \
+ from generating timestamp token.",
))?)
}
AuthRequestState::OpAuth => None,
@@ -228,10 +229,7 @@
fn timestamp_token_request(challenge: i64, sender: Sender<Result<TimeStampToken, Error>>) {
if let Err(e) = sender.send(get_timestamp_token(challenge)) {
log::info!(
- concat!(
- "In timestamp_token_request: Receiver hung up ",
- "before timestamp token could be delivered. {:?}"
- ),
+ concat!("Receiver hung up ", "before timestamp token could be delivered. {:?}"),
e
);
}
@@ -322,7 +320,7 @@
.check_and_update_key_usage_count(key_id)
.context("Trying to update key usage count.")
})
- .context("In after_finish.")?;
+ .context(ks_err!())?;
}
Ok(())
}
@@ -349,14 +347,14 @@
DeferredAuthState::OpAuthRequired
| DeferredAuthState::TimeStampedOpAuthRequired
| DeferredAuthState::TimeStampRequired(_) => {
- Err(Error::Km(ErrorCode::KEY_USER_NOT_AUTHENTICATED)).context(concat!(
- "In AuthInfo::get_auth_tokens: No operation auth token requested??? ",
- "This should not happen."
+ Err(Error::Km(ErrorCode::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!(
+ "No operation auth token requested??? \
+ This should not happen."
))
}
// This should not be reachable, because it should have been handled above.
DeferredAuthState::Waiting(_) => {
- Err(Error::sys()).context("In AuthInfo::get_auth_tokens: Cannot be reached.")
+ Err(Error::sys()).context(ks_err!("AuthInfo::get_auth_tokens: Cannot be reached.",))
}
}
}
@@ -418,7 +416,7 @@
key_usage_limited: None,
confirmation_token_receiver: None,
},
- ))
+ ));
}
};
@@ -428,7 +426,7 @@
// Rule out WRAP_KEY purpose
KeyPurpose::WRAP_KEY => {
return Err(Error::Km(Ec::INCOMPATIBLE_PURPOSE))
- .context("In authorize_create: WRAP_KEY purpose is not allowed here.");
+ .context(ks_err!("WRAP_KEY purpose is not allowed here.",));
}
// Allow AGREE_KEY for EC keys only.
KeyPurpose::AGREE_KEY => {
@@ -436,9 +434,8 @@
if kp.get_tag() == Tag::ALGORITHM
&& *kp.key_parameter_value() != KeyParameterValue::Algorithm(Algorithm::EC)
{
- return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)).context(
- "In authorize_create: key agreement is only supported for EC keys.",
- );
+ return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE))
+ .context(ks_err!("key agreement is only supported for EC keys.",));
}
}
}
@@ -449,10 +446,10 @@
match *kp.key_parameter_value() {
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
- supported.",
- );
+ return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)).context(ks_err!(
+ "public operations on asymmetric keys are \
+ not supported."
+ ));
}
_ => {}
}
@@ -460,7 +457,7 @@
}
_ => {
return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE))
- .context("In authorize_create: specified purpose is not supported.");
+ .context(ks_err!("authorize_create: specified purpose is not supported."));
}
}
// The following variables are to record information from key parameters to be used in
@@ -505,23 +502,21 @@
KeyParameterValue::ActiveDateTime(a) => {
if !Enforcements::is_given_time_passed(*a, true) {
return Err(Error::Km(Ec::KEY_NOT_YET_VALID))
- .context("In authorize_create: key is not yet active.");
+ .context(ks_err!("key is not yet active."));
}
}
KeyParameterValue::OriginationExpireDateTime(o) => {
if (purpose == KeyPurpose::ENCRYPT || purpose == KeyPurpose::SIGN)
&& Enforcements::is_given_time_passed(*o, false)
{
- return Err(Error::Km(Ec::KEY_EXPIRED))
- .context("In authorize_create: key is expired.");
+ return Err(Error::Km(Ec::KEY_EXPIRED)).context(ks_err!("key is expired."));
}
}
KeyParameterValue::UsageExpireDateTime(u) => {
if (purpose == KeyPurpose::DECRYPT || purpose == KeyPurpose::VERIFY)
&& Enforcements::is_given_time_passed(*u, false)
{
- return Err(Error::Km(Ec::KEY_EXPIRED))
- .context("In authorize_create: key is expired.");
+ return Err(Error::Km(Ec::KEY_EXPIRED)).context(ks_err!("key is expired."));
}
}
KeyParameterValue::UserSecureID(s) => {
@@ -560,25 +555,23 @@
// authorize the purpose
if !key_purpose_authorized {
return Err(Error::Km(Ec::INCOMPATIBLE_PURPOSE))
- .context("In authorize_create: the purpose is not authorized.");
+ .context(ks_err!("the purpose is not authorized."));
}
// 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.",
- );
+ return Err(Error::Km(Ec::INVALID_KEY_BLOB))
+ .context(ks_err!("key has both NO_AUTH_REQUIRED and USER_SECURE_ID tags."));
}
// if either of auth_type or secure_id is present and the other is not present, return error
if (user_auth_type.is_some() && user_secure_ids.is_empty())
|| (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.",
- );
+ return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!(
+ "Auth required, but either auth type or secure ids \
+ are not present."
+ ));
}
// validate caller nonce for origination purposes
@@ -586,25 +579,22 @@
&& !caller_nonce_allowed
&& 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",
- );
+ return Err(Error::Km(Ec::CALLER_NONCE_PROHIBITED))
+ .context(ks_err!("NONCE is present, although CALLER_NONCE is not present"));
}
if unlocked_device_required {
// check the device locked status. If locked, operations on the key are not
// allowed.
if self.is_device_locked(user_id) {
- return Err(Error::Km(Ec::DEVICE_LOCKED))
- .context("In authorize_create: device is locked.");
+ return Err(Error::Km(Ec::DEVICE_LOCKED)).context(ks_err!("device is locked."));
}
}
if let Some(level) = max_boot_level {
- if !SUPER_KEY.level_accessible(level) {
+ if !SUPER_KEY.read().unwrap().level_accessible(level) {
return Err(Error::Km(Ec::BOOT_LEVEL_EXCEEDED))
- .context("In authorize_create: boot level is too late.");
+ .context(ks_err!("boot level is too late."));
}
}
@@ -629,7 +619,7 @@
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, has_sids) {
+ if let (Some(auth_type), true) = (user_auth_type, timeout_bound) {
hat.satisfies(&user_secure_ids, auth_type)
} else {
unlocked_device_required
@@ -638,7 +628,7 @@
Some(
hat_and_last_off_body
.ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED))
- .context("In authorize_create: No suitable auth token found.")?,
+ .context(ks_err!("No suitable auth token found."))?,
)
} else {
None
@@ -651,16 +641,16 @@
let token_age = now
.checked_sub(&hat.time_received())
.ok_or_else(Error::sys)
- .context(concat!(
- "In authorize_create: Overflow while computing Auth token validity. ",
- "Validity cannot be established."
+ .context(ks_err!(
+ "Overflow while computing Auth token validity. \
+ Validity cannot be established."
))?;
let on_body_extended = allow_while_on_body && last_off_body < hat.time_received();
if token_age.seconds() > key_time_out && !on_body_extended {
return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED))
- .context("In authorize_create: matching auth token is expired.");
+ .context(ks_err!("matching auth token is expired."));
}
Some(hat)
}
@@ -834,20 +824,20 @@
auth_token_entry.take_auth_token()
} else {
return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND))
- .context("In get_auth_tokens: No auth token found.");
+ .context(ks_err!("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."
+ ks_err!(
+ "No auth token found for \
+ the given challenge and passed-in auth token max age is zero."
),
);
}
};
// Wait and obtain the timestamp token from secure clock service.
- let tst = get_timestamp_token(challenge)
- .context("In get_auth_tokens. Error in getting timestamp token.")?;
+ let tst =
+ get_timestamp_token(challenge).context(ks_err!("Error in getting timestamp token."))?;
Ok((auth_token, tst))
}
}
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index f969cb6..b60b64f 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -37,10 +37,11 @@
};
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.
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// Wraps a Keystore `ResponseCode` as defined by the Keystore AIDL interface specification.
#[error("Error::Rc({0:?})")]
@@ -66,10 +67,15 @@
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
@@ -184,6 +190,20 @@
)
}
+/// 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.
@@ -200,7 +220,10 @@
|e| {
let e = map_err(e);
let rc = get_error_code(&e);
- Err(BinderStatus::new_service_specific_error(rc, None))
+ Err(BinderStatus::new_service_specific_error(
+ rc,
+ anyhow_error_to_cstring(&e).as_deref(),
+ ))
},
handle_ok,
)
diff --git a/keystore2/src/fuzzers/Android.bp b/keystore2/src/fuzzers/Android.bp
index 384ab77..3adb922 100644
--- a/keystore2/src/fuzzers/Android.bp
+++ b/keystore2/src/fuzzers/Android.bp
@@ -17,13 +17,23 @@
}
rust_fuzz {
- name: "legacy_blob_fuzzer",
- srcs: ["legacy_blob_fuzzer.rs"],
+ name: "keystore2_unsafe_fuzzer",
+ srcs: ["keystore2_unsafe_fuzzer.rs"],
rustlibs: [
"libkeystore2",
+ "libkeystore2_crypto_rust",
+ "libkeystore2_vintf_rust",
+ "libkeystore2_aaid-rust",
+ "libkeystore2_apc_compat-rust",
+ "libkeystore2_selinux",
+ "libarbitrary",
],
fuzz_config: {
fuzz_on_haiku_device: true,
fuzz_on_haiku_host: false,
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
},
}
diff --git a/keystore2/src/fuzzers/README.md b/keystore2/src/fuzzers/README.md
new file mode 100644
index 0000000..a4ed095
--- /dev/null
+++ b/keystore2/src/fuzzers/README.md
@@ -0,0 +1,18 @@
+# Fuzzers for libkeystore2
+## Table of contents
++ [keystore2_unsafe_fuzzer](#Keystore2Unsafe)
+
+# <a name="Keystore2Unsafe"></a> Fuzzer for Keystore2Unsafe
+All the parameters of Keystore2Unsafe are populated randomly from libfuzzer. You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+$ m -j$(nproc) keystore2_unsafe_fuzzer
+```
+
+2. Run on device
+```
+$ adb sync data
+$ adb shell /data/fuzz/${TARGET_ARCH}/keystore2_unsafe_fuzzer/keystore2_unsafe_fuzzer
+```
diff --git a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
new file mode 100644
index 0000000..4c2419a
--- /dev/null
+++ b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
@@ -0,0 +1,248 @@
+// 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.
+
+//! Fuzzes unsafe APIs of libkeystore2 module
+
+#![feature(slice_internals)]
+#![no_main]
+#[macro_use]
+extern crate libfuzzer_sys;
+
+use core::slice::memchr;
+use keystore2::{legacy_blob::LegacyBlobLoader, utils::ui_opts_2_compat};
+use keystore2_aaid::get_aaid;
+use keystore2_apc_compat::ApcHal;
+use keystore2_crypto::{
+ aes_gcm_decrypt, aes_gcm_encrypt, ec_key_generate_key, ec_key_get0_public_key,
+ ec_key_marshal_private_key, ec_key_parse_private_key, ec_point_oct_to_point,
+ ec_point_point_to_oct, ecdh_compute_key, generate_random_data, hkdf_expand, hkdf_extract,
+ hmac_sha256, parse_subject_from_certificate, Password, ZVec,
+};
+use keystore2_selinux::{check_access, getpidcon, setcon, Backend, Context, KeystoreKeyBackend};
+use keystore2_vintf::{get_aidl_instances, get_hidl_instances};
+use libfuzzer_sys::arbitrary::Arbitrary;
+use std::{ffi::CString, sync::Arc};
+
+// Avoid allocating too much memory and crashing the fuzzer.
+const MAX_SIZE_MODIFIER: usize = 1024;
+
+/// CString does not contain any internal 0 bytes
+fn get_valid_cstring_data(data: &[u8]) -> &[u8] {
+ match memchr::memchr(0, data) {
+ Some(idx) => &data[0..idx],
+ None => data,
+ }
+}
+
+#[derive(Arbitrary, Debug)]
+enum FuzzCommand<'a> {
+ DecodeAlias {
+ string: String,
+ },
+ TryFrom {
+ vector_data: Vec<u8>,
+ },
+ GenerateRandomData {
+ size: usize,
+ },
+ HmacSha256 {
+ key_hmac: &'a [u8],
+ msg: &'a [u8],
+ },
+ AesGcmDecrypt {
+ data: &'a [u8],
+ iv: &'a [u8],
+ tag: &'a [u8],
+ key_aes_decrypt: &'a [u8],
+ },
+ AesGcmEecrypt {
+ plaintext: &'a [u8],
+ key_aes_encrypt: &'a [u8],
+ },
+ Password {
+ pw: &'a [u8],
+ salt: &'a [u8],
+ key_length: usize,
+ },
+ HkdfExtract {
+ hkdf_secret: &'a [u8],
+ hkdf_salt: &'a [u8],
+ },
+ HkdfExpand {
+ out_len: usize,
+ hkdf_prk: &'a [u8],
+ hkdf_info: &'a [u8],
+ },
+ PublicPrivateKey {
+ ec_priv_buf: &'a [u8],
+ ec_oct_buf: &'a [u8],
+ },
+ ParseSubjectFromCertificate {
+ parse_buf: &'a [u8],
+ },
+ GetHidlInstances {
+ hidl_package: &'a str,
+ major_version: usize,
+ minor_version: usize,
+ hidl_interface_name: &'a str,
+ },
+ GetAidlInstances {
+ aidl_package: &'a str,
+ version: usize,
+ aidl_interface_name: &'a str,
+ },
+ GetAaid {
+ aaid_uid: u32,
+ },
+ Hal {
+ opt: i32,
+ prompt_text: &'a str,
+ locale: &'a str,
+ extra_data: &'a [u8],
+ },
+ Context {
+ context: &'a str,
+ },
+ Backend {
+ namespace: &'a str,
+ },
+ GetPidCon {
+ pid: i32,
+ },
+ CheckAccess {
+ source: &'a [u8],
+ target: &'a [u8],
+ tclass: &'a str,
+ perm: &'a str,
+ },
+ SetCon {
+ set_target: &'a [u8],
+ },
+}
+
+fuzz_target!(|commands: Vec<FuzzCommand>| {
+ for command in commands {
+ match command {
+ FuzzCommand::DecodeAlias { string } => {
+ let _res = LegacyBlobLoader::decode_alias(&string);
+ }
+ FuzzCommand::TryFrom { vector_data } => {
+ let _res = ZVec::try_from(vector_data);
+ }
+ FuzzCommand::GenerateRandomData { size } => {
+ let _res = generate_random_data(size % MAX_SIZE_MODIFIER);
+ }
+ FuzzCommand::HmacSha256 { key_hmac, msg } => {
+ let _res = hmac_sha256(key_hmac, msg);
+ }
+ FuzzCommand::AesGcmDecrypt { data, iv, tag, key_aes_decrypt } => {
+ let _res = aes_gcm_decrypt(data, iv, tag, key_aes_decrypt);
+ }
+ FuzzCommand::AesGcmEecrypt { plaintext, key_aes_encrypt } => {
+ let _res = aes_gcm_encrypt(plaintext, key_aes_encrypt);
+ }
+ FuzzCommand::Password { pw, salt, key_length } => {
+ let _res = Password::from(pw).derive_key(salt, key_length % MAX_SIZE_MODIFIER);
+ }
+ FuzzCommand::HkdfExtract { hkdf_secret, hkdf_salt } => {
+ let _res = hkdf_extract(hkdf_secret, hkdf_salt);
+ }
+ FuzzCommand::HkdfExpand { out_len, hkdf_prk, hkdf_info } => {
+ let _res = hkdf_expand(out_len % MAX_SIZE_MODIFIER, hkdf_prk, hkdf_info);
+ }
+ FuzzCommand::PublicPrivateKey { ec_priv_buf, ec_oct_buf } => {
+ let check_private_key = {
+ let mut check_private_key = ec_key_parse_private_key(ec_priv_buf);
+ if check_private_key.is_err() {
+ check_private_key = ec_key_generate_key();
+ };
+ check_private_key
+ };
+ let check_ecpoint = ec_point_oct_to_point(ec_oct_buf);
+ if check_private_key.is_ok() {
+ let private_key = check_private_key.unwrap();
+ ec_key_get0_public_key(&private_key);
+ let _res = ec_key_marshal_private_key(&private_key);
+
+ if check_ecpoint.is_ok() {
+ let public_key = check_ecpoint.unwrap();
+ let _res = ec_point_point_to_oct(public_key.get_point());
+ let _res = ecdh_compute_key(public_key.get_point(), &private_key);
+ }
+ }
+ }
+ FuzzCommand::ParseSubjectFromCertificate { parse_buf } => {
+ let _res = parse_subject_from_certificate(parse_buf);
+ }
+ FuzzCommand::GetHidlInstances {
+ hidl_package,
+ major_version,
+ minor_version,
+ hidl_interface_name,
+ } => {
+ get_hidl_instances(hidl_package, major_version, minor_version, hidl_interface_name);
+ }
+ FuzzCommand::GetAidlInstances { aidl_package, version, aidl_interface_name } => {
+ get_aidl_instances(aidl_package, version, aidl_interface_name);
+ }
+ FuzzCommand::GetAaid { aaid_uid } => {
+ let _res = get_aaid(aaid_uid);
+ }
+ FuzzCommand::Hal { opt, prompt_text, locale, extra_data } => {
+ let hal = ApcHal::try_get_service();
+ if hal.is_some() {
+ let hal = Arc::new(hal.unwrap());
+ let apc_compat_options = ui_opts_2_compat(opt);
+ let prompt_text =
+ std::str::from_utf8(get_valid_cstring_data(prompt_text.as_bytes()))
+ .unwrap();
+ let locale =
+ std::str::from_utf8(get_valid_cstring_data(locale.as_bytes())).unwrap();
+ let _res = hal.prompt_user_confirmation(
+ prompt_text,
+ extra_data,
+ locale,
+ apc_compat_options,
+ move |_, _, _| {},
+ );
+ }
+ }
+ FuzzCommand::Context { context } => {
+ let _res = Context::new(context);
+ }
+ FuzzCommand::Backend { namespace } => {
+ let backend = KeystoreKeyBackend::new();
+ if let Ok(backend) = backend {
+ let _res = backend.lookup(namespace);
+ }
+ }
+ FuzzCommand::GetPidCon { pid } => {
+ let _res = getpidcon(pid);
+ }
+ FuzzCommand::CheckAccess { source, target, tclass, perm } => {
+ let source = get_valid_cstring_data(source);
+ let target = get_valid_cstring_data(target);
+ let _res = check_access(
+ &CString::new(source).unwrap(),
+ &CString::new(target).unwrap(),
+ tclass,
+ perm,
+ );
+ }
+ FuzzCommand::SetCon { set_target } => {
+ let _res = setcon(&CString::new(get_valid_cstring_data(set_target)).unwrap());
+ }
+ }
+ }
+});
diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs
index 25f08c8..a033356 100644
--- a/keystore2/src/gc.rs
+++ b/keystore2/src/gc.rs
@@ -18,6 +18,7 @@
//! optionally dispose of sensitive key material appropriately, and then delete
//! the key entry from the database.
+use crate::ks_err;
use crate::{
async_task,
database::{BlobMetaData, KeystoreDB, Uuid},
@@ -27,7 +28,7 @@
use async_task::AsyncTask;
use std::sync::{
atomic::{AtomicU8, Ordering},
- Arc,
+ Arc, RwLock,
};
pub struct Gc {
@@ -47,7 +48,7 @@
F: FnOnce() -> (
Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>,
KeystoreDB,
- Arc<SuperKeyManager>,
+ Arc<RwLock<SuperKeyManager>>,
) + Send
+ 'static,
{
@@ -87,7 +88,7 @@
invalidate_key: Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>,
db: KeystoreDB,
async_task: std::sync::Weak<AsyncTask>,
- super_key: Arc<SuperKeyManager>,
+ super_key: Arc<RwLock<SuperKeyManager>>,
notified: Arc<AtomicU8>,
}
@@ -103,7 +104,7 @@
let blobs = self
.db
.handle_next_superseded_blobs(&self.deleted_blob_ids, 20)
- .context("In process_one_key: Trying to handle superseded blob.")?;
+ .context(ks_err!("Trying to handle superseded blob."))?;
self.deleted_blob_ids = vec![];
self.superseded_blobs = blobs;
}
@@ -121,10 +122,11 @@
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)
- .context("In process_one_key: Trying to invalidate key.")?;
+ .context(ks_err!("Trying to unwrap to-be-deleted blob.",))?;
+ (self.invalidate_key)(uuid, &blob).context(ks_err!("Trying to invalidate key."))?;
}
}
Ok(())
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index eae5ad0..425812f 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -16,9 +16,10 @@
//! database connections and connections to services that Keystore needs
//! to talk to.
+use crate::ks_err;
use crate::gc::Gc;
use crate::legacy_blob::LegacyBlobLoader;
-use crate::legacy_migrator::LegacyMigrator;
+use crate::legacy_importer::LegacyImporter;
use crate::super_key::SuperKeyManager;
use crate::utils::watchdog as wd;
use crate::{async_task::AsyncTask, database::MonotonicRawTime};
@@ -27,11 +28,13 @@
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,
+ IKeyMintDevice::IKeyMintDevice, KeyMintHardwareInfo::KeyMintHardwareInfo,
+ SecurityLevel::SecurityLevel,
};
+use android_hardware_security_rkp::aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent::IRemotelyProvisionedComponent;
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
ISecureClock::ISecureClock,
};
@@ -156,7 +159,7 @@
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<SuperKeyManager> = Default::default();
+ pub static ref SUPER_KEY: Arc<RwLock<SuperKeyManager>> = Default::default();
/// Map of KeyMint devices.
static ref KEY_MINT_DEVICES: Mutex<DevicesMap<dyn IKeyMintDevice>> = Default::default();
/// Timestamp service.
@@ -175,8 +178,8 @@
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_MIGRATOR: Arc<LegacyMigrator> =
- Arc::new(LegacyMigrator::new(Arc::new(Default::default())));
+ pub static ref LEGACY_IMPORTER: Arc<LegacyImporter> =
+ Arc::new(LegacyImporter::new(Arc::new(Default::default())));
/// Background thread which handles logging via statsd and logd
pub static ref LOGS_HANDLER: Arc<AsyncTask> = Default::default();
@@ -185,8 +188,8 @@
Box::new(|uuid, blob| {
let km_dev = get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?;
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.")
+ map_km_error(km_dev.deleteKey(blob))
+ .context(ks_err!("Trying to invalidate key blob."))
}),
KeystoreDB::new(&DB_PATH.read().expect("Could not get the database directory."), None)
.expect("Failed to open database."),
@@ -197,14 +200,15 @@
static KEYMINT_SERVICE_NAME: &str = "android.hardware.security.keymint.IKeyMintDevice";
-/// 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(
+/// 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,
-) -> Result<(Strong<dyn IKeyMintDevice>, KeyMintHardwareInfo)> {
+ version: i32,
+) -> Result<Option<(i32, String)>> {
let keymint_instances =
- get_aidl_instances("android.hardware.security.keymint", 1, "IKeyMintDevice");
+ get_aidl_instances("android.hardware.security.keymint", version as usize, "IKeyMintDevice");
let service_name = match *security_level {
SecurityLevel::TRUSTED_ENVIRONMENT => {
@@ -222,24 +226,52 @@
}
}
_ => {
- return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
- .context("In connect_keymint.")
+ return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(ks_err!(
+ "Trying to find keymint V{} for security level: {:?}",
+ version,
+ security_level
+ ));
}
};
- let (keymint, hal_version) = if let Some(service_name) = service_name {
- (
+ 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(ks_err!())?;
+
+ let (keymint, hal_version) = if let Some((version, service_name)) = service_name {
+ let km: Strong<dyn IKeyMintDevice> =
map_binder_status_code(binder::get_interface(&service_name))
- .context("In connect_keymint: Trying to connect to genuine KeyMint service.")?,
- Some(100i32), // The HAL version code for KeyMint V1 is 100.
- )
+ .context(ks_err!("Trying to connect to genuine KeyMint service."))?;
+ // Map the HAL version code for KeyMint to be <AIDL version> * 100, so
+ // - V1 is 100
+ // - V2 is 200
+ // etc.
+ (km, Some(version * 100))
} else {
// 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 connect_keymint: Trying to connect to compat service.")?;
+ .context(ks_err!("Trying to connect to compat service."))?;
(
map_binder_status(keystore_compat_service.getKeyMintDevice(*security_level))
.map_err(|e| match e {
@@ -248,23 +280,68 @@
}
e => e,
})
- .context("In connect_keymint: Trying to get Legacy wrapper.")?,
+ .context(ks_err!("Trying to get Legacy wrapper."))?,
None,
)
};
+ // 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(ks_err!("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(ks_err!("Trying to create km_compat V1 compatibility wrapper ."))?
+ }
+ _ => {
+ return Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)).context(ks_err!(
+ "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.")?;
+ let mut hw_info =
+ map_km_error(keymint.getHardwareInfo()).context(ks_err!("Failed to get hardware info."))?;
drop(wp);
// 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 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.
+ //
+ // 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).
if let Some(hal_version) = hal_version {
hw_info.versionNumber = hal_version;
}
@@ -282,7 +359,7 @@
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.")?;
+ let (dev, hw_info) = connect_keymint(security_level).context(ks_err!())?;
devices_map.insert(*security_level, dev, hw_info);
// Unwrap must succeed because we just inserted it.
Ok(devices_map.dev_by_sec_level(security_level).unwrap())
@@ -300,7 +377,7 @@
if let Some((dev, hw_info, _)) = devices_map.dev_by_uuid(uuid) {
Ok((dev, hw_info))
} else {
- Err(Error::sys()).context("In get_keymint_dev_by_uuid: No KeyMint instance found.")
+ Err(Error::sys()).context(ks_err!("No KeyMint instance found."))
}
}
@@ -325,14 +402,14 @@
let secureclock = if secure_clock_available {
map_binder_status_code(binder::get_interface(&default_time_stamp_service_name))
- .context("In connect_secureclock: Trying to connect to genuine secure clock service.")
+ .context(ks_err!("Trying to connect to genuine secure clock service."))
} else {
// 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 connect_secureclock: Trying to connect to compat service.")?;
+ .context(ks_err!("Trying to connect to compat service."))?;
// Legacy secure clock services were only implemented by TEE.
map_binder_status(keystore_compat_service.getSecureClock())
@@ -342,7 +419,7 @@
}
e => e,
})
- .context("In connect_secureclock: Trying to get Legacy wrapper.")
+ .context(ks_err!("Trying to get Legacy wrapper."))
}?;
Ok(secureclock)
@@ -355,7 +432,7 @@
if let Some(dev) = &*ts_device {
Ok(dev.clone())
} else {
- let dev = connect_secureclock().context("In get_timestamp_service.")?;
+ let dev = connect_secureclock().context(ks_err!())?;
*ts_device = Some(dev.clone());
Ok(dev)
}
@@ -388,15 +465,11 @@
_ => None,
}
.ok_or(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
- .context("In connect_remotely_provisioned_component.")?;
+ .context(ks_err!())?;
let rem_prov_hal: Strong<dyn IRemotelyProvisionedComponent> =
map_binder_status_code(binder::get_interface(&service_name))
- .context(concat!(
- "In connect_remotely_provisioned_component: Trying to connect to",
- " RemotelyProvisionedComponent service."
- ))
- .map_err(|e| e)?;
+ .context(ks_err!("Trying to connect to RemotelyProvisionedComponent service."))?;
Ok(rem_prov_hal)
}
@@ -409,8 +482,7 @@
if let Some(dev) = devices_map.dev_by_sec_level(security_level) {
Ok(dev)
} else {
- let dev = connect_remotely_provisioned_component(security_level)
- .context("In get_remotely_provisioned_component.")?;
+ let dev = connect_remotely_provisioned_component(security_level).context(ks_err!())?;
devices_map.insert(*security_level, dev);
// Unwrap must succeed because we just inserted it.
Ok(devices_map.dev_by_sec_level(security_level).unwrap())
diff --git a/keystore2/src/id_rotation.rs b/keystore2/src/id_rotation.rs
index e3992d8..460caa7 100644
--- a/keystore2/src/id_rotation.rs
+++ b/keystore2/src/id_rotation.rs
@@ -20,6 +20,8 @@
//! It is assumed that the timestamp file does not exist after a factory reset. So the creation
//! time of the timestamp file provides a lower bound for the time since factory reset.
+use crate::ks_err;
+
use anyhow::{Context, Result};
use std::fs;
use std::io::ErrorKind;
@@ -66,7 +68,7 @@
_ => Err(e).context("Failed to open timestamp file."),
},
}
- .context("In had_factory_reset_since_id_rotation:")
+ .context(ks_err!())
}
}
diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs
index 771d609..b3dcf45 100644
--- a/keystore2/src/key_parameter.rs
+++ b/keystore2/src/key_parameter.rs
@@ -107,6 +107,9 @@
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, ...}.
@@ -121,7 +124,7 @@
/// there is no wrapped type):
/// `KeyParameterValue::$vname(<$vtype>::from_primitive(row.get(0)))`
trait AssociatePrimitive {
- type Primitive;
+ type Primitive: Into<Primitive> + TryFrom<Primitive>;
fn from_primitive(v: Self::Primitive) -> Self;
fn to_primitive(&self) -> Self::Primitive;
@@ -177,6 +180,7 @@
/// 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),
@@ -213,37 +217,57 @@
UnknownTag,
}
-impl TryInto<i64> for Primitive {
+impl TryFrom<Primitive> for i64 {
type Error = PrimitiveError;
- fn try_into(self) -> Result<i64, Self::Error> {
- match self {
- Self::I64(v) => Ok(v),
+ fn try_from(p: Primitive) -> Result<i64, Self::Error> {
+ match p {
+ Primitive::I64(v) => Ok(v),
_ => Err(Self::Error::TypeMismatch),
}
}
}
-impl TryInto<i32> for Primitive {
+impl TryFrom<Primitive> for i32 {
type Error = PrimitiveError;
- fn try_into(self) -> Result<i32, Self::Error> {
- match self {
- Self::I32(v) => Ok(v),
+ fn try_from(p: Primitive) -> Result<i32, Self::Error> {
+ match p {
+ Primitive::I32(v) => Ok(v),
_ => Err(Self::Error::TypeMismatch),
}
}
}
-impl TryInto<Vec<u8>> for Primitive {
+impl TryFrom<Primitive> for Vec<u8> {
type Error = PrimitiveError;
- fn try_into(self) -> Result<Vec<u8>, Self::Error> {
- match self {
- Self::Vec(v) => Ok(v),
+ fn try_from(p: Primitive) -> Result<Vec<u8>, Self::Error> {
+ match p {
+ Primitive::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:
@@ -763,6 +787,14 @@
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!(
@@ -777,27 +809,37 @@
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)]
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)]
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
@@ -807,6 +849,8 @@
#[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
@@ -856,6 +900,8 @@
#[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
@@ -886,6 +932,8 @@
#[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
@@ -918,9 +966,12 @@
/// Provides the device's serial number, to attestKey()
#[key_param(tag = ATTESTATION_ID_SERIAL, field = Blob)]
AttestationIdSerial(Vec<u8>),
- /// Provides the IMEIs for all radios on the device, to attestKey()
+ /// Provides the primary IMEI for the device, to attestKey()
#[key_param(tag = ATTESTATION_ID_IMEI, field = Blob)]
AttestationIdIMEI(Vec<u8>),
+ /// Provides a second IMEI for the device, to attestKey()
+ #[key_param(tag = ATTESTATION_ID_SECOND_IMEI, field = Blob)]
+ AttestationIdSecondIMEI(Vec<u8>),
/// Provides the MEIDs for all radios on the device, to attestKey()
#[key_param(tag = ATTESTATION_ID_MEID, field = Blob)]
AttestationIdMEID(Vec<u8>),
@@ -981,9 +1032,11 @@
}
/// KeyParameter wraps the KeyParameterValue and the security level at which it is enforced.
-#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct KeyParameter {
value: KeyParameterValue,
+ #[serde(deserialize_with = "deserialize_primitive")]
+ #[serde(serialize_with = "serialize_primitive")]
security_level: SecurityLevel,
}
@@ -1106,6 +1159,18 @@
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 abab4b6..8c5bf0f 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -19,7 +19,9 @@
use keystore2::maintenance::Maintenance;
use keystore2::metrics::Metrics;
use keystore2::metrics_store;
-use keystore2::remote_provisioning::RemoteProvisioningService;
+use keystore2::remote_provisioning::{
+ RemoteProvisioningService, RemotelyProvisionedKeyPoolService,
+};
use keystore2::service::KeystoreService;
use keystore2::{apc::ApcManager, shared_secret_negotiation};
use keystore2::{authorization::AuthorizationManager, id_rotation::IdRotationState};
@@ -33,6 +35,8 @@
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";
@@ -40,7 +44,19 @@
fn main() {
// Initialize android logging.
android_logger::init_once(
- android_logger::Config::default().with_tag("keystore2").with_min_level(log::Level::Debug),
+ android_logger::Config::default()
+ .with_tag("keystore2")
+ .with_min_level(log::Level::Debug)
+ .with_log_id(android_logger::LogId::System)
+ .format(|buf, record| {
+ writeln!(
+ buf,
+ "{}:{} - {}",
+ record.file().unwrap_or("unknown"),
+ record.line().unwrap_or(0),
+ record.args()
+ )
+ }),
);
// Redirect panic messages to logcat.
panic::set_hook(Box::new(|panic_info| {
@@ -132,17 +148,39 @@
// Devices with KS2 and KM 1.0 may not have any IRemotelyProvisionedComponent HALs at all. Do
// not panic if new_native_binder returns failure because it could not find the TEE HAL.
- if let Ok(remote_provisioning_service) = RemoteProvisioningService::new_native_binder() {
- binder::add_service(
- REMOTE_PROVISIONING_SERVICE_NAME,
- remote_provisioning_service.as_binder(),
- )
- .unwrap_or_else(|e| {
- panic!(
- "Failed to register service {} because of {:?}.",
- REMOTE_PROVISIONING_SERVICE_NAME, e
- );
- });
+ match RemoteProvisioningService::new_native_binder() {
+ Ok(remote_provisioning_service) => {
+ binder::add_service(
+ REMOTE_PROVISIONING_SERVICE_NAME,
+ remote_provisioning_service.as_binder(),
+ )
+ .unwrap_or_else(|e| {
+ panic!(
+ "Failed to register service {} because of {:?}.",
+ REMOTE_PROVISIONING_SERVICE_NAME, e
+ );
+ });
+ }
+ Err(e) => log::info!("Not publishing {}: {:?}", REMOTE_PROVISIONING_SERVICE_NAME, e),
+ }
+
+ // Even if the IRemotelyProvisionedComponent HAL is implemented, it doesn't mean that the keys
+ // may be fetched via the key pool. The HAL must be a new version that exports a unique id. If
+ // none of the HALs support this, then the key pool service is not published.
+ match RemotelyProvisionedKeyPoolService::new_native_binder() {
+ Ok(key_pool_service) => {
+ binder::add_service(
+ REMOTELY_PROVISIONED_KEY_POOL_SERVICE_NAME,
+ key_pool_service.as_binder(),
+ )
+ .unwrap_or_else(|e| {
+ panic!(
+ "Failed to register service {} because of {:?}.",
+ REMOTELY_PROVISIONED_KEY_POOL_SERVICE_NAME, e
+ );
+ });
+ }
+ Err(e) => log::info!("Not publishing IRemotelyProvisionedKeyPool service: {:?}", e),
}
binder::add_service(LEGACY_KEYSTORE_SERVICE_NAME, legacykeystore.as_binder()).unwrap_or_else(
diff --git a/keystore2/src/km_compat.rs b/keystore2/src/km_compat.rs
new file mode 100644
index 0000000..035edd9
--- /dev/null
+++ b/keystore2/src/km_compat.rs
@@ -0,0 +1,588 @@
+// 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::ks_err;
+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(ks_err!("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(ks_err!("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(ks_err!("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 32406ae..806f3dc 100644
--- a/keystore2/src/km_compat/Android.bp
+++ b/keystore2/src/km_compat/Android.bp
@@ -25,9 +25,10 @@
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: [
@@ -41,8 +42,10 @@
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: [
@@ -53,15 +56,17 @@
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.keymint-V1-ndk",
"android.hardware.security.secureclock-V1-ndk",
"android.hardware.security.sharedsecret-V1-ndk",
"android.security.compat-ndk",
- "android.system.keystore2-V1-ndk",
"libbase",
"libbinder_ndk",
"libcrypto",
@@ -77,8 +82,10 @@
cc_library {
name: "libkm_compat_service",
srcs: ["km_compat_service.cpp"],
+ defaults: [
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ ],
shared_libs: [
- "android.hardware.security.keymint-V1-ndk",
"android.hardware.security.secureclock-V1-ndk",
"android.hardware.security.sharedsecret-V1-ndk",
"android.security.compat-ndk",
@@ -103,15 +110,17 @@
"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.keymint-V1-ndk",
"android.hardware.security.secureclock-V1-ndk",
"android.hardware.security.sharedsecret-V1-ndk",
"android.security.compat-ndk",
- "android.system.keystore2-V1-ndk",
"libbase",
"libbinder_ndk",
"libcrypto",
diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp
index 40ca554..e27cd1c 100644
--- a/keystore2/src/km_compat/km_compat.cpp
+++ b/keystore2/src/km_compat/km_compat.cpp
@@ -80,7 +80,6 @@
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:
@@ -127,7 +126,7 @@
case Tag::TRUSTED_CONFIRMATION_REQUIRED:
case Tag::UNLOCKED_DEVICE_REQUIRED:
case Tag::CREATION_DATETIME:
- case Tag::UNIQUE_ID:
+ case Tag::INCLUDE_UNIQUE_ID:
case Tag::IDENTITY_CREDENTIAL_KEY:
case Tag::STORAGE_KEY:
case Tag::MAC_LENGTH:
@@ -384,29 +383,39 @@
return ssps;
}
-void OperationSlots::setNumFreeSlots(uint8_t numFreeSlots) {
+void OperationSlotManager::setNumFreeSlots(uint8_t numFreeSlots) {
std::lock_guard<std::mutex> lock(mNumFreeSlotsMutex);
mNumFreeSlots = numFreeSlots;
}
-bool OperationSlots::claimSlot() {
- std::lock_guard<std::mutex> lock(mNumFreeSlotsMutex);
- if (mNumFreeSlots > 0) {
- mNumFreeSlots--;
- return true;
+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);
}
- return false;
+ return std::nullopt;
}
-void OperationSlots::freeSlot() {
+OperationSlot
+OperationSlotManager::claimReservedSlot(std::shared_ptr<OperationSlotManager> operationSlots) {
+ std::unique_lock<std::mutex> reservedGuard(operationSlots->mReservedSlotMutex);
+ return OperationSlot(std::move(operationSlots), std::move(reservedGuard));
+}
+
+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() {
std::lock_guard<std::mutex> lock(mNumFreeSlotsMutex);
mNumFreeSlots++;
}
-void OperationSlot::freeSlot() {
- if (mIsActive) {
+OperationSlot::~OperationSlot() {
+ if (!mReservedGuard && mOperationSlots) {
mOperationSlots->freeSlot();
- mIsActive = false;
}
}
@@ -491,21 +500,42 @@
ScopedAStatus KeyMintDevice::importKey(const std::vector<KeyParameter>& inKeyParams,
KeyFormat in_inKeyFormat,
const std::vector<uint8_t>& in_inKeyData,
- const std::optional<AttestationKey>& /* in_attestationKey */,
+ const std::optional<AttestationKey>& in_attestationKey,
KeyCreationResult* out_creationResult) {
+ // Since KeyMaster doesn't support ECDH, route all ECDH key import requests to
+ // soft-KeyMint.
+ //
+ // For this to work we'll need to also route begin() and deleteKey() calls to
+ // soft-KM. In order to do that, we'll prefix all keyblobs with whether it was
+ // created by the real underlying KeyMaster HAL or whether it was created by
+ // soft-KeyMint.
+ //
+ // See keyBlobPrefix() for more discussion.
+ //
+ for (const auto& keyParam : inKeyParams) {
+ if (keyParam.tag == Tag::PURPOSE &&
+ keyParam.value.get<KeyParameterValue::Tag::keyPurpose>() == KeyPurpose::AGREE_KEY) {
+ auto ret = softKeyMintDevice_->importKey(inKeyParams, in_inKeyFormat, in_inKeyData,
+ in_attestationKey, out_creationResult);
+ if (ret.isOk()) {
+ out_creationResult->keyBlob = keyBlobPrefix(out_creationResult->keyBlob, true);
+ }
+ return ret;
+ }
+ }
+
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);
@@ -566,6 +596,17 @@
auto legacyUpgradeParams = convertKeyParametersToLegacy(in_inUpgradeParams);
V4_0_ErrorCode errorCode;
+ if (prefixedKeyBlobIsSoftKeyMint(in_inKeyBlobToUpgrade)) {
+ auto status = softKeyMintDevice_->upgradeKey(
+ prefixedKeyBlobRemovePrefix(in_inKeyBlobToUpgrade), in_inUpgradeParams, _aidl_return);
+ if (!status.isOk()) {
+ LOG(ERROR) << __func__ << " transaction failed. " << status.getDescription();
+ } else {
+ *_aidl_return = keyBlobPrefix(*_aidl_return, true);
+ }
+ return status;
+ }
+
auto result =
mDevice->upgradeKey(prefixedKeyBlobRemovePrefix(in_inKeyBlobToUpgrade), legacyUpgradeParams,
[&](V4_0_ErrorCode error, const hidl_vec<uint8_t>& upgradedKeyBlob) {
@@ -613,9 +654,15 @@
const std::vector<KeyParameter>& in_inParams,
const std::optional<HardwareAuthToken>& in_inAuthToken,
BeginResult* _aidl_return) {
- if (!mOperationSlots.claimSlot()) {
- return convertErrorCode(V4_0_ErrorCode::TOO_MANY_OPERATIONS);
- }
+ 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) {
const std::vector<uint8_t>& in_inKeyBlob = prefixedKeyBlobRemovePrefix(prefixedKeyBlob);
if (prefixedKeyBlobIsSoftKeyMint(prefixedKeyBlob)) {
@@ -623,28 +670,41 @@
_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);
- _aidl_return->challenge = operationHandle;
- _aidl_return->params = convertKeyParametersFromLegacy(outParams);
- _aidl_return->operation = ndk::SharedRefBase::make<KeyMintOperation>(
- mDevice, operationHandle, &mOperationSlots, error == V4_0_ErrorCode::OK);
- });
+ 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));
+ }
+ });
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);
}
@@ -704,8 +764,9 @@
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);
}
@@ -741,6 +802,19 @@
}
}
+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) {
@@ -757,12 +831,30 @@
LOG(ERROR) << __func__ << " transaction failed. " << result.description();
errorCode = KMV1::ErrorCode::UNKNOWN_ERROR;
}
- if (errorCode != KMV1::ErrorCode::OK) mOperationSlot.freeSlot();
+
+ // Operation slot is no longer occupied.
+ if (errorCode != KMV1::ErrorCode::OK) {
+ mOperationSlot = std::nullopt;
+ }
return convertErrorCode(errorCode);
}
-ScopedAStatus KeyMintOperation::update(const std::vector<uint8_t>& input,
+void KeyMintOperation::setUpdateBuffer(std::vector<uint8_t> data) {
+ mUpdateBuffer = std::move(data);
+}
+
+const std::vector<uint8_t>&
+KeyMintOperation::getExtendedUpdateBuffer(const std::vector<uint8_t>& suffix) {
+ if (mUpdateBuffer.empty()) {
+ return suffix;
+ } else {
+ mUpdateBuffer.insert(mUpdateBuffer.end(), suffix.begin(), suffix.end());
+ return mUpdateBuffer;
+ }
+}
+
+ScopedAStatus KeyMintOperation::update(const std::vector<uint8_t>& input_raw,
const std::optional<HardwareAuthToken>& optAuthToken,
const std::optional<TimeStampToken>& optTimeStampToken,
std::vector<uint8_t>* out_output) {
@@ -772,8 +864,10 @@
size_t inputPos = 0;
*out_output = {};
KMV1::ErrorCode errorCode = KMV1::ErrorCode::OK;
+ auto input = getExtendedUpdateBuffer(input_raw);
while (inputPos < input.size() && errorCode == KMV1::ErrorCode::OK) {
+ uint32_t consumed = 0;
auto result =
mDevice->update(mOperationHandle, {} /* inParams */,
{input.begin() + inputPos, input.end()}, authToken, verificationToken,
@@ -781,16 +875,28 @@
const hidl_vec<uint8_t>& output) {
errorCode = convert(error);
out_output->insert(out_output->end(), output.begin(), output.end());
- inputPos += inputConsumed;
+ consumed = inputConsumed;
});
if (!result.isOk()) {
LOG(ERROR) << __func__ << " transaction failed. " << result.description();
errorCode = KMV1::ErrorCode::UNKNOWN_ERROR;
}
+
+ if (errorCode == KMV1::ErrorCode::OK && consumed == 0) {
+ // Some very old KM implementations do not buffer sub blocks in certain block modes,
+ // instead, the simply return consumed == 0. So we buffer the input here in the
+ // hope that we complete the bock in a future call to update.
+ setUpdateBuffer({input.begin() + inputPos, input.end()});
+ return convertErrorCode(errorCode);
+ }
+ inputPos += consumed;
}
- if (errorCode != KMV1::ErrorCode::OK) mOperationSlot.freeSlot();
+ // Operation slot is no longer occupied.
+ if (errorCode != KMV1::ErrorCode::OK) {
+ mOperationSlot = std::nullopt;
+ }
return convertErrorCode(errorCode);
}
@@ -802,7 +908,8 @@
const std::optional<TimeStampToken>& in_timeStampToken,
const std::optional<std::vector<uint8_t>>& in_confirmationToken,
std::vector<uint8_t>* out_output) {
- auto input = in_input.value_or(std::vector<uint8_t>());
+ auto input_raw = in_input.value_or(std::vector<uint8_t>());
+ auto input = getExtendedUpdateBuffer(input_raw);
auto signature = in_signature.value_or(std::vector<uint8_t>());
V4_0_HardwareAuthToken authToken = convertAuthTokenToLegacy(in_authToken);
V4_0_VerificationToken verificationToken = convertTimestampTokenToLegacy(in_timeStampToken);
@@ -820,17 +927,19 @@
*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.freeSlot();
+ mOperationSlot = std::nullopt;
if (!result.isOk()) {
LOG(ERROR) << __func__ << " transaction failed. " << result.description();
return convertErrorCode(KMV1::ErrorCode::UNKNOWN_ERROR);
@@ -839,7 +948,7 @@
}
KeyMintOperation::~KeyMintOperation() {
- if (mOperationSlot.hasSlot()) {
+ if (mOperationSlot) {
auto error = abort();
if (!error.isOk()) {
LOG(WARNING) << "Error calling abort in ~KeyMintOperation: " << error.getMessage();
@@ -1092,8 +1201,8 @@
kps.push_back(KMV1::makeKeyParameter(KMV1::TAG_PADDING, origPadding));
}
BeginResult beginResult;
- auto error =
- begin(KeyPurpose::SIGN, prefixedKeyBlob, kps, HardwareAuthToken(), &beginResult);
+ auto error = beginInternal(KeyPurpose::SIGN, prefixedKeyBlob, kps, HardwareAuthToken(),
+ true /* useReservedSlot */, &beginResult);
if (!error.isOk()) {
errorCode = toErrorCode(error);
return std::vector<uint8_t>();
@@ -1329,20 +1438,21 @@
}
void KeyMintDevice::setNumFreeSlots(uint8_t numFreeSlots) {
- mOperationSlots.setNumFreeSlots(numFreeSlots);
+ mOperationSlots->setNumFreeSlots(numFreeSlots);
}
// Constructors and helpers.
KeyMintDevice::KeyMintDevice(sp<Keymaster> device, KeyMintSecurityLevel securityLevel)
- : mDevice(device), securityLevel_(securityLevel) {
+ : mDevice(device), mOperationSlots(std::make_shared<OperationSlotManager>()),
+ securityLevel_(securityLevel) {
if (securityLevel == KeyMintSecurityLevel::STRONGBOX) {
setNumFreeSlots(3);
} else {
setNumFreeSlots(15);
}
- softKeyMintDevice_.reset(CreateKeyMintDevice(KeyMintSecurityLevel::SOFTWARE));
+ softKeyMintDevice_ = CreateKeyMintDevice(KeyMintSecurityLevel::SOFTWARE);
}
sp<Keymaster> getDevice(KeyMintSecurityLevel securityLevel) {
@@ -1365,14 +1475,33 @@
}
}
+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::createKeyMintDevice(KeyMintSecurityLevel securityLevel) {
+KeyMintDevice::getWrappedKeymasterDevice(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 2d892da..6654c4a 100644
--- a/keystore2/src/km_compat/km_compat.h
+++ b/keystore2/src/km_compat/km_compat.h
@@ -50,41 +50,55 @@
using ::android::hardware::keymaster::V4_1::support::Keymaster;
using ::ndk::ScopedAStatus;
-class OperationSlots {
- private:
- uint8_t mNumFreeSlots;
- std::mutex mNumFreeSlotsMutex;
-
- public:
- void setNumFreeSlots(uint8_t numFreeSlots);
- bool claimSlot();
- void freeSlot();
-};
-
+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;
+
private:
- OperationSlots* mOperationSlots;
- bool mIsActive;
+ 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;
public:
- OperationSlot(OperationSlots* slots, bool isActive)
- : mOperationSlots(slots), mIsActive(isActive) {}
+ OperationSlot() : mOperationSlots(nullptr), mReservedGuard(std::nullopt) {}
+ OperationSlot(OperationSlot&&) = default;
+ OperationSlot& operator=(OperationSlot&&) = default;
+ ~OperationSlot();
+};
+class OperationSlotManager {
+ private:
+ uint8_t mNumFreeSlots;
+ std::mutex mNumFreeSlotsMutex;
+ std::mutex mReservedSlotMutex;
+
+ public:
+ void setNumFreeSlots(uint8_t numFreeSlots);
+ static std::optional<OperationSlot>
+ claimSlot(std::shared_ptr<OperationSlotManager> operationSlots);
+ static OperationSlot claimReservedSlot(std::shared_ptr<OperationSlotManager> operationSlots);
void freeSlot();
- bool hasSlot() { return mIsActive; }
};
class KeyMintDevice : public aidl::android::hardware::security::keymint::BnKeyMintDevice {
private:
::android::sp<Keymaster> mDevice;
- OperationSlots mOperationSlots;
+ std::shared_ptr<OperationSlotManager> mOperationSlots;
public:
explicit KeyMintDevice(::android::sp<Keymaster>, KeyMintSecurityLevel);
- static std::shared_ptr<KeyMintDevice> createKeyMintDevice(KeyMintSecurityLevel securityLevel);
+ static std::shared_ptr<IKeyMintDevice> createKeyMintDevice(KeyMintSecurityLevel securityLevel);
+ static std::shared_ptr<KeyMintDevice>
+ getWrappedKeymasterDevice(KeyMintSecurityLevel securityLevel);
ScopedAStatus getHardwareInfo(KeyMintHardwareInfo* _aidl_return) override;
ScopedAStatus addRngEntropy(const std::vector<uint8_t>& in_data) override;
@@ -107,10 +121,15 @@
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;
@@ -123,6 +142,11 @@
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>
@@ -140,15 +164,9 @@
};
class KeyMintOperation : public aidl::android::hardware::security::keymint::BnKeyMintOperation {
- private:
- ::android::sp<Keymaster> mDevice;
- uint64_t mOperationHandle;
- OperationSlot mOperationSlot;
-
public:
- KeyMintOperation(::android::sp<Keymaster> device, uint64_t operationHandle,
- OperationSlots* slots, bool isActive)
- : mDevice(device), mOperationHandle(operationHandle), mOperationSlot(slots, isActive) {}
+ KeyMintOperation(::android::sp<Keymaster> device, uint64_t operationHandle, OperationSlot slot)
+ : mDevice(device), mOperationHandle(operationHandle), mOperationSlot(std::move(slot)) {}
~KeyMintOperation();
ScopedAStatus updateAad(const std::vector<uint8_t>& input,
@@ -168,6 +186,25 @@
std::vector<uint8_t>* output) override;
ScopedAStatus abort();
+
+ private:
+ /**
+ * Sets mUpdateBuffer to the given value.
+ * @param data
+ */
+ void setUpdateBuffer(std::vector<uint8_t> data);
+ /**
+ * If mUpdateBuffer is not empty, suffix is appended to mUpdateBuffer, and a reference to
+ * mUpdateBuffer is returned. Otherwise a reference to suffix is returned.
+ * @param suffix
+ * @return
+ */
+ const std::vector<uint8_t>& getExtendedUpdateBuffer(const std::vector<uint8_t>& suffix);
+
+ std::vector<uint8_t> mUpdateBuffer;
+ ::android::sp<Keymaster> mDevice;
+ uint64_t mOperationHandle;
+ std::optional<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 de09477..5db7e3d 100644
--- a/keystore2/src/km_compat/km_compat_type_conversion.h
+++ b/keystore2/src/km_compat/km_compat_type_conversion.h
@@ -16,6 +16,9 @@
#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>
@@ -278,7 +281,7 @@
}
}
-static V4_0::EcCurve convert(KMV1::EcCurve e) {
+static std::optional<V4_0::EcCurve> convert(KMV1::EcCurve e) {
switch (e) {
case KMV1::EcCurve::P_224:
return V4_0::EcCurve::P_224;
@@ -288,7 +291,11 @@
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) {
@@ -490,7 +497,9 @@
break;
case KMV1::Tag::EC_CURVE:
if (auto v = KMV1::authorizationValue(KMV1::TAG_EC_CURVE, kp)) {
- return V4_0::makeKeyParameter(V4_0::TAG_EC_CURVE, convert(v->get()));
+ if (auto curve = convert(v->get())) {
+ return V4_0::makeKeyParameter(V4_0::TAG_EC_CURVE, curve.value());
+ }
}
break;
case KMV1::Tag::RSA_PUBLIC_EXPONENT:
@@ -741,6 +750,7 @@
case KMV1::Tag::CERTIFICATE_SUBJECT:
case KMV1::Tag::CERTIFICATE_NOT_BEFORE:
case KMV1::Tag::CERTIFICATE_NOT_AFTER:
+ case KMV1::Tag::ATTESTATION_ID_SECOND_IMEI:
// These tags do not exist in KM < KeyMint 1.0.
break;
case KMV1::Tag::MAX_BOOT_LEVEL:
diff --git a/keystore2/src/km_compat/lib.rs b/keystore2/src/km_compat/lib.rs
index 8d7310b..2632ec4 100644
--- a/keystore2/src/km_compat/lib.rs
+++ b/keystore2/src/km_compat/lib.rs
@@ -286,7 +286,7 @@
let operation = begin_result.operation.unwrap();
let update_aad_result = operation.updateAad(
- &b"foobar".to_vec(),
+ b"foobar".as_ref(),
None, /* authToken */
None, /* timestampToken */
);
@@ -310,7 +310,7 @@
let operation = begin_result.operation.unwrap();
let update_aad_result = operation.updateAad(
- &b"foobar".to_vec(),
+ b"foobar".as_ref(),
None, /* authToken */
None, /* timestampToken */
);
diff --git a/keystore2/src/km_compat/slot_test.cpp b/keystore2/src/km_compat/slot_test.cpp
index 43f3bc6..d734970 100644
--- a/keystore2/src/km_compat/slot_test.cpp
+++ b/keystore2/src/km_compat/slot_test.cpp
@@ -26,6 +26,7 @@
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;
@@ -53,6 +54,25 @@
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);
@@ -69,17 +89,57 @@
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::createKeyMintDevice(SecurityLevel::TRUSTED_ENVIRONMENT);
+ KeyMintDevice::getWrappedKeymasterDevice(SecurityLevel::TRUSTED_ENVIRONMENT);
+ ASSERT_NE(device.get(), nullptr);
+
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++) {
@@ -94,6 +154,14 @@
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/ks_err.rs b/keystore2/src/ks_err.rs
new file mode 100644
index 0000000..c9c38c0
--- /dev/null
+++ b/keystore2/src/ks_err.rs
@@ -0,0 +1,35 @@
+// 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.
+
+//! A ks_err macro that expands error messages to include the file and line number
+
+///
+/// # Examples
+///
+/// ```
+/// use crate::ks_err;
+///
+/// ks_err!("Key is expired.");
+/// Result:
+/// "src/lib.rs:7 Key is expired."
+/// ```
+#[macro_export]
+macro_rules! ks_err {
+ { $($arg:tt)+ } => {
+ format!("{}:{}: {}", file!(), line!(), format_args!($($arg)+))
+ };
+ {} => {
+ format!("{}:{}", file!(), line!())
+ };
+}
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index 7454cca..7cf1819 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -14,11 +14,12 @@
//! This module implements methods to load legacy keystore key blob files.
+use crate::ks_err;
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,6 +27,7 @@
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,
@@ -87,6 +89,14 @@
/// 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.
@@ -96,6 +106,16 @@
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>),
@@ -124,6 +144,17 @@
/// 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
@@ -132,6 +163,19 @@
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 {
@@ -169,6 +213,16 @@
}
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
@@ -212,10 +266,14 @@
// version (1 Byte)
// blob_type (1 Byte)
// flags (1 Byte)
- // info (1 Byte)
+ // info (1 Byte) Size of an info field appended to the blob.
// 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;
@@ -291,24 +349,23 @@
None
}
_ => {
- return Err(Error::BadEncoding)
- .context("In decode_alias: could not decode filename.")
+ return Err(Error::BadEncoding).context(ks_err!("could not decode filename."));
}
};
}
if multi.is_some() {
- return Err(Error::BadEncoding).context("In decode_alias: could not decode filename.");
+ return Err(Error::BadEncoding).context(ks_err!("could not decode filename."));
}
- String::from_utf8(s).context("In decode_alias: encoded alias was not valid UTF-8.")
+ String::from_utf8(s).context(ks_err!("encoded alias was not valid UTF-8."))
}
fn new_from_stream(stream: &mut dyn Read) -> Result<Blob> {
let mut buffer = Vec::new();
- stream.read_to_end(&mut buffer).context("In new_from_stream.")?;
+ stream.read_to_end(&mut buffer).context(ks_err!())?;
if buffer.len() < Self::COMMON_HEADER_SIZE {
- return Err(Error::BadLen).context("In new_from_stream.")?;
+ return Err(Error::BadLen).context(ks_err!())?;
}
let version: u8 = buffer[Self::VERSION_OFFSET];
@@ -323,15 +380,15 @@
if version != SUPPORTED_LEGACY_BLOB_VERSION {
return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
- .context(format!("In new_from_stream: Unknown blob version: {}.", version));
+ .context(ks_err!("Unknown blob version: {}.", version));
}
let length = u32::from_be_bytes(
buffer[Self::LENGTH_OFFSET..Self::LENGTH_OFFSET + 4].try_into().unwrap(),
) as usize;
if buffer.len() < Self::COMMON_HEADER_SIZE + length {
- return Err(Error::BadLen).context(format!(
- "In new_from_stream. Expected: {} got: {}.",
+ return Err(Error::BadLen).context(ks_err!(
+ "Expected: {} got: {}.",
Self::COMMON_HEADER_SIZE + length,
buffer.len()
));
@@ -341,12 +398,28 @@
let tag = &buffer[Self::AEAD_TAG_OFFSET..Self::AEAD_TAG_OFFSET + Self::GCM_TAG_LENGTH];
match (blob_type, is_encrypted, salt) {
- (blob_types::GENERIC, _, _) => {
+ (blob_types::GENERIC, false, _) => {
Ok(Blob { flags, value: BlobValue::Generic(value.to_vec()) })
}
- (blob_types::KEY_CHARACTERISTICS, _, _) => {
+ (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, _) => {
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()) })
}
@@ -384,11 +457,12 @@
}),
(blob_types::SUPER_KEY, _, None) | (blob_types::SUPER_KEY_AES256, _, None) => {
Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In new_from_stream: Super key without salt for key derivation.")
+ .context(ks_err!("Super key without salt for key derivation."))
}
- _ => Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
- "In new_from_stream: Unknown blob type. {} {}",
- blob_type, is_encrypted
+ _ => Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!(
+ "Unknown blob type. {} {}",
+ blob_type,
+ is_encrypted
)),
}
}
@@ -409,24 +483,26 @@
where
F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>,
{
- let blob =
- Self::new_from_stream(&mut stream).context("In new_from_stream_decrypt_with.")?;
+ let blob = Self::new_from_stream(&mut stream).context(ks_err!())?;
match blob.value() {
BlobValue::Encrypted { iv, tag, data } => Ok(Blob {
flags: blob.flags,
- value: BlobValue::Decrypted(
- decrypt(data, iv, tag, None, None)
- .context("In new_from_stream_decrypt_with.")?,
- ),
+ value: BlobValue::Decrypted(decrypt(data, iv, tag, None, None).context(ks_err!())?),
}),
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))
- .context("In new_from_stream_decrypt_with.")?,
+ decrypt(data, iv, tag, Some(salt), Some(*key_size)).context(ks_err!())?,
),
}),
+ BlobValue::EncryptedGeneric { iv, tag, data } => Ok(Blob {
+ flags: blob.flags,
+ value: BlobValue::Generic(
+ decrypt(data, iv, tag, None, None).context(ks_err!())?[..].to_vec(),
+ ),
+ }),
+
_ => Ok(blob),
}
}
@@ -466,33 +542,30 @@
/// | 32 bit indirect_offset | Offset from the beginning of the indirect section.
/// +------------------------+
pub fn read_key_parameters(stream: &mut &[u8]) -> Result<Vec<KeyParameterValue>> {
- let indirect_size =
- read_ne_u32(stream).context("In read_key_parameters: While reading indirect size.")?;
+ let indirect_size = read_ne_u32(stream).context(ks_err!("While reading indirect size."))?;
let indirect_buffer = stream
.get(0..indirect_size as usize)
.ok_or(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In read_key_parameters: While reading indirect buffer.")?;
+ .context(ks_err!("While reading indirect buffer."))?;
// update the stream position.
*stream = &stream[indirect_size as usize..];
- let element_count =
- read_ne_u32(stream).context("In read_key_parameters: While reading element count.")?;
- let element_size =
- read_ne_u32(stream).context("In read_key_parameters: While reading element size.")?;
+ let element_count = read_ne_u32(stream).context(ks_err!("While reading element count."))?;
+ let element_size = read_ne_u32(stream).context(ks_err!("While reading element size."))?;
let mut element_stream = stream
.get(0..element_size as usize)
.ok_or(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In read_key_parameters: While reading elements buffer.")?;
+ .context(ks_err!("While reading elements buffer."))?;
// update the stream position.
*stream = &stream[element_size as usize..];
let mut params: Vec<KeyParameterValue> = Vec::new();
for _ in 0..element_count {
- let tag = Tag(read_ne_i32(&mut element_stream).context("In read_key_parameters.")?);
+ let tag = Tag(read_ne_i32(&mut element_stream).context(ks_err!())?);
let param = match Self::tag_type(tag) {
TagType::ENUM | TagType::ENUM_REP | TagType::UINT | TagType::UINT_REP => {
KeyParameterValue::new_from_tag_primitive_pair(
@@ -535,7 +608,7 @@
TagType::INVALID => Err(anyhow::anyhow!("Invalid.")),
_ => {
return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In read_key_parameters: Encountered bogus tag type.");
+ .context(ks_err!("Encountered bogus tag type."));
}
};
if let Ok(p) = param {
@@ -546,29 +619,96 @@
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(ks_err!("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(ks_err!("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(ks_err!("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(ks_err!("Encountered encrypted blob without super key.")),
+ }
+ }
+
fn read_characteristics_file(
&self,
uid: u32,
prefix: &str,
alias: &str,
hw_sec_level: SecurityLevel,
- ) -> Result<Vec<KeyParameter>> {
+ super_key: &Option<Arc<dyn AesGcm>>,
+ ) -> Result<LegacyKeyCharacteristics> {
let blob = Self::read_generic_blob(&self.make_chr_filename(uid, alias, prefix))
- .context("In read_characteristics_file")?;
+ .context(ks_err!())?;
let blob = match blob {
- None => return Ok(Vec::new()),
+ None => return Ok(LegacyKeyCharacteristics::Cache(Vec::new())),
Some(blob) => blob,
};
- let mut stream = match blob.value() {
- BlobValue::Characteristics(data) => &data[..],
- BlobValue::CharacteristicsCache(data) => &data[..],
+ let blob = Self::decrypt_if_required(super_key, blob)
+ .context(ks_err!("Trying to decrypt blob."))?;
+
+ let (mut stream, is_cache) = match blob.value() {
+ BlobValue::Characteristics(data) => (&data[..], false),
+ BlobValue::CharacteristicsCache(data) => (&data[..], true),
_ => {
- return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(concat!(
- "In read_characteristics_file: ",
- "Characteristics file does not hold key characteristics."
- ))
+ return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
+ .context(ks_err!("Characteristics file does not hold key characteristics."));
}
};
@@ -577,7 +717,7 @@
// the hardware enforced list.
BlobValue::CharacteristicsCache(_) => Some(
Self::read_key_parameters(&mut stream)
- .context("In read_characteristics_file.")?
+ .context(ks_err!())?
.into_iter()
.map(|value| KeyParameter::new(value, hw_sec_level)),
),
@@ -585,11 +725,16 @@
};
let sw_list = Self::read_key_parameters(&mut stream)
- .context("In read_characteristics_file.")?
+ .context(ks_err!())?
.into_iter()
.map(|value| KeyParameter::new(value, SecurityLevel::KEYSTORE));
- Ok(hw_list.into_iter().flatten().chain(sw_list).collect())
+ 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))
+ }
}
// This is a list of known prefixes that the Keystore 1.0 SPI used to use.
@@ -632,22 +777,45 @@
Ok(file) => file,
Err(e) => match e.kind() {
ErrorKind::NotFound => return Ok(None),
- _ => return Err(e).context("In read_generic_blob."),
+ _ => return Err(e).context(ks_err!()),
},
};
- Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?))
+ Ok(Some(Self::new_from_stream(&mut file).context(ks_err!())?))
+ }
+
+ 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(ks_err!()),
+ },
+ };
+
+ Ok(Some(Self::new_from_stream_decrypt_with(&mut file, decrypt).context(ks_err!())?))
}
/// Read a legacy keystore entry blob.
- pub fn read_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
+ 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>,
+ {
let path = match self.make_legacy_keystore_entry_filename(uid, alias) {
Some(path) => path,
None => return Ok(None),
};
- let blob = Self::read_generic_blob(&path)
- .context("In read_legacy_keystore_entry: Failed to read blob.")?;
+ let blob = Self::read_generic_blob_decrypt_with(&path, decrypt)
+ .context(ks_err!("Failed to read blob."))?;
Ok(blob.and_then(|blob| match blob.value {
BlobValue::Generic(blob) => Some(blob),
@@ -659,22 +827,23 @@
}
/// Remove a legacy keystore entry by the name alias with owner uid.
- pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<()> {
+ pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<bool> {
let path = match self.make_legacy_keystore_entry_filename(uid, alias) {
Some(path) => path,
- None => return Ok(()),
+ None => return Ok(false),
};
if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
match e.kind() {
- ErrorKind::NotFound => return Ok(()),
- _ => return Err(e).context("In remove_legacy_keystore_entry."),
+ ErrorKind::NotFound => return Ok(false),
+ _ => return Err(e).context(ks_err!()),
}
}
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.")
+ .context(ks_err!("Trying to remove empty user dir."))?;
+ Ok(true)
}
/// List all entries belonging to the given uid.
@@ -688,27 +857,21 @@
Err(e) => match e.kind() {
ErrorKind::NotFound => return Ok(Default::default()),
_ => {
- return Err(e).context(format!(
- concat!(
- "In list_legacy_keystore_entries_for_uid: ,",
- "Failed to open legacy blob database: {:?}"
- ),
- path
- ))
+ return Err(e)
+ .context(ks_err!("Failed to open legacy blob database: {:?}", path));
}
},
};
let mut result: Vec<String> = Vec::new();
for entry in dir {
- let file_name = entry
- .context("In list_legacy_keystore_entries_for_uid: Trying to access dir entry")?
- .file_name();
+ let file_name = entry.context(ks_err!("Trying to access dir entry"))?.file_name();
if let Some(f) = file_name.to_str() {
let encoded_alias = &f[uid_str.len() + 1..];
if f.starts_with(&uid_str) && !Self::is_keystore_alias(encoded_alias) {
- result.push(Self::decode_alias(encoded_alias).context(
- "In list_legacy_keystore_entries_for_uid: Trying to decode alias.",
- )?)
+ result.push(
+ Self::decode_alias(encoded_alias)
+ .context(ks_err!("Trying to decode alias."))?,
+ )
}
}
}
@@ -730,9 +893,7 @@
&self,
user_id: u32,
) -> Result<HashMap<u32, HashSet<String>>> {
- let user_entries = self
- .list_user(user_id)
- .context("In list_legacy_keystore_entries_for_user: Trying to list user.")?;
+ let user_entries = self.list_user(user_id).context(ks_err!("Trying to list user."))?;
let result =
user_entries.into_iter().fold(HashMap::<u32, HashSet<String>>::new(), |mut acc, v| {
@@ -805,9 +966,9 @@
/// in the database dir.
pub fn is_empty(&self) -> Result<bool> {
let dir = Self::with_retry_interrupted(|| fs::read_dir(self.path.as_path()))
- .context("In is_empty: Failed to open legacy blob database.")?;
+ .context(ks_err!("Failed to open legacy blob database."))?;
for entry in dir {
- if (*entry.context("In is_empty: Trying to access dir entry")?.file_name())
+ if (*entry.context(ks_err!("Trying to access dir entry"))?.file_name())
.to_str()
.map_or(false, |f| f.starts_with("user_"))
{
@@ -826,7 +987,7 @@
return Ok(true);
}
Ok(Self::with_retry_interrupted(|| user_path.read_dir())
- .context("In is_empty_user: Failed to open legacy user dir.")?
+ .context(ks_err!("Failed to open legacy user dir."))?
.next()
.is_none())
}
@@ -851,16 +1012,14 @@
Err(e) => match e.kind() {
ErrorKind::NotFound => return Ok(Default::default()),
_ => {
- return Err(e).context(format!(
- "In list_user: Failed to open legacy blob database. {:?}",
- path
- ))
+ return Err(e)
+ .context(ks_err!("Failed to open legacy blob database. {:?}", path));
}
},
};
let mut result: Vec<String> = Vec::new();
for entry in dir {
- let file_name = entry.context("In list_user: Trying to access dir entry")?.file_name();
+ let file_name = entry.context(ks_err!("Trying to access dir entry"))?.file_name();
if let Some(f) = file_name.to_str() {
result.push(f.to_string())
}
@@ -874,9 +1033,7 @@
&self,
user_id: u32,
) -> Result<HashMap<u32, HashSet<String>>> {
- let user_entries = self
- .list_user(user_id)
- .context("In list_keystore_entries_for_user: Trying to list user.")?;
+ let user_entries = self.list_user(user_id).context(ks_err!("Trying to list user."))?;
let result =
user_entries.into_iter().fold(HashMap::<u32, HashSet<String>>::new(), |mut acc, v| {
@@ -897,9 +1054,7 @@
pub fn list_keystore_entries_for_uid(&self, uid: u32) -> Result<Vec<String>> {
let user_id = uid_to_android_user(uid);
- let user_entries = self
- .list_user(user_id)
- .context("In list_keystore_entries_for_uid: Trying to list user.")?;
+ let user_entries = self.list_user(user_id).context(ks_err!("Trying to list user."))?;
let uid_str = format!("{}_", uid);
@@ -982,17 +1137,91 @@
if something_was_deleted {
let user_id = uid_to_android_user(uid);
self.remove_user_dir_if_empty(user_id)
- .context("In remove_keystore_entry: Trying to remove empty user dir.")?;
+ .context(ks_err!("Trying to remove empty user dir."))?;
}
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(ks_err!("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(ks_err!());
+ }
+
+ 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(|| ks_err!("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(|| {
+ ks_err!(
+ "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)
- .context("In remove_user_dir_if_empty: Trying to check for empty user dir.")?
- {
+ if self.is_empty_user(user_id).context(ks_err!("Trying to check for empty user dir."))? {
let user_path = self.make_user_path_name(user_id);
Self::with_retry_interrupted(|| fs::remove_dir(user_path.as_path())).ok();
}
@@ -1004,42 +1233,18 @@
&self,
uid: u32,
alias: &str,
- key_manager: Option<&SuperKeyManager>,
- ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
+ super_key: &Option<Arc<dyn AesGcm>>,
+ ) -> Result<(Option<(Blob, LegacyKeyCharacteristics)>, 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(_) } => 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
- }
- }
+ Blob { flags: _, value: BlobValue::Decrypted(_) }
+ | Blob { flags: _, value: BlobValue::Encrypted { .. } } => km_blob,
_ => {
- return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
- "In load_by_uid_alias: Found wrong blob type in legacy key blob file.",
- )
+ return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
+ .context(ks_err!("Found wrong blob type in legacy key blob file."))
}
};
@@ -1048,35 +1253,46 @@
false => SecurityLevel::TRUSTED_ENVIRONMENT,
};
let key_parameters = self
- .read_characteristics_file(uid, &prefix, alias, hw_sec_level)
- .context("In load_by_uid_alias.")?;
+ .read_characteristics_file(uid, &prefix, alias, hw_sec_level, super_key)
+ .context(ks_err!())?;
Some((km_blob, key_parameters))
}
None => None,
};
- 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",
- )
- }
- };
+ let user_cert_blob =
+ Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT"))
+ .context(ks_err!("While loading user cert."))?;
- 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,
- _ => {
+ let user_cert = if let Some(blob) = user_cert_blob {
+ let blob = Self::decrypt_if_required(super_key, blob)
+ .context(ks_err!("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 CACERT file")
+ .context(ks_err!("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(ks_err!("While loading ca cert."))?;
+
+ let ca_cert = if let Some(blob) = ca_cert_blob {
+ let blob = Self::decrypt_if_required(super_key, blob)
+ .context(ks_err!("While decrypting ca cert."))?;
+
+ if let Blob { value: BlobValue::Generic(data), .. } = blob {
+ Some(data)
+ } else {
+ return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
+ .context(ks_err!("Found unexpected blob type in CACERT file"));
+ }
+ } else {
+ None
};
Ok((km_blob, user_cert, ca_cert))
@@ -1090,32 +1306,26 @@
/// Load and decrypt legacy super key blob.
pub fn load_super_key(&self, user_id: u32, pw: &Password) -> Result<Option<ZVec>> {
let path = self.make_super_key_filename(user_id);
- let blob = Self::read_generic_blob(&path)
- .context("In load_super_key: While loading super key.")?;
+ let blob = Self::read_generic_blob(&path).context(ks_err!("While loading super key."))?;
let blob = match blob {
Some(blob) => match blob {
Blob { flags, value: BlobValue::PwEncrypted { iv, tag, data, salt, key_size } } => {
if (flags & flags::ENCRYPTED) != 0 {
let key = pw
- .derive_key(Some(&salt), key_size)
- .context("In load_super_key: Failed to derive key from password.")?;
- let blob = aes_gcm_decrypt(&data, &iv, &tag, &key).context(
- "In load_super_key: while trying to decrypt legacy super key blob.",
- )?;
+ .derive_key(&salt, key_size)
+ .context(ks_err!("Failed to derive key from password."))?;
+ let blob = aes_gcm_decrypt(&data, &iv, &tag, &key)
+ .context(ks_err!("while trying to decrypt legacy super key blob."))?;
Some(blob)
} else {
// In 2019 we had some unencrypted super keys due to b/141955555.
- Some(
- data.try_into()
- .context("In load_super_key: Trying to convert key into ZVec")?,
- )
+ Some(data.try_into().context(ks_err!("Trying to convert key into ZVec"))?)
}
}
_ => {
- return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
- "In load_super_key: Found wrong blob type in legacy super key blob file.",
- )
+ return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
+ .context(ks_err!("Found wrong blob type in legacy super key blob file."));
}
},
None => None,
@@ -1137,17 +1347,318 @@
}
}
+/// 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 anyhow::anyhow;
+ use crate::legacy_blob::test_utils::legacy_blob_test_vectors::*;
+ use crate::legacy_blob::test_utils::*;
+ use anyhow::{anyhow, Result};
use keystore2_crypto::aes_gcm_decrypt;
- use rand::Rng;
- 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;
+ use rand::Rng;
+ use std::convert::TryInto;
+ use std::ops::Deref;
+ use std::string::FromUtf8Error;
#[test]
fn decode_encode_alias_test() {
@@ -1203,7 +1714,8 @@
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());
@@ -1213,7 +1725,8 @@
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());
@@ -1301,62 +1814,75 @@
#[test]
fn test_legacy_blobs() -> anyhow::Result<()> {
- let temp_dir = TempDir::new("legacy_blob_test")?;
- std::fs::create_dir(&*temp_dir.build().push("user_0"))?;
+ let temp_dir = TempDir::new("legacy_blob_test").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
- std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY)?;
+ 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();
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());
- 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)?
+ {
+ 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!("");
+ }
if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) =
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
{
assert_eq!(flags, 4);
//assert_eq!(value, BlobValue::Encrypted(..));
@@ -1366,7 +1892,7 @@
panic!("");
}
if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
- legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
{
assert_eq!(flags, 0);
assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
@@ -1383,11 +1909,11 @@
assert_eq!(
(None, None, None),
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
);
assert_eq!(
(None, None, None),
- legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
);
// The database should not be empty due to the super key.
@@ -1406,9 +1932,319 @@
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(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(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(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")?;
+ let temp_dir = TempDir::new("list_non_existing_user").unwrap();
let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
assert!(legacy_blob_loader.list_user(20)?.is_empty());
@@ -1418,11 +2254,66 @@
#[test]
fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> {
- let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user")?;
+ let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap();
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/legacy_blob_test_vectors.rs b/keystore2/src/legacy_blob/test_utils/legacy_blob_test_vectors.rs
similarity index 91%
rename from keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs
rename to keystore2/src/legacy_blob/test_utils/legacy_blob_test_vectors.rs
index 14bd40c..3eecee0 100644
--- a/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs
+++ b/keystore2/src/legacy_blob/test_utils/legacy_blob_test_vectors.rs
@@ -12,6 +12,15 @@
// 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
@@ -22,6 +31,109 @@
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,
@@ -53,6 +165,7 @@
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,
@@ -82,11 +195,13 @@
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,
@@ -119,6 +234,7 @@
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,
@@ -149,6 +265,7 @@
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,
@@ -156,6 +273,7 @@
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,
@@ -164,6 +282,29 @@
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,
@@ -203,6 +344,56 @@
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,
@@ -218,6 +409,8 @@
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,
@@ -264,6 +457,8 @@
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,
@@ -452,6 +647,7 @@
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,
@@ -491,6 +687,8 @@
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,
@@ -506,6 +704,7 @@
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,
@@ -553,6 +752,7 @@
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,
@@ -741,6 +941,7 @@
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,
@@ -778,6 +979,7 @@
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,
@@ -822,6 +1024,8 @@
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,
@@ -1008,6 +1212,7 @@
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,
@@ -1045,6 +1250,7 @@
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,
@@ -1090,6 +1296,7 @@
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_importer.rs
new file mode 100644
index 0000000..9eb702d
--- /dev/null
+++ b/keystore2/src/legacy_importer.rs
@@ -0,0 +1,927 @@
+// 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 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::ks_err;
+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::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
+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,
+};
+use anyhow::{Context, Result};
+use core::ops::Deref;
+use keystore2_crypto::{Password, ZVec};
+use std::collections::{HashMap, HashSet};
+use std::sync::atomic::{AtomicU8, Ordering};
+use std::sync::mpsc::channel;
+use std::sync::{Arc, Mutex};
+
+/// Represents LegacyImporter.
+pub struct LegacyImporter {
+ async_task: Arc<AsyncTask>,
+ initializer: Mutex<
+ Option<
+ Box<
+ dyn FnOnce() -> (KeystoreDB, HashMap<SecurityLevel, Uuid>, Arc<LegacyBlobLoader>)
+ + Send
+ + 'static,
+ >,
+ >,
+ >,
+ /// 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.
+ /// 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 {
+ uid: u32,
+ alias: String,
+}
+
+impl RecentImport {
+ fn new(uid: u32, alias: String) -> Self {
+ Self { uid, alias }
+ }
+}
+
+enum BulkDeleteRequest {
+ Uid(u32),
+ User(u32),
+}
+
+struct LegacyImporterState {
+ recently_imported: HashSet<RecentImport>,
+ recently_imported_super_key: HashSet<u32>,
+ legacy_loader: Arc<LegacyBlobLoader>,
+ sec_level_to_km_uuid: HashMap<SecurityLevel, Uuid>,
+ db: KeystoreDB,
+}
+
+impl LegacyImporter {
+ const WIFI_NAMESPACE: i64 = 102;
+ const AID_WIFI: u32 = 1010;
+
+ const STATE_UNINITIALIZED: u8 = 0;
+ const STATE_READY: u8 = 1;
+ const STATE_EMPTY: u8 = 2;
+
+ /// Constructs a new LegacyImporter using the given AsyncTask object as import
+ /// worker.
+ pub fn new(async_task: Arc<AsyncTask>) -> Self {
+ Self {
+ async_task,
+ initializer: Default::default(),
+ state: AtomicU8::new(Self::STATE_UNINITIALIZED),
+ }
+ }
+
+ /// The legacy importer must be initialized deferred, because keystore starts very early.
+ /// At this time the data partition may not be mounted. So we cannot open database connections
+ /// until we get actual key load requests. This sets the function that the legacy loader
+ /// uses to connect to the database.
+ pub fn set_init<F>(&self, f_init: F) -> Result<()>
+ where
+ F: FnOnce() -> (KeystoreDB, HashMap<SecurityLevel, Uuid>, Arc<LegacyBlobLoader>)
+ + Send
+ + 'static,
+ {
+ let mut initializer = self.initializer.lock().expect("Failed to lock initializer.");
+
+ // If we are not uninitialized we have no business setting the initializer.
+ if self.state.load(Ordering::Relaxed) != Self::STATE_UNINITIALIZED {
+ return Ok(());
+ }
+
+ // Only set the initializer if it hasn't been set before.
+ if initializer.is_none() {
+ *initializer = Some(Box::new(f_init))
+ }
+
+ 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
+ /// 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
+ /// 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> {
+ let mut first_try = true;
+ loop {
+ match (self.state.load(Ordering::Relaxed), first_try) {
+ (Self::STATE_EMPTY, _) => {
+ return Ok(Self::STATE_EMPTY);
+ }
+ (Self::STATE_UNINITIALIZED, true) => {
+ // If we find the legacy loader uninitialized, we grab the initializer lock,
+ // check if the legacy database is empty, and if not, schedule an initialization
+ // request. Coming out of the initializer lock, the state is either EMPTY or
+ // READY.
+ let mut initializer = self.initializer.lock().unwrap();
+
+ if let Some(initializer) = initializer.take() {
+ let (db, sec_level_to_km_uuid, legacy_loader) = (initializer)();
+
+ if legacy_loader.is_empty().context(
+ "In check_state: Trying to check if the legacy database is empty.",
+ )? {
+ self.state.store(Self::STATE_EMPTY, Ordering::Relaxed);
+ return Ok(Self::STATE_EMPTY);
+ }
+
+ self.async_task.queue_hi(move |shelf| {
+ shelf.get_or_put_with(|| LegacyImporterState {
+ recently_imported: Default::default(),
+ recently_imported_super_key: Default::default(),
+ legacy_loader,
+ sec_level_to_km_uuid,
+ db,
+ });
+ });
+
+ // It is safe to set this here even though the async task may not yet have
+ // run because any thread observing this will not be able to schedule a
+ // task that can run before the initialization.
+ // Also we can only transition out of this state while having the
+ // initializer lock and having found an initializer.
+ self.state.store(Self::STATE_READY, Ordering::Relaxed);
+ return Ok(Self::STATE_READY);
+ } else {
+ // There is a chance that we just lost the race from state.load() to
+ // grabbing the initializer mutex. If that is the case the state must
+ // be EMPTY or READY after coming out of the lock. So we can give it
+ // one more try.
+ first_try = false;
+ continue;
+ }
+ }
+ (Self::STATE_UNINITIALIZED, false) => {
+ // Okay, tough luck. The legacy loader was really completely uninitialized.
+ return Err(Error::sys())
+ .context(ks_err!("Legacy loader should not be called uninitialized."));
+ }
+ (Self::STATE_READY, _) => return Ok(Self::STATE_READY),
+ (s, _) => panic!("Unknown legacy importer 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 uid = match (domain, namespace) {
+ (Domain::APP, namespace) => namespace as u32,
+ (Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
+ _ => return Ok(Vec::new()),
+ };
+ self.do_serialized(move |state| state.list_uid(uid)).unwrap_or_else(|| Ok(Vec::new())).map(
+ |v| {
+ v.into_iter()
+ .map(|alias| KeyDescriptor {
+ domain,
+ nspace: namespace,
+ alias: Some(alias),
+ blob: None,
+ })
+ .collect()
+ },
+ )
+ }
+
+ /// Sends the given closure to the importer 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.
+ /// 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,
+ {
+ // Short circuit if the database is empty or not initialized (error case).
+ match self.check_state().context(ks_err!("Checking state.")) {
+ Ok(LegacyImporter::STATE_EMPTY) => return None,
+ Ok(LegacyImporter::STATE_READY) => {}
+ Err(e) => return Some(Err(e)),
+ Ok(s) => panic!("Unknown legacy importer state. {} ", s),
+ }
+
+ // We have established that there may be a key in the legacy database.
+ // Now we schedule an import 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
+ // 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 result = f(legacy_importer_state);
+ (legacy_importer_state.check_empty(), Some(result))
+ } else {
+ (Self::STATE_EMPTY, None)
+ };
+
+ // If the import 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>();
+ }
+
+ // Send the result to the requester.
+ if let Err(e) = sender.send((new_state, result)) {
+ log::error!("In do_serialized. Error in sending the result. {:?}", e);
+ }
+ });
+
+ let (new_state, result) = match receiver.recv() {
+ Err(e) => {
+ return Some(Err(e).context(ks_err!("Failed to receive from the sender.")));
+ }
+ Ok(r) => r,
+ };
+
+ // We can only transition to EMPTY but never back.
+ // The importer never creates any legacy blobs.
+ if new_state == Self::STATE_EMPTY {
+ self.state.store(Self::STATE_EMPTY, Ordering::Relaxed)
+ }
+
+ result
+ }
+
+ /// 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
+ /// 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>(
+ &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);
+
+ // Access the key and return on success.
+ match key_accessor() {
+ Ok(result) => return Ok(result),
+ Err(e) => match e.root_cause().downcast_ref::<Error>() {
+ Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => {}
+ _ => return Err(e),
+ },
+ }
+
+ // 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.
+ let uid = match key {
+ KeyDescriptor { domain: Domain::APP, alias: Some(_), .. } => caller_uid,
+ KeyDescriptor { domain: Domain::SELINUX, nspace, alias: Some(_), .. } => {
+ match *nspace {
+ Self::WIFI_NAMESPACE => Self::AID_WIFI,
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context(format!("No legacy keys for namespace {}", nspace))
+ }
+ }
+ }
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("No legacy keys for key descriptor.")
+ }
+ };
+
+ 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)
+ });
+
+ if let Some(result) = result {
+ result?;
+ // After successful import try again.
+ key_accessor()
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context("Legacy database is empty.")
+ }
+ }
+
+ /// 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>(
+ &self,
+ user_id: u32,
+ pw: &Password,
+ mut key_accessor: F,
+ ) -> Result<Option<T>>
+ where
+ F: FnMut() -> Result<Option<T>>,
+ {
+ let _wp = wd::watch_millis("LegacyImporter::with_try_import_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(ks_err!("Cloning password."))?;
+ let result = self.do_serialized(move |importer_state| {
+ importer_state.check_and_import_super_key(user_id, &pw)
+ });
+
+ if let Some(result) = result {
+ result?;
+ // After successful import try again.
+ key_accessor()
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Deletes all keys belonging to the given namespace, importing 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 uid = match (domain, nspace) {
+ (Domain::APP, nspace) => nspace as u32,
+ (Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
+ // Nothing to do.
+ _ => return Ok(()),
+ };
+
+ let result = self.do_serialized(move |importer_state| {
+ importer_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
+ /// 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 result = self.do_serialized(move |importer_state| {
+ importer_state
+ .bulk_delete(BulkDeleteRequest::User(user_id), keep_non_super_encrypted_keys)
+ });
+
+ result.unwrap_or(Ok(()))
+ }
+
+ /// 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));
+ result.unwrap_or(Ok(false))
+ }
+}
+
+impl LegacyImporterState {
+ fn get_km_uuid(&self, is_strongbox: bool) -> Result<Uuid> {
+ let sec_level = if is_strongbox {
+ SecurityLevel::STRONGBOX
+ } else {
+ SecurityLevel::TRUSTED_ENVIRONMENT
+ };
+
+ self.sec_level_to_km_uuid.get(&sec_level).copied().ok_or_else(|| {
+ anyhow::anyhow!(Error::sys()).context(ks_err!("No KM instance for blob."))
+ })
+ }
+
+ fn list_uid(&mut self, uid: u32) -> Result<Vec<String>> {
+ self.legacy_loader
+ .list_keystore_entries_for_uid(uid)
+ .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(ks_err!("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(ks_err!(
+ "Cannot import super key of this key while user is locked."
+ ))
+ } else {
+ self.legacy_loader
+ .remove_keystore_entry(uid, alias)
+ .context(ks_err!("Trying to remove obsolete key."))?;
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context(ks_err!("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(ks_err!("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(ks_err!("Decryption failed."))?;
+ LegacyBlob::ZVec(blob)
+ }
+ (BlobValue::Encrypted { .. }, None) => {
+ return Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!(
+ "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(ks_err!("Unexpected blob type."));
+ }
+ };
+
+ let (km_params, upgraded_blob) = get_key_characteristics_without_app_data(&km_uuid, &blob)
+ .context(ks_err!("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(ks_err!("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(ks_err!("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(ks_err!(
+ "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(ks_err!(
+ "Failed to convert upgraded blob to ZVec."
+ ))?),
+ ),
+ Some(superseded_blob),
+ )
+ }
+ _ => {
+ return Err(Error::sys()).context(ks_err!(
+ "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
+ /// be passed to do_serialized.
+ fn check_and_import(
+ &mut self,
+ uid: u32,
+ mut key: KeyDescriptor,
+ super_key: Option<Arc<dyn AesGcm>>,
+ ) -> Result<()> {
+ let alias = key.alias.clone().ok_or_else(|| {
+ anyhow::anyhow!(Error::sys()).context(ks_err!(
+ "Must be Some because \
+ our caller must not have called us otherwise."
+ ))
+ })?;
+
+ if self.recently_imported.contains(&RecentImport::new(uid, alias.clone())) {
+ return Ok(());
+ }
+
+ if key.domain == Domain::APP {
+ key.nspace = uid as i64;
+ }
+
+ // 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(ks_err!("Trying to load legacy blob."))?;
+
+ let (km_blob_params, superseded_blob) = self
+ .characteristics_file_to_cache(km_blob_params, &super_key, uid, &alias)
+ .context(ks_err!("Trying to update legacy characteristics."))?;
+
+ 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(ks_err!("Failed to get super key id."))?;
+
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
+ blob_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
+ blob_metadata
+ .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+ (LegacyBlob::Vec(data), blob_metadata)
+ }
+ BlobValue::Decrypted(data) => (LegacyBlob::ZVec(data), BlobMetaData::new()),
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context(ks_err!("Legacy key has unexpected type."));
+ }
+ };
+
+ let km_uuid =
+ self.get_km_uuid(is_strongbox).context(ks_err!("Trying to get KM UUID"))?;
+ blob_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+
+ let mut metadata = KeyMetaData::new();
+ let creation_date =
+ DateTime::now().context(ks_err!("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,
+ &CertificateInfo::new(user_cert, ca_cert),
+ &metadata,
+ &km_uuid,
+ )
+ .context(ks_err!())?;
+ Ok(())
+ }
+ None => {
+ if let Some(ca_cert) = ca_cert {
+ self.db
+ .store_new_certificate(&key, KeyType::Client, &ca_cert, &KEYSTORE_UUID)
+ .context(ks_err!("Failed to insert new certificate."))?;
+ Ok(())
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context(ks_err!("Legacy key not found."))
+ }
+ }
+ };
+
+ match result {
+ Ok(()) => {
+ // Add the key to the imported_keys list.
+ self.recently_imported.insert(RecentImport::new(uid, alias.clone()));
+ // Delete legacy key from the file system
+ self.legacy_loader
+ .remove_keystore_entry(uid, &alias)
+ .context(ks_err!("Trying to remove imported 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) {
+ return Ok(());
+ }
+
+ if let Some(super_key) = self
+ .legacy_loader
+ .load_super_key(user_id, pw)
+ .context(ks_err!("Trying to load legacy super key."))?
+ {
+ let (blob, blob_metadata) =
+ crate::super_key::SuperKeyManager::encrypt_with_password(&super_key, pw)
+ .context(ks_err!("Trying to encrypt super key."))?;
+
+ self.db
+ .store_super_key(
+ user_id,
+ &USER_SUPER_KEY,
+ &blob,
+ &blob_metadata,
+ &KeyMetaData::new(),
+ )
+ .context(ks_err!("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);
+ Ok(())
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context(ks_err!("No key found do import."))
+ }
+ }
+
+ /// Key importer request to be run by do_serialized.
+ /// See LegacyImporter::bulk_delete_uid and LegacyImporter::bulk_delete_user.
+ fn bulk_delete(
+ &mut self,
+ bulk_delete_request: BulkDeleteRequest,
+ keep_non_super_encrypted_keys: bool,
+ ) -> Result<()> {
+ let (aliases, user_id) = match bulk_delete_request {
+ BulkDeleteRequest::Uid(uid) => (
+ self.legacy_loader
+ .list_keystore_entries_for_uid(uid)
+ .context(ks_err!("Trying to get aliases for uid."))
+ .map(|aliases| {
+ let mut h = HashMap::<u32, HashSet<String>>::new();
+ h.insert(uid, aliases.into_iter().collect());
+ h
+ })?,
+ uid_to_android_user(uid),
+ ),
+ BulkDeleteRequest::User(user_id) => (
+ self.legacy_loader
+ .list_keystore_entries_for_user(user_id)
+ .context(ks_err!("Trying to get aliases for user_id."))?,
+ user_id,
+ ),
+ };
+
+ let super_key_id = self
+ .db
+ .load_super_key(&USER_SUPER_KEY, user_id)
+ .context(ks_err!("Failed to load super key"))?
+ .map(|(_, entry)| entry.id());
+
+ for (uid, alias) in aliases
+ .into_iter()
+ .flat_map(|(uid, aliases)| aliases.into_iter().map(move |alias| (uid, alias)))
+ {
+ let (km_blob_params, _, _) = self
+ .legacy_loader
+ .load_by_uid_alias(uid, &alias, &None)
+ .context(ks_err!("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()
+ }),
+ blob.is_encrypted(),
+ )
+ })
+ .unwrap_or((false, false));
+
+ if keep_non_super_encrypted_keys && !is_super_encrypted {
+ continue;
+ }
+
+ if need_gc {
+ let mark_deleted = match km_blob_params
+ .map(|(blob, _)| (blob.is_strongbox(), blob.take_value()))
+ {
+ Some((is_strongbox, BlobValue::Encrypted { iv, tag, data })) => {
+ let mut blob_metadata = BlobMetaData::new();
+ if let (Ok(km_uuid), Some(super_key_id)) =
+ (self.get_km_uuid(is_strongbox), super_key_id)
+ {
+ blob_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+ blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
+ blob_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
+ blob_metadata
+ .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+ Some((LegacyBlob::Vec(data), blob_metadata))
+ } else {
+ // Oh well - we tried our best, but if we cannot determine which
+ // KeyMint instance we have to send this blob to, we cannot
+ // do more than delete the key from the file system.
+ // And if we don't know which key wraps this key we cannot
+ // unwrap it for KeyMint either.
+ None
+ }
+ }
+ Some((_, BlobValue::Decrypted(data))) => {
+ Some((LegacyBlob::ZVec(data), BlobMetaData::new()))
+ }
+ _ => None,
+ };
+
+ if let Some((blob, blob_metadata)) = mark_deleted {
+ self.db.set_deleted_blob(&blob, &blob_metadata).context(ks_err!(
+ "Trying to insert deleted \
+ blob into the database for garbage collection."
+ ))?;
+ }
+ }
+
+ self.legacy_loader
+ .remove_keystore_entry(uid, &alias)
+ .context(ks_err!("Trying to remove imported key."))?;
+ }
+ Ok(())
+ }
+
+ fn has_super_key(&mut self, user_id: u32) -> Result<bool> {
+ Ok(self.recently_imported_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
+ } else {
+ LegacyImporter::STATE_READY
+ }
+ }
+}
+
+enum LegacyBlob<'a> {
+ Vec(Vec<u8>),
+ ZVec(ZVec),
+ Ref(&'a [u8]),
+}
+
+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);
+ }
+ }
+ 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(|| ks_err!("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("Calling GetKeyCharacteristics.", 500);
+ map_km_error(km_dev.getKeyCharacteristics(blob, &[], &[]))
+ },
+ |_| Ok(()),
+ )
+ .context(ks_err!())?;
+ Ok((key_characteristics_to_internal(characteristics), upgraded_blob))
+}
diff --git a/keystore2/src/legacy_migrator.rs b/keystore2/src/legacy_migrator.rs
deleted file mode 100644
index 65f4b0b..0000000
--- a/keystore2/src/legacy_migrator.rs
+++ /dev/null
@@ -1,731 +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 acts as a bridge between the legacy key database and the keystore2 database.
-
-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,
-};
-use anyhow::{Context, Result};
-use core::ops::Deref;
-use keystore2_crypto::{Password, ZVec};
-use std::collections::{HashMap, HashSet};
-use std::sync::atomic::{AtomicU8, Ordering};
-use std::sync::mpsc::channel;
-use std::sync::{Arc, Mutex};
-
-/// Represents LegacyMigrator.
-pub struct LegacyMigrator {
- async_task: Arc<AsyncTask>,
- initializer: Mutex<
- Option<
- Box<
- dyn FnOnce() -> (KeystoreDB, HashMap<SecurityLevel, Uuid>, Arc<LegacyBlobLoader>)
- + Send
- + 'static,
- >,
- >,
- >,
- /// This atomic is used for cheap interior mutability. It is intended to prevent
- /// 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 RecentMigration {
- uid: u32,
- alias: String,
-}
-
-impl RecentMigration {
- fn new(uid: u32, alias: String) -> Self {
- Self { uid, alias }
- }
-}
-
-enum BulkDeleteRequest {
- Uid(u32),
- User(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 LegacyMigrator {
- const WIFI_NAMESPACE: i64 = 102;
- const AID_WIFI: u32 = 1010;
-
- const STATE_UNINITIALIZED: u8 = 0;
- const STATE_READY: u8 = 1;
- const STATE_EMPTY: u8 = 2;
-
- /// Constructs a new LegacyMigrator using the given AsyncTask object as migration
- /// worker.
- pub fn new(async_task: Arc<AsyncTask>) -> Self {
- Self {
- async_task,
- initializer: Default::default(),
- state: AtomicU8::new(Self::STATE_UNINITIALIZED),
- }
- }
-
- /// 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.
- pub fn set_init<F>(&self, f_init: F) -> Result<()>
- where
- F: FnOnce() -> (KeystoreDB, HashMap<SecurityLevel, Uuid>, Arc<LegacyBlobLoader>)
- + Send
- + 'static,
- {
- let mut initializer = self.initializer.lock().expect("Failed to lock initializer.");
-
- // If we are not uninitialized we have no business setting the initializer.
- if self.state.load(Ordering::Relaxed) != Self::STATE_UNINITIALIZED {
- return Ok(());
- }
-
- // Only set the initializer if it hasn't been set before.
- if initializer.is_none() {
- *initializer = Some(Box::new(f_init))
- }
-
- Ok(())
- }
-
- /// 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 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> {
- let mut first_try = true;
- loop {
- match (self.state.load(Ordering::Relaxed), first_try) {
- (Self::STATE_EMPTY, _) => {
- return Ok(Self::STATE_EMPTY);
- }
- (Self::STATE_UNINITIALIZED, true) => {
- // If we find the legacy loader uninitialized, we grab the initializer lock,
- // check if the legacy database is empty, and if not, schedule an initialization
- // request. Coming out of the initializer lock, the state is either EMPTY or
- // READY.
- let mut initializer = self.initializer.lock().unwrap();
-
- if let Some(initializer) = initializer.take() {
- let (db, sec_level_to_km_uuid, legacy_loader) = (initializer)();
-
- if legacy_loader.is_empty().context(
- "In check_state: Trying to check if the legacy database is empty.",
- )? {
- self.state.store(Self::STATE_EMPTY, Ordering::Relaxed);
- return Ok(Self::STATE_EMPTY);
- }
-
- self.async_task.queue_hi(move |shelf| {
- shelf.get_or_put_with(|| LegacyMigratorState {
- recently_migrated: Default::default(),
- recently_migrated_super_key: Default::default(),
- legacy_loader,
- sec_level_to_km_uuid,
- db,
- });
- });
-
- // It is safe to set this here even though the async task may not yet have
- // run because any thread observing this will not be able to schedule a
- // task that can run before the initialization.
- // Also we can only transition out of this state while having the
- // initializer lock and having found an initializer.
- self.state.store(Self::STATE_READY, Ordering::Relaxed);
- return Ok(Self::STATE_READY);
- } else {
- // There is a chance that we just lost the race from state.load() to
- // grabbing the initializer mutex. If that is the case the state must
- // be EMPTY or READY after coming out of the lock. So we can give it
- // one more try.
- first_try = false;
- continue;
- }
- }
- (Self::STATE_UNINITIALIZED, false) => {
- // Okay, tough luck. The legacy loader was really completely uninitialized.
- return Err(Error::sys()).context(
- "In check_state: Legacy loader should not be called uninitialized.",
- );
- }
- (Self::STATE_READY, _) => return Ok(Self::STATE_READY),
- (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("LegacyMigrator::list_uid", 500);
-
- let uid = match (domain, namespace) {
- (Domain::APP, namespace) => namespace as u32,
- (Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
- _ => return Ok(Vec::new()),
- };
- self.do_serialized(move |state| state.list_uid(uid)).unwrap_or_else(|| Ok(Vec::new())).map(
- |v| {
- v.into_iter()
- .map(|alias| KeyDescriptor {
- domain,
- nspace: namespace,
- alias: Some(alias),
- blob: None,
- })
- .collect()
- },
- )
- }
-
- /// 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 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 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(LegacyMigrator::STATE_EMPTY) => return None,
- Ok(LegacyMigrator::STATE_READY) => {}
- Err(e) => return Some(Err(e)),
- Ok(s) => panic!("Unknown legacy migrator state. {} ", s),
- }
-
- // We have established that there may be a key in the legacy database.
- // Now we schedule a migration request.
- let (sender, receiver) = channel();
- self.async_task.queue_hi(move |shelf| {
- // 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_migrator_state) =
- shelf.get_downcast_mut::<LegacyMigratorState>()
- {
- let result = f(legacy_migrator_state);
- (legacy_migrator_state.check_empty(), Some(result))
- } else {
- (Self::STATE_EMPTY, None)
- };
-
- // 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::<LegacyMigratorState>();
- }
-
- // Send the result to the requester.
- if let Err(e) = sender.send((new_state, result)) {
- log::error!("In do_serialized. Error in sending the result. {:?}", e);
- }
- });
-
- let (new_state, result) = match receiver.recv() {
- Err(e) => {
- return Some(Err(e).context("In do_serialized. Failed to receive from the sender."))
- }
- Ok(r) => r,
- };
-
- // We can only transition to EMPTY but never back.
- // The migrator never creates any legacy blobs.
- if new_state == Self::STATE_EMPTY {
- self.state.store(Self::STATE_EMPTY, Ordering::Relaxed)
- }
-
- result
- }
-
- /// Runs the key_accessor function and returns its result. If it returns an error and the
- /// 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 migration request was successful.
- pub fn with_try_migrate<F, T>(
- &self,
- key: &KeyDescriptor,
- caller_uid: u32,
- key_accessor: F,
- ) -> Result<T>
- where
- F: Fn() -> Result<T>,
- {
- let _wp = wd::watch_millis("LegacyMigrator::with_try_migrate", 500);
-
- // Access the key and return on success.
- match key_accessor() {
- Ok(result) => return Ok(result),
- Err(e) => match e.root_cause().downcast_ref::<Error>() {
- Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => {}
- _ => return Err(e),
- },
- }
-
- // Filter inputs. We can only load legacy app domain keys and some special rules due
- // 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(_), .. } => {
- match *nspace {
- Self::WIFI_NAMESPACE => Self::AID_WIFI,
- _ => {
- return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
- .context(format!("No legacy keys for namespace {}", nspace))
- }
- }
- }
- _ => {
- return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
- .context("No legacy keys for key descriptor.")
- }
- };
-
- let key_clone = key.clone();
- let result = self
- .do_serialized(move |migrator_state| migrator_state.check_and_migrate(uid, key_clone));
-
- if let Some(result) = result {
- result?;
- // After successful migration try again.
- key_accessor()
- } else {
- Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context("Legacy database is empty.")
- }
- }
-
- /// Calls key_accessor and returns the result on success. In the case of a KEY_NOT_FOUND error
- /// 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,
- mut key_accessor: F,
- ) -> Result<Option<T>>
- where
- F: FnMut() -> Result<Option<T>>,
- {
- 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_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 migration try again.
- key_accessor()
- } else {
- Ok(None)
- }
- }
-
- /// 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("LegacyMigrator::bulk_delete_uid", 500);
-
- let uid = match (domain, nspace) {
- (Domain::APP, nspace) => nspace as u32,
- (Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
- // Nothing to do.
- _ => return Ok(()),
- };
-
- 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, 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("LegacyMigrator::bulk_delete_user", 500);
-
- let result = self.do_serialized(move |migrator_state| {
- migrator_state
- .bulk_delete(BulkDeleteRequest::User(user_id), keep_non_super_encrypted_keys)
- });
-
- result.unwrap_or(Ok(()))
- }
-
- /// 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 |migrator_state| migrator_state.has_super_key(user_id));
- result.unwrap_or(Ok(false))
- }
-}
-
-impl LegacyMigratorState {
- fn get_km_uuid(&self, is_strongbox: bool) -> Result<Uuid> {
- let sec_level = if is_strongbox {
- SecurityLevel::STRONGBOX
- } else {
- SecurityLevel::TRUSTED_ENVIRONMENT
- };
-
- self.sec_level_to_km_uuid.get(&sec_level).copied().ok_or_else(|| {
- anyhow::anyhow!(Error::sys()).context("In get_km_uuid: No KM instance for blob.")
- })
- }
-
- fn list_uid(&mut self, uid: u32) -> Result<Vec<String>> {
- self.legacy_loader
- .list_keystore_entries_for_uid(uid)
- .context("In list_uid: Trying to list legacy entries.")
- }
-
- /// This is a key migration request that must run in the migrator thread. This must
- /// be passed to do_serialized.
- 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(concat!(
- "In check_and_migrate: Must be Some because ",
- "our caller must not have called us otherwise."
- ))
- })?;
-
- if self.recently_migrated.contains(&RecentMigration::new(uid, alias.clone())) {
- return Ok(());
- }
-
- if key.domain == Domain::APP {
- key.nspace = uid as i64;
- }
-
- // 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, 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 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()));
- blob_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
- blob_metadata
- .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
- (LegacyBlob::Vec(data), blob_metadata)
- }
- BlobValue::Decrypted(data) => (LegacyBlob::ZVec(data), BlobMetaData::new()),
- _ => {
- return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
- .context("In check_and_migrate: Legacy key has unexpected type.")
- }
- };
-
- let km_uuid = self
- .get_km_uuid(is_strongbox)
- .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_migrate: Trying to make creation time.")?;
- metadata.add(KeyMetaEntry::CreationDate(creation_date));
-
- // Store legacy key in the database.
- self.db
- .store_new_key(
- &key,
- KeyType::Client,
- ¶ms,
- &(&blob, &blob_metadata),
- &CertificateInfo::new(user_cert, ca_cert),
- &metadata,
- &km_uuid,
- )
- .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_migrate: Failed to insert new certificate.")?;
- Ok(())
- } else {
- Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
- .context("In check_and_migrate: Legacy key not found.")
- }
- }
- };
-
- match result {
- Ok(()) => {
- // 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_migrate: Trying to remove migrated key.")?;
- Ok(())
- }
- Err(e) => Err(e),
- }
- }
-
- 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_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_migrate_super_key: Trying to encrypt super key.")?;
-
- self.db
- .store_super_key(
- user_id,
- &USER_SUPER_KEY,
- &blob,
- &blob_metadata,
- &KeyMetaData::new(),
- )
- .context(concat!(
- "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_migrated_super_key.insert(user_id);
- Ok(())
- } else {
- Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
- .context("In check_and_migrate_super_key: No key found do migrate.")
- }
- }
-
- /// 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,
- keep_non_super_encrypted_keys: bool,
- ) -> Result<()> {
- let (aliases, user_id) = match bulk_delete_request {
- BulkDeleteRequest::Uid(uid) => (
- self.legacy_loader
- .list_keystore_entries_for_uid(uid)
- .context("In bulk_delete: Trying to get aliases for uid.")
- .map(|aliases| {
- let mut h = HashMap::<u32, HashSet<String>>::new();
- h.insert(uid, aliases.into_iter().collect());
- h
- })?,
- uid_to_android_user(uid),
- ),
- BulkDeleteRequest::User(user_id) => (
- self.legacy_loader
- .list_keystore_entries_for_user(user_id)
- .context("In bulk_delete: Trying to get aliases for user_id.")?,
- user_id,
- ),
- };
-
- let super_key_id = self
- .db
- .load_super_key(&USER_SUPER_KEY, user_id)
- .context("In bulk_delete: Failed to load super key")?
- .map(|(_, entry)| entry.id());
-
- for (uid, alias) in aliases
- .into_iter()
- .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)
- .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)| {
- (
- params.iter().any(|kp| {
- KeyParameterValue::RollbackResistance == *kp.key_parameter_value()
- }),
- blob.is_encrypted(),
- )
- })
- .unwrap_or((false, false));
-
- if keep_non_super_encrypted_keys && !is_super_encrypted {
- continue;
- }
-
- if need_gc {
- let mark_deleted = match km_blob_params
- .map(|(blob, _)| (blob.is_strongbox(), blob.take_value()))
- {
- Some((is_strongbox, BlobValue::Encrypted { iv, tag, data })) => {
- let mut blob_metadata = BlobMetaData::new();
- if let (Ok(km_uuid), Some(super_key_id)) =
- (self.get_km_uuid(is_strongbox), super_key_id)
- {
- blob_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
- blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
- blob_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
- blob_metadata
- .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
- Some((LegacyBlob::Vec(data), blob_metadata))
- } else {
- // Oh well - we tried our best, but if we cannot determine which
- // KeyMint instance we have to send this blob to, we cannot
- // do more than delete the key from the file system.
- // And if we don't know which key wraps this key we cannot
- // unwrap it for KeyMint either.
- None
- }
- }
- Some((_, BlobValue::Decrypted(data))) => {
- Some((LegacyBlob::ZVec(data), BlobMetaData::new()))
- }
- _ => None,
- };
-
- if let Some((blob, blob_metadata)) = mark_deleted {
- self.db.set_deleted_blob(&blob, &blob_metadata).context(concat!(
- "In bulk_delete: Trying to insert deleted ",
- "blob into the database for garbage collection."
- ))?;
- }
- }
-
- self.legacy_loader
- .remove_keystore_entry(uid, &alias)
- .context("In bulk_delete: Trying to remove migrated key.")?;
- }
- Ok(())
- }
-
- fn has_super_key(&mut self, user_id: u32) -> Result<bool> {
- 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) {
- LegacyMigrator::STATE_EMPTY
- } else {
- LegacyMigrator::STATE_READY
- }
- }
-}
-
-enum LegacyBlob {
- Vec(Vec<u8>),
- ZVec(ZVec),
-}
-
-impl Deref for LegacyBlob {
- type Target = [u8];
-
- fn deref(&self) -> &Self::Target {
- match self {
- Self::Vec(v) => v,
- Self::ZVec(v) => v,
- }
- }
-}
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 8b629b1..0b830be 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -28,8 +28,9 @@
pub mod id_rotation;
/// Internal Representation of Key Parameter and convenience functions.
pub mod key_parameter;
+pub mod ks_err;
pub mod legacy_blob;
-pub mod legacy_migrator;
+pub mod legacy_importer;
pub mod maintenance;
pub mod metrics;
pub mod metrics_store;
@@ -40,12 +41,12 @@
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 7ce9042..5efb798 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -19,10 +19,13 @@
use crate::error::map_or_log_err;
use crate::error::Error;
use crate::globals::get_keymint_device;
-use crate::globals::{DB, LEGACY_MIGRATOR, SUPER_KEY};
+use crate::globals::{DB, LEGACY_IMPORTER, SUPER_KEY};
+use crate::ks_err;
use crate::permission::{KeyPerm, KeystorePerm};
-use crate::super_key::UserState;
-use crate::utils::{check_key_permission, check_keystore_permission, watchdog as wd};
+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,
};
@@ -67,34 +70,33 @@
}
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::change_password())
- .context("In on_user_password_changed.")?;
+ // Check permission. Function should return if this failed. Therefore having '?' at the end
+ // is very important.
+ check_keystore_permission(KeystorePerm::ChangePassword).context(ks_err!())?;
+
+ let mut skm = SUPER_KEY.write().unwrap();
if let Some(pw) = password.as_ref() {
DB.with(|db| {
- SUPER_KEY.unlock_screen_lock_bound_key(&mut db.borrow_mut(), user_id as u32, pw)
+ skm.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")?;
+ .context(ks_err!("unlock_screen_lock_bound_key failed"))?;
}
match DB
.with(|db| {
- UserState::get_with_password_changed(
+ skm.reset_or_init_user_and_get_user_state(
&mut db.borrow_mut(),
- &LEGACY_MIGRATOR,
- &SUPER_KEY,
+ &LEGACY_IMPORTER,
user_id as u32,
password.as_ref(),
)
})
- .context("In on_user_password_changed.")?
+ .context(ks_err!())?
{
UserState::LskfLocked => {
// Error - password can not be changed when the device is locked
- Err(Error::Rc(ResponseCode::LOCKED))
- .context("In on_user_password_changed. Device is locked.")
+ Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked."))
}
_ => {
// LskfLocked is the only error case for password change
@@ -106,45 +108,49 @@
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::change_user()).context("In add_or_remove_user.")?;
+ check_keystore_permission(KeystorePerm::ChangeUser).context(ks_err!())?;
+
DB.with(|db| {
- UserState::reset_user(
+ SUPER_KEY.write().unwrap().reset_user(
&mut db.borrow_mut(),
- &SUPER_KEY,
- &LEGACY_MIGRATOR,
+ &LEGACY_IMPORTER,
user_id as u32,
false,
)
})
- .context("In add_or_remove_user: Trying to delete keys from db.")?;
+ .context(ks_err!("Trying to delete keys from db."))?;
self.delete_listener
.delete_user(user_id as u32)
- .context("In add_or_remove_user: While invoking the delete listener.")
+ .context(ks_err!("While invoking the delete listener."))
}
fn clear_namespace(&self, domain: Domain, nspace: i64) -> Result<()> {
// Permission check. Must return on error. Do not touch the '?'.
- check_keystore_permission(KeystorePerm::clear_uid()).context("In clear_namespace.")?;
+ check_keystore_permission(KeystorePerm::ClearUID).context("In clear_namespace.")?;
- LEGACY_MIGRATOR
+ LEGACY_IMPORTER
.bulk_delete_uid(domain, nspace)
- .context("In clear_namespace: Trying to delete legacy keys.")?;
+ .context(ks_err!("Trying to delete legacy keys."))?;
DB.with(|db| db.borrow_mut().unbind_keys_for_namespace(domain, nspace))
- .context("In clear_namespace: Trying to delete keys from db.")?;
+ .context(ks_err!("Trying to delete keys from db."))?;
self.delete_listener
.delete_namespace(domain, nspace)
- .context("In clear_namespace: While invoking the delete listener.")
+ .context(ks_err!("While invoking the delete listener."))
}
fn get_state(user_id: i32) -> Result<AidlUserState> {
// Check permission. Function should return if this failed. Therefore having '?' at the end
// is very important.
- check_keystore_permission(KeystorePerm::get_state()).context("In get_state.")?;
+ check_keystore_permission(KeystorePerm::GetState).context("In get_state.")?;
let state = DB
.with(|db| {
- UserState::get(&mut db.borrow_mut(), &LEGACY_MIGRATOR, &SUPER_KEY, user_id as u32)
+ SUPER_KEY.read().unwrap().get_user_state(
+ &mut db.borrow_mut(),
+ &LEGACY_IMPORTER,
+ user_id as u32,
+ )
})
- .context("In get_state. Trying to get UserState.")?;
+ .context(ks_err!("Trying to get UserState."))?;
match state {
UserState::Uninitialized => Ok(AidlUserState::UNINITIALIZED),
@@ -155,21 +161,21 @@
fn call_with_watchdog<F>(sec_level: SecurityLevel, name: &'static str, op: &F) -> Result<()>
where
- F: Fn(Strong<dyn IKeyMintDevice>) -> binder::public_api::Result<()>,
+ F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>,
{
- let (km_dev, _, _) = get_keymint_device(&sec_level)
- .context("In call_with_watchdog: getting keymint device")?;
+ let (km_dev, _, _) =
+ get_keymint_device(&sec_level).context(ks_err!("getting keymint device"))?;
let _wp = wd::watch_millis_with("In call_with_watchdog", 500, move || {
format!("Seclevel: {:?} Op: {}", sec_level, name)
});
- map_km_error(op(km_dev)).with_context(|| format!("In keymint device: calling {}", name))?;
+ map_km_error(op(km_dev)).with_context(|| ks_err!("calling {}", name))?;
Ok(())
}
fn call_on_all_security_levels<F>(name: &'static str, op: F) -> Result<()>
where
- F: Fn(Strong<dyn IKeyMintDevice>) -> binder::public_api::Result<()>,
+ F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>,
{
let sec_levels = [
(SecurityLevel::TRUSTED_ENVIRONMENT, "TRUSTED_ENVIRONMENT"),
@@ -195,11 +201,13 @@
}
fn early_boot_ended() -> Result<()> {
- check_keystore_permission(KeystorePerm::early_boot_ended())
- .context("In early_boot_ended. Checking permission")?;
+ check_keystore_permission(KeystorePerm::EarlyBootEnded)
+ .context(ks_err!("Checking permission"))?;
log::info!("In early_boot_ended.");
- if let Err(e) = DB.with(|db| SUPER_KEY.set_up_boot_level_cache(&mut db.borrow_mut())) {
+ if let Err(e) =
+ DB.with(|db| SuperKeyManager::set_up_boot_level_cache(&SUPER_KEY, &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())
@@ -207,54 +215,63 @@
fn on_device_off_body() -> Result<()> {
// Security critical permission check. This statement must return on fail.
- check_keystore_permission(KeystorePerm::report_off_body())
- .context("In on_device_off_body.")?;
+ check_keystore_permission(KeystorePerm::ReportOffBody).context(ks_err!())?;
DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now()));
Ok(())
}
fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
- let caller_uid = ThreadState::get_calling_uid();
+ 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(ks_err!("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(ks_err!("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);
DB.with(|db| {
- 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)
- })
+ 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(ks_err!("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)
+ })
+ }
})
}
fn delete_all_keys() -> Result<()> {
// Security critical permission check. This statement must return on fail.
- check_keystore_permission(KeystorePerm::delete_all_keys())
- .context("In delete_all_keys. Checking permission")?;
+ check_keystore_permission(KeystorePerm::DeleteAllKeys)
+ .context(ks_err!("Checking permission"))?;
log::info!("In delete_all_keys.");
Maintenance::call_on_all_security_levels("deleteAllKeys", |dev| dev.deleteAllKeys())
diff --git a/keystore2/src/metrics.rs b/keystore2/src/metrics.rs
index 42295b7..cd1cd75 100644
--- a/keystore2/src/metrics.rs
+++ b/keystore2/src/metrics.rs
@@ -15,6 +15,7 @@
//! This module implements the IKeystoreMetrics AIDL interface, which exposes the API method for the
//! proxy in the system server to pull the aggregated metrics in keystore.
use crate::error::map_or_log_err;
+use crate::ks_err;
use crate::metrics_store::METRICS_STORE;
use crate::permission::KeystorePerm;
use crate::utils::{check_keystore_permission, watchdog as wd};
@@ -41,7 +42,7 @@
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::pull_metrics()).context("In pull_metrics.")?;
+ check_keystore_permission(KeystorePerm::PullMetrics).context(ks_err!())?;
METRICS_STORE.get_atoms(atom_id)
}
}
diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs
index 741d65e..6043612 100644
--- a/keystore2/src/metrics_store.rs
+++ b/keystore2/src/metrics_store.rs
@@ -17,9 +17,10 @@
//! stores them in an in-memory store.
//! 2. Returns the collected metrics when requested by the statsd proxy.
-use crate::error::get_error_code;
+use crate::error::{get_error_code, Error};
use crate::globals::DB;
use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
+use crate::ks_err;
use crate::operation::Outcome;
use crate::remote_provisioning::get_pool_status;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
@@ -44,6 +45,7 @@
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 lazy_static::lazy_static;
use rustutils::system_properties::PropertyWatcherError;
@@ -287,6 +289,7 @@
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,
}
}
@@ -560,10 +563,14 @@
fn pull_attestation_pool_stats() -> Result<Vec<KeystoreAtom>> {
let mut atoms = Vec::<KeystoreAtom>::new();
for sec_level in &[SecurityLevel::TRUSTED_ENVIRONMENT, SecurityLevel::STRONGBOX] {
+ // set the expired_by date to be three days from now
let expired_by = SystemTime::now()
+ .checked_add(Duration::from_secs(60 * 60 * 24 * 3))
+ .ok_or(Error::Rc(ResponseCode::SYSTEM_ERROR))
+ .context(ks_err!("Failed to compute expired by system time."))?
.duration_since(UNIX_EPOCH)
- .unwrap_or_else(|_| Duration::new(0, 0))
- .as_secs() as i64;
+ .context(ks_err!("Failed to compute expired by duration."))?
+ .as_millis() as i64;
let result = get_pool_status(expired_by, *sec_level);
@@ -593,8 +600,11 @@
}
/// Log error events related to Remote Key Provisioning (RKP).
-pub fn log_rkp_error_stats(rkp_error: MetricsRkpError) {
- let rkp_error_stats = KeystoreAtomPayload::RkpErrorStats(RkpErrorStats { rkpError: rkp_error });
+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),
+ });
METRICS_STORE.insert_atom(AtomID::RKP_ERROR_STATS, rkp_error_stats);
}
@@ -642,7 +652,8 @@
/// 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(ks_err!("Failed read property."))?
+ .context(ks_err!("Property not set."))?
.parse::<i32>()
.map_err(std::convert::Into::into)
}
diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs
index 7e08f4e..2034a8a 100644
--- a/keystore2/src/operation.rs
+++ b/keystore2/src/operation.rs
@@ -127,6 +127,7 @@
use crate::enforcements::AuthInfo;
use crate::error::{map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, ResponseCode};
+use crate::ks_err;
use crate::metrics_store::log_key_operation_event_stats;
use crate::utils::watchdog as wd;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
@@ -320,10 +321,8 @@
let guard = self.outcome.lock().expect("In check_active.");
match *guard {
Outcome::Unknown => Ok(guard),
- _ => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)).context(format!(
- "In check_active: Call on finalized operation with outcome: {:?}.",
- *guard
- )),
+ _ => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE))
+ .context(ks_err!("Call on finalized operation with outcome: {:?}.", *guard)),
}
}
@@ -358,13 +357,13 @@
.lock()
.unwrap()
.before_update()
- .context("In update_aad: Trying to get auth tokens.")?;
+ .context(ks_err!("Trying to get auth tokens."))?;
- self.update_outcome(&mut *outcome, {
+ 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()))
})
- .context("In update_aad: KeyMint::update failed.")?;
+ .context(ks_err!("Update failed."))?;
Ok(())
}
@@ -381,14 +380,14 @@
.lock()
.unwrap()
.before_update()
- .context("In update: Trying to get auth tokens.")?;
+ .context(ks_err!("Trying to get auth tokens."))?;
let output = self
- .update_outcome(&mut *outcome, {
+ .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()))
})
- .context("In update: KeyMint::update failed.")?;
+ .context(ks_err!("Update failed."))?;
if output.is_empty() {
Ok(None)
@@ -411,10 +410,10 @@
.lock()
.unwrap()
.before_finish()
- .context("In finish: Trying to get auth tokens.")?;
+ .context(ks_err!("Trying to get auth tokens."))?;
let output = self
- .update_outcome(&mut *outcome, {
+ .update_outcome(&mut outcome, {
let _wp = wd::watch_millis("Operation::finish: calling finish", 500);
map_km_error(self.km_op.finish(
input,
@@ -424,7 +423,7 @@
confirmation_token.as_deref(),
))
})
- .context("In finish: KeyMint::finish failed.")?;
+ .context(ks_err!("Finish failed."))?;
self.auth_info.lock().unwrap().after_finish().context("In finish.")?;
@@ -447,7 +446,7 @@
{
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(self.km_op.abort()).context(ks_err!("KeyMint::abort failed."))
}
}
}
@@ -493,7 +492,7 @@
/// owner uid and returns a new Operation wrapped in a `std::sync::Arc`.
pub fn create_operation(
&self,
- km_op: binder::public_api::Strong<dyn IKeyMintOperation>,
+ km_op: binder::Strong<dyn IKeyMintOperation>,
owner: u32,
auth_info: AuthInfo,
forced: bool,
@@ -771,9 +770,7 @@
/// 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::public_api::Strong<dyn IKeystoreOperation> {
+ pub fn new_native_binder(operation: Arc<Operation>) -> binder::Strong<dyn IKeystoreOperation> {
BnKeystoreOperation::new_binder(
Self { operation: Mutex::new(Some(operation)) },
BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() },
@@ -792,7 +789,7 @@
Ok(mut mutex_guard) => {
let result = match &*mutex_guard {
Some(op) => {
- let result = f(&*op);
+ let result = f(op);
// Any error here means we can discard the operation.
if result.is_err() {
delete_op = true;
@@ -800,7 +797,7 @@
result
}
None => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE))
- .context("In KeystoreOperation::with_locked_operation"),
+ .context(ks_err!("KeystoreOperation::with_locked_operation")),
};
if delete_op {
@@ -813,7 +810,7 @@
result
}
Err(_) => Err(Error::Rc(ResponseCode::OPERATION_BUSY))
- .context("In KeystoreOperation::with_locked_operation"),
+ .context(ks_err!("KeystoreOperation::with_locked_operation")),
}
}
}
@@ -821,22 +818,22 @@
impl binder::Interface for KeystoreOperation {}
impl IKeystoreOperation for KeystoreOperation {
- fn updateAad(&self, aad_input: &[u8]) -> binder::public_api::Result<()> {
+ fn updateAad(&self, aad_input: &[u8]) -> binder::Result<()> {
let _wp = wd::watch_millis("IKeystoreOperation::updateAad", 500);
map_or_log_err(
self.with_locked_operation(
- |op| op.update_aad(aad_input).context("In KeystoreOperation::updateAad"),
+ |op| op.update_aad(aad_input).context(ks_err!("KeystoreOperation::updateAad")),
false,
),
Ok,
)
}
- fn update(&self, input: &[u8]) -> binder::public_api::Result<Option<Vec<u8>>> {
+ fn update(&self, input: &[u8]) -> binder::Result<Option<Vec<u8>>> {
let _wp = wd::watch_millis("IKeystoreOperation::update", 500);
map_or_log_err(
self.with_locked_operation(
- |op| op.update(input).context("In KeystoreOperation::update"),
+ |op| op.update(input).context(ks_err!("KeystoreOperation::update")),
false,
),
Ok,
@@ -846,22 +843,22 @@
&self,
input: Option<&[u8]>,
signature: Option<&[u8]>,
- ) -> binder::public_api::Result<Option<Vec<u8>>> {
+ ) -> binder::Result<Option<Vec<u8>>> {
let _wp = wd::watch_millis("IKeystoreOperation::finish", 500);
map_or_log_err(
self.with_locked_operation(
- |op| op.finish(input, signature).context("In KeystoreOperation::finish"),
+ |op| op.finish(input, signature).context(ks_err!("KeystoreOperation::finish")),
true,
),
Ok,
)
}
- fn abort(&self) -> binder::public_api::Result<()> {
+ fn abort(&self) -> binder::Result<()> {
let _wp = wd::watch_millis("IKeystoreOperation::abort", 500);
map_err_with(
self.with_locked_operation(
- |op| op.abort(Outcome::Abort).context("In KeystoreOperation::abort"),
+ |op| op.abort(Outcome::Abort).context(ks_err!("KeystoreOperation::abort")),
true,
),
|e| {
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index 4392acf..d9bdf79 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -18,23 +18,20 @@
//! 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 crate::error::ResponseCode;
+use crate::ks_err;
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;
@@ -52,273 +49,109 @@
KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string())
}
-/// ## 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!(
+implement_class!(
/// 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. 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;
+ /// the SELinux permissions.
+ #[repr(i32)]
+ #[selinux(class_name = keystore2_key)]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ 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,
+ /// Does nothing, and is not checked. For use of device identifiers,
+ /// the caller must hold the READ_PRIVILEGED_PHONE_STATE Android
+ /// permission.
+ #[selinux(name = use_dev_id)]
+ UseDevId = KeyPermission::USE_DEV_ID.0,
}
);
-/// 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!(
+implement_class!(
/// KeystorePerm provides a convenient abstraction from the SELinux class `keystore2`.
/// Using the implement_permission macro we get the same features as `KeyPerm`.
- #[derive(Clone, Copy, Debug, PartialEq)]
- KeystorePerm with default (None = 0, none) {
+ #[selinux(class_name = keystore2)]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ pub enum KeystorePerm {
/// Checked when a new auth token is installed.
- AddAuth = 1, selinux name: add_auth;
+ #[selinux(name = add_auth)]
+ AddAuth,
/// Checked when an app is uninstalled or wiped.
- ClearNs = 2, selinux name: clear_ns;
+ #[selinux(name = clear_ns)]
+ ClearNs,
/// Checked when the user state is queried from Keystore 2.0.
- GetState = 4, selinux name: get_state;
+ #[selinux(name = get_state)]
+ GetState,
/// Checked when Keystore 2.0 is asked to list a namespace that the caller
/// does not have the get_info permission for.
- List = 8, selinux name: list;
+ #[selinux(name = list)]
+ List,
/// Checked when Keystore 2.0 gets locked.
- Lock = 0x10, selinux name: lock;
+ #[selinux(name = lock)]
+ Lock,
/// Checked when Keystore 2.0 shall be reset.
- Reset = 0x20, selinux name: reset;
+ #[selinux(name = reset)]
+ Reset,
/// Checked when Keystore 2.0 shall be unlocked.
- Unlock = 0x40, selinux name: unlock;
+ #[selinux(name = unlock)]
+ Unlock,
/// Checked when user is added or removed.
- ChangeUser = 0x80, selinux name: change_user;
+ #[selinux(name = change_user)]
+ ChangeUser,
/// Checked when password of the user is changed.
- ChangePassword = 0x100, selinux name: change_password;
+ #[selinux(name = change_password)]
+ ChangePassword,
/// Checked when a UID is cleared.
- ClearUID = 0x200, selinux name: clear_uid;
+ #[selinux(name = clear_uid)]
+ ClearUID,
/// Checked when Credstore calls IKeystoreAuthorization to obtain auth tokens.
- GetAuthToken = 0x400, selinux name: get_auth_token;
+ #[selinux(name = get_auth_token)]
+ GetAuthToken,
/// Checked when earlyBootEnded() is called.
- EarlyBootEnded = 0x800, selinux name: early_boot_ended;
+ #[selinux(name = early_boot_ended)]
+ EarlyBootEnded,
/// Checked when IKeystoreMaintenance::onDeviceOffBody is called.
- ReportOffBody = 0x1000, selinux name: report_off_body;
- /// Checked when IkeystoreMetrics::pullMetris is called.
- PullMetrics = 0x2000, selinux name: pull_metrics;
+ #[selinux(name = report_off_body)]
+ ReportOffBody,
+ /// Checked when IkeystoreMetrics::pullMetrics is called.
+ #[selinux(name = pull_metrics)]
+ PullMetrics,
/// Checked when IKeystoreMaintenance::deleteAllKeys is called.
- DeleteAllKeys = 0x4000, selinux name: delete_all_keys;
+ #[selinux(name = delete_all_keys)]
+ DeleteAllKeys,
+ /// Checked on calls to IRemotelyProvisionedKeyPool::getAttestationKey
+ #[selinux(name = get_attestation_key)]
+ GetAttestationKey,
}
);
@@ -332,17 +165,17 @@
///
/// ## Example
/// ```
-/// let perms1 = key_perm_set![KeyPerm::use_(), KeyPerm::manage_blob(), KeyPerm::grant()];
-/// let perms2 = key_perm_set![KeyPerm::use_(), KeyPerm::manage_blob()];
+/// let perms1 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob, KeyPerm::Grant];
+/// let perms2 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob];
///
/// 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::manage_blob()), i.next());
-/// assert_eq(Some(KeyPerm::grant()), i.next());
-/// assert_eq(Some(KeyPerm::use_()), i.next());
+/// assert_eq(Some(KeyPerm::ManageBlob), 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)]
@@ -373,7 +206,7 @@
let p = self.vec.0 & (1 << self.pos);
self.pos += 1;
if p != 0 {
- return Some(KeyPerm::from(KeyPermission(p)));
+ return Some(KeyPerm::from(p));
}
}
}
@@ -382,7 +215,7 @@
impl From<KeyPerm> for KeyPermSet {
fn from(p: KeyPerm) -> Self {
- Self((p.0).0 as i32)
+ Self(p as i32)
}
}
@@ -417,7 +250,7 @@
macro_rules! key_perm_set {
() => { KeyPermSet(0) };
($head:expr $(, $tail:expr)* $(,)?) => {
- KeyPermSet(($head.0).0 $(| ($tail.0).0)*)
+ KeyPermSet($head as i32 $(| $tail as i32)*)
};
}
@@ -430,14 +263,14 @@
}
}
-/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` may access
+/// Uses `selinux::check_permission` 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_access(caller_ctx, &target_context, "keystore2", perm.to_selinux())
+ selinux::check_permission(caller_ctx, &target_context, perm)
}
-/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` has
+/// Uses `selinux::check_permission` 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`.
///
@@ -462,27 +295,24 @@
_ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)),
};
- selinux::check_access(caller_ctx, &target_context, "keystore2_key", "grant")
+ selinux::check_permission(caller_ctx, &target_context, KeyPerm::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_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
- ))?
+ selinux::check_permission(caller_ctx, &target_context, p).context(ks_err!(
+ "check_permission failed. \
+ The caller may have tried to grant a permission that they don't possess. {:?}",
+ p
+ ))?
}
Ok(())
}
-/// Uses `selinux::check_access` to check if the given caller context `caller_cxt`
+/// Uses `selinux::check_permission` 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`.
///
@@ -492,7 +322,7 @@
/// 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_access. Instead the `access_vector`
+/// * `Domain::GRANT` Does not use selinux::check_permission. Instead the `access_vector`
/// parameter is queried for permission, which must be supplied in this case.
///
/// ## Return values.
@@ -528,21 +358,21 @@
return Err(selinux::Error::perm())
.context("Trying to access key without ownership.");
}
- getcon().context("check_key_permission: getcon failed.")?
+ getcon().context(ks_err!("getcon failed."))?
}
Domain::SELINUX => lookup_keystore2_key_context(key.nspace)
- .context("check_key_permission: Domain::SELINUX: Failed to lookup namespace.")?,
+ .context(ks_err!("Domain::SELINUX: Failed to lookup namespace."))?,
Domain::GRANT => {
match access_vector {
Some(_) => {
return Err(selinux::Error::perm())
- .context(format!("\"{}\" not granted", perm.to_selinux()));
+ .context(format!("\"{}\" not granted", perm.name()));
}
None => {
// If DOMAIN_GRANT was selected an access vector must be supplied.
- return Err(KsError::sys()).context(
+ return Err(KsError::sys()).context(ks_err!(
"Cannot check permission for Domain::GRANT without access vector.",
- );
+ ));
}
}
}
@@ -550,29 +380,25 @@
// We should never be called with `Domain::KEY_ID. The database
// lookup should have converted this into one of `Domain::APP`
// or `Domain::SELINUX`.
- return Err(KsError::sys()).context("Cannot check permission for Domain::KEY_ID.");
+ return Err(KsError::sys())
+ .context(ks_err!("Cannot check permission for Domain::KEY_ID.",));
}
Domain::BLOB => {
let tctx = lookup_keystore2_key_context(key.nspace)
- .context("Domain::BLOB: Failed to lookup namespace.")?;
+ .context(ks_err!("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_access(
- caller_ctx,
- &tctx,
- "keystore2_key",
- KeyPerm::manage_blob().to_selinux(),
- )?;
+ selinux::check_permission(caller_ctx, &tctx, KeyPerm::ManageBlob)?;
tctx
}
_ => {
- return Err(KsError::sys())
+ return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
.context(format!("Unknown domain value: \"{:?}\".", key.domain))
}
};
- selinux::check_access(caller_ctx, &target_context, "keystore2_key", perm.to_selinux())
+ selinux::check_permission(caller_ctx, &target_context, perm)
}
#[cfg(test)]
@@ -583,49 +409,49 @@
use keystore2_selinux::*;
const ALL_PERMS: KeyPermSet = key_perm_set![
- 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(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Grant,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ KeyPerm::ConvertStorageKeyToEphemeral,
];
const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![
- KeyPerm::delete(),
- KeyPerm::use_dev_id(),
- // No KeyPerm::grant()
- KeyPerm::get_info(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ // No KeyPerm::Grant
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![
- 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(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ // No KeyPerm::Grant
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ KeyPerm::ConvertStorageKeyToEphemeral,
];
const UNPRIV_PERMS: KeyPermSet = key_perm_set![
- KeyPerm::delete(),
- KeyPerm::get_info(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::Delete,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
/// The su_key namespace as defined in su.te and keystore_key_contexts of the
@@ -672,28 +498,26 @@
#[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::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());
+ 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());
let shell_ctx = Context::new("u:r:shell:s0")?;
- 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()));
+ 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));
Ok(())
}
@@ -708,7 +532,7 @@
// 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.
@@ -728,7 +552,7 @@
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));
@@ -743,7 +567,7 @@
assert_perm_failed!(check_key_permission(
0,
&selinux::Context::new("ignored").unwrap(),
- KeyPerm::grant(),
+ KeyPerm::Grant,
&key,
&Some(UNPRIV_PERMS)
));
@@ -751,7 +575,7 @@
check_key_permission(
0,
&selinux::Context::new("ignored").unwrap(),
- KeyPerm::use_(),
+ KeyPerm::Use,
&key,
&Some(ALL_PERMS),
)
@@ -765,61 +589,31 @@
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::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, &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::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
- ));
+ 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));
// 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
));
@@ -827,18 +621,18 @@
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::get_info()])
+ &Some(key_perm_set![KeyPerm::GetInfo])
));
Ok(())
@@ -854,42 +648,24 @@
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::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::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::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());
+ 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());
} else {
- 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
- ));
+ 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));
}
Ok(())
}
@@ -905,9 +681,9 @@
};
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(())
}
}
@@ -921,7 +697,7 @@
check_key_permission(
0,
&selinux::Context::new("ignored").unwrap(),
- KeyPerm::use_(),
+ KeyPerm::Use,
&key,
&None
)
@@ -936,45 +712,45 @@
#[test]
fn key_perm_set_all_test() {
let v = key_perm_set![
- 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.
+ 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.
];
let mut i = v.into_iter();
- 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!(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!(None, i.next());
}
#[test]
fn key_perm_set_sparse_test() {
let v = key_perm_set![
- 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.
+ KeyPerm::ManageBlob,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ 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().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!(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!(None, i.next());
}
#[test]
@@ -986,23 +762,23 @@
#[test]
fn key_perm_set_include_subset_test() {
let v1 = key_perm_set![
- 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::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Grant,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
let v2 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
assert!(v1.includes(v2));
assert!(!v2.includes(v1));
@@ -1010,18 +786,18 @@
#[test]
fn key_perm_set_include_equal_test() {
let v1 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
let v2 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
assert!(v1.includes(v2));
assert!(v2.includes(v1));
@@ -1029,33 +805,29 @@
#[test]
fn key_perm_set_include_overlap_test() {
let v1 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::grant(), // only in v1
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Grant, // only in v1
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
let v2 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::req_forced_op(), // only in v2
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::ReqForcedOp, // 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::manage_blob(), KeyPerm::delete(), KeyPerm::grant(),];
- let v2 = key_perm_set![
- KeyPerm::req_forced_op(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
- ];
+ let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,];
+ let v2 =
+ key_perm_set![KeyPerm::ReqForcedOp, 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 991535f..fa9872a 100644
--- a/keystore2/src/raw_device.rs
+++ b/keystore2/src/raw_device.rs
@@ -16,11 +16,13 @@
use crate::{
database::{
- BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits,
- KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB, SubComponentType, Uuid,
+ BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry,
+ KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB,
+ SubComponentType, Uuid,
},
error::{map_km_error, Error, ErrorCode},
globals::get_keymint_device,
+ ks_err,
super_key::KeyBlob,
utils::{key_characteristics_to_internal, watchdog as wd, AID_KEYSTORE},
};
@@ -59,11 +61,15 @@
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;
+ /// Version number of KeyMintDevice@V3
+ pub const KEY_MINT_V3: i32 = 300;
/// 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)
- .context("In KeyMintDevice::get: get_keymint_device failed")?;
+ let (km_dev, hw_info, km_uuid) =
+ get_keymint_device(&security_level).context(ks_err!("get_keymint_device failed"))?;
Ok(KeyMintDevice {
km_dev,
@@ -107,12 +113,11 @@
where
F: FnOnce(&Strong<dyn IKeyMintDevice>) -> Result<KeyCreationResult, binder::Status>,
{
- let creation_result = map_km_error(creator(&self.km_dev))
- .context("In create_and_store_key: creator failed")?;
+ let creation_result =
+ map_km_error(creator(&self.km_dev)).context(ks_err!("creator failed"))?;
let key_parameters = key_characteristics_to_internal(creation_result.keyCharacteristics);
- let creation_date =
- DateTime::now().context("In create_and_store_key: DateTime::now() failed")?;
+ let creation_date = DateTime::now().context(ks_err!("DateTime::now() failed"))?;
let mut key_metadata = KeyMetaData::new();
key_metadata.add(KeyMetaEntry::CreationDate(creation_date));
@@ -123,12 +128,12 @@
key_desc,
key_type,
&key_parameters,
- &(&creation_result.keyBlob, &blob_metadata),
+ &BlobInfo::new(&creation_result.keyBlob, &blob_metadata),
&CertificateInfo::new(None, None),
&key_metadata,
&self.km_uuid,
)
- .context("In create_and_store_key: store_new_key failed")?;
+ .context(ks_err!("store_new_key failed"))?;
Ok(())
}
@@ -149,7 +154,7 @@
key_type: KeyType,
) -> Result<(KeyIdGuard, KeyEntry)> {
db.load_key_entry(key_desc, key_type, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| Ok(()))
- .context("In lookup_from_desc: load_key_entry failed.")
+ .context(ks_err!("load_key_entry failed."))
}
/// Look up the key in the database, and return None if it is absent.
@@ -184,7 +189,7 @@
// - because it avoids holding database locks during slow
// KeyMint operations
let lookup = Self::not_found_is_none(Self::lookup_from_desc(db, key_desc, key_type))
- .context("In lookup_or_generate_key: first lookup failed")?;
+ .context(ks_err!("first lookup failed"))?;
if let Some((key_id_guard, mut key_entry)) = lookup {
// If the key is associated with a different km instance
@@ -217,7 +222,7 @@
})
},
)
- .context("In lookup_or_generate_key: calling getKeyCharacteristics")?;
+ .context(ks_err!("calling getKeyCharacteristics"))?;
if validate_characteristics(&key_characteristics) {
return Ok((key_id_guard, key_blob));
@@ -231,7 +236,7 @@
self.create_and_store_key(db, key_desc, key_type, |km_dev| {
km_dev.generateKey(params, None)
})
- .context("In lookup_or_generate_key: generate_and_store_key failed")?;
+ .context(ks_err!("generate_and_store_key failed"))?;
Self::lookup_from_desc(db, key_desc, key_type)
.and_then(|(key_id_guard, mut key_entry)| {
Ok((
@@ -240,10 +245,10 @@
.take_key_blob_info()
.ok_or(Error::Rc(ResponseCode::KEY_NOT_FOUND))
.map(|(key_blob, _)| KeyBlob::NonSensitive(key_blob))
- .context("Missing key blob info.")?,
+ .context(ks_err!("Missing key blob info."))?,
))
})
- .context("In lookup_or_generate_key: second lookup failed")
+ .context(ks_err!("second lookup failed"))
}
/// Call the passed closure; if it returns `KEY_REQUIRES_UPGRADE`, call upgradeKey, and
@@ -267,7 +272,7 @@
);
self.km_dev.upgradeKey(&key_blob, &[])
})
- .context("In upgrade_keyblob_if_required_with: Upgrade failed")?;
+ .context(ks_err!("Upgrade failed"))?;
let mut new_blob_metadata = BlobMetaData::new();
new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
@@ -278,22 +283,14 @@
Some(&upgraded_blob),
Some(&new_blob_metadata),
)
- .context(concat!(
- "In upgrade_keyblob_if_required_with: ",
- "Failed to insert upgraded blob into the database"
- ))?;
+ .context(ks_err!("Failed to insert upgraded blob into the database"))?;
Ok((
- f(&upgraded_blob).context(
- "In upgrade_keyblob_if_required_with: Closure failed after upgrade",
- )?,
+ f(&upgraded_blob).context(ks_err!("Closure failed after upgrade"))?,
KeyBlob::NonSensitive(upgraded_blob),
))
}
- result => Ok((
- result.context("In upgrade_keyblob_if_required_with: Closure failed")?,
- key_blob,
- )),
+ result => Ok((result.context(ks_err!("Closure failed"))?, key_blob)),
}
}
@@ -319,15 +316,13 @@
self.km_dev.begin(purpose, blob, operation_parameters, auth_token)
})
})
- .context("In use_key_in_one_step: Failed to begin operation.")?;
- let operation: Strong<dyn IKeyMintOperation> = begin_result
- .operation
- .ok_or_else(Error::sys)
- .context("In use_key_in_one_step: Operation missing")?;
+ .context(ks_err!("Failed to begin operation."))?;
+ let operation: Strong<dyn IKeyMintOperation> =
+ begin_result.operation.ok_or_else(Error::sys).context(ks_err!("Operation missing"))?;
map_km_error({
let _wp = wd::watch_millis("In use_key_in_one_step: calling: finish", 500);
operation.finish(Some(input), None, None, None, None)
})
- .context("In use_key_in_one_step: Failed to finish operation.")
+ .context(ks_err!("Failed to finish operation."))
}
}
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index a19462b..fec1b92 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -23,28 +23,37 @@
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate,
- DeviceInfo::DeviceInfo, IRemotelyProvisionedComponent::IRemotelyProvisionedComponent,
- KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue,
- MacedPublicKey::MacedPublicKey, ProtectedData::ProtectedData, SecurityLevel::SecurityLevel,
+ KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel,
Tag::Tag,
};
+use android_hardware_security_rkp::aidl::android::hardware::security::keymint::{
+ DeviceInfo::DeviceInfo, IRemotelyProvisionedComponent::IRemotelyProvisionedComponent,
+ MacedPublicKey::MacedPublicKey, ProtectedData::ProtectedData,
+};
use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{
AttestationPoolStatus::AttestationPoolStatus, IRemoteProvisioning::BnRemoteProvisioning,
- IRemoteProvisioning::IRemoteProvisioning, ImplInfo::ImplInfo,
+ IRemoteProvisioning::IRemoteProvisioning,
+ IRemotelyProvisionedKeyPool::BnRemotelyProvisionedKeyPool,
+ IRemotelyProvisionedKeyPool::IRemotelyProvisionedKeyPool, ImplInfo::ImplInfo,
+ RemotelyProvisionedKey::RemotelyProvisionedKey,
};
use android_security_remoteprovisioning::binder::{BinderFeatures, Strong};
use android_system_keystore2::aidl::android::system::keystore2::{
- Domain::Domain, KeyDescriptor::KeyDescriptor,
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
};
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, KeystoreDB, Uuid};
+use crate::database::{CertificateChain, KeyIdGuard, KeystoreDB, Uuid};
use crate::error::{self, map_or_log_err, map_rem_prov_error, Error};
use crate::globals::{get_keymint_device, get_remotely_provisioned_component, DB};
+use crate::ks_err;
use crate::metrics_store::log_rkp_error_stats;
-use crate::utils::watchdog as wd;
+use crate::permission::KeystorePerm;
+use crate::utils::{check_keystore_permission, watchdog as wd};
use android_security_metrics::aidl::android::security::metrics::RkpError::RkpError as MetricsRkpError;
/// Contains helper functions to check if remote provisioning is enabled on the system and, if so,
@@ -56,17 +65,43 @@
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()
{
@@ -83,70 +118,6 @@
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!(
@@ -174,7 +145,7 @@
caller_uid: u32,
params: &[KeyParameter],
db: &mut KeystoreDB,
- ) -> Result<Option<(AttestationKey, Certificate)>> {
+ ) -> Result<Option<(KeyIdGuard, AttestationKey, Certificate)>> {
if !self.is_asymmetric_key(params) || !self.check_rem_prov_enabled(db)? {
// There is no remote provisioning component for this security level on the
// device. Return None so the underlying KM instance knows to use its
@@ -182,30 +153,29 @@
// and therefore will not be attested.
Ok(None)
} else {
- match self.get_rem_prov_attest_key(key, caller_uid, db) {
+ match get_rem_prov_attest_key(key.domain, caller_uid, db, &self.km_uuid) {
Err(e) => {
- log::error!(
- concat!(
- "In get_remote_provisioning_key_and_certs: Failed to get ",
- "attestation key. {:?}"
- ),
- e
+ if self.is_rkp_only() {
+ log::error!("Error occurred: {:?}", e);
+ return Err(e);
+ }
+ log::warn!("Error occurred: {:?}", e);
+ log_rkp_error_stats(
+ MetricsRkpError::FALL_BACK_DURING_HYBRID,
+ &self.security_level,
);
- log_rkp_error_stats(MetricsRkpError::FALL_BACK_DURING_HYBRID);
Ok(None)
}
Ok(v) => match v {
- Some(cert_chain) => Ok(Some((
+ Some((guard, cert_chain)) => Ok(Some((
+ guard,
AttestationKey {
keyBlob: cert_chain.private_key.to_vec(),
attestKeyParams: vec![],
issuerSubjectName: parse_subject_from_certificate(
&cert_chain.batch_cert,
)
- .context(concat!(
- "In get_remote_provisioning_key_and_certs: Failed to ",
- "parse subject."
- ))?,
+ .context(ks_err!("Failed to parse subject."))?,
},
Certificate { encodedCertificate: cert_chain.cert_chain },
))),
@@ -226,13 +196,13 @@
fn get_dev_by_sec_level(
&self,
sec_level: &SecurityLevel,
- ) -> Result<Strong<dyn IRemotelyProvisionedComponent>> {
+ ) -> Result<&dyn IRemotelyProvisionedComponent> {
if let Some(dev) = self.device_by_sec_level.get(sec_level) {
- Ok(dev.clone())
+ Ok(dev.as_ref())
} else {
- Err(error::Error::sys()).context(concat!(
- "In get_dev_by_sec_level: Remote instance for requested security level",
- " not found."
+ Err(error::Error::sys()).context(ks_err!(
+ "Remote instance for requested security level \
+ not found.",
))
}
}
@@ -241,11 +211,11 @@
pub fn new_native_binder() -> Result<Strong<dyn IRemoteProvisioning>> {
let mut result: Self = Default::default();
let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT)
- .context("In new_native_binder: Failed to get TEE Remote Provisioner instance.")?;
+ .context(ks_err!("Failed to get TEE Remote Provisioner instance."))?;
result.curve_by_sec_level.insert(
SecurityLevel::TRUSTED_ENVIRONMENT,
dev.getHardwareInfo()
- .context("In new_native_binder: Failed to get hardware info for the TEE.")?
+ .context(ks_err!("Failed to get hardware info for the TEE."))?
.supportedEekCurve,
);
result.device_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, dev);
@@ -253,7 +223,7 @@
result.curve_by_sec_level.insert(
SecurityLevel::STRONGBOX,
dev.getHardwareInfo()
- .context("In new_native_binder: Failed to get hardware info for StrongBox.")?
+ .context(ks_err!("Failed to get hardware info for StrongBox."))?
.supportedEekCurve,
);
result.device_by_sec_level.insert(SecurityLevel::STRONGBOX, dev);
@@ -261,6 +231,28 @@
Ok(BnRemoteProvisioning::new_binder(result, BinderFeatures::default()))
}
+ fn extract_payload_from_cose_mac(data: &[u8]) -> Result<Value> {
+ let cose_mac0: Vec<Value> = serde_cbor::from_slice(data)
+ .context(ks_err!("COSE_Mac0 returned from IRPC cannot be parsed"))?;
+ if cose_mac0.len() != COSE_MAC0_LEN {
+ return Err(error::Error::sys()).context(ks_err!(
+ "COSE_Mac0 has improper length. \
+ Expected: {}, Actual: {}",
+ COSE_MAC0_LEN,
+ cose_mac0.len(),
+ ));
+ }
+ match &cose_mac0[COSE_MAC0_PAYLOAD] {
+ Value::Bytes(key) => {
+ Ok(serde_cbor::from_slice(key)
+ .context(ks_err!("COSE_Mac0 payload is malformed."))?)
+ }
+ _ => {
+ Err(error::Error::sys()).context(ks_err!("COSE_Mac0 payload is the wrong type."))?
+ }
+ }
+ }
+
/// Generates a CBOR blob which will be assembled by the calling code into a larger
/// CBOR blob intended for delivery to a provisioning serever. This blob will contain
/// `num_csr` certificate signing requests for attestation keys generated in the TEE,
@@ -290,7 +282,7 @@
.map(|key| MacedPublicKey { macedKey: key.to_vec() })
.collect())
})?;
- let mut mac = map_rem_prov_error(dev.generateCertificateRequest(
+ let mac = map_rem_prov_error(dev.generateCertificateRequest(
test_mode,
&keys_to_sign,
eek,
@@ -298,31 +290,17 @@
device_info,
protected_data,
))
- .context("In generate_csr: Failed to generate csr")?;
- // 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;
+ .context(ks_err!("Failed to generate csr"))?;
+ let mut mac_and_keys: Vec<Value> = vec![Value::from(mac)];
for maced_public_key in keys_to_sign {
- 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],
- );
- }
+ mac_and_keys.push(
+ Self::extract_payload_from_cose_mac(&maced_public_key.macedKey)
+ .context(ks_err!("Failed to get the payload from the COSE_Mac0"))?,
+ )
}
- Ok(cose_mac_0)
+ let cbor_array: Value = Value::Array(mac_and_keys);
+ serde_cbor::to_vec(&cbor_array)
+ .context(ks_err!("Failed to serialize the mac and keys array"))
}
/// Provisions a certificate chain for a key whose CSR was included in generate_csr. The
@@ -332,52 +310,104 @@
/// 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<()> {
- 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,
- )
- })
+ let (_, _, uuid) = get_keymint_device(&sec_level)?;
+ db.store_signed_attestation_certificate_chain(
+ public_key,
+ batch_cert,
+ certs, /* DER encoded certificate chain */
+ expiration_date,
+ &uuid,
+ )
+ }
+
+ fn parse_cose_mac0_for_coords(data: &[u8]) -> Result<Vec<u8>> {
+ let cose_mac0: Vec<Value> = serde_cbor::from_slice(data)
+ .context(ks_err!("COSE_Mac0 returned from IRPC cannot be parsed"))?;
+ if cose_mac0.len() != COSE_MAC0_LEN {
+ return Err(error::Error::sys()).context(ks_err!(
+ "COSE_Mac0 has improper length. \
+ Expected: {}, Actual: {}",
+ COSE_MAC0_LEN,
+ cose_mac0.len(),
+ ));
+ }
+ let cose_key: BTreeMap<Value, Value> = match &cose_mac0[COSE_MAC0_PAYLOAD] {
+ Value::Bytes(key) => {
+ serde_cbor::from_slice(key).context(ks_err!("COSE_Key is malformed."))?
+ }
+ _ => {
+ Err(error::Error::sys()).context(ks_err!("COSE_Mac0 payload is the wrong type."))?
+ }
+ };
+ if !cose_key.contains_key(&COSE_KEY_XCOORD) || !cose_key.contains_key(&COSE_KEY_YCOORD) {
+ return Err(error::Error::sys())
+ .context(ks_err!("COSE_Key returned from IRPC is lacking required fields"));
+ }
+ let mut raw_key: Vec<u8> = vec![0; 64];
+ match &cose_key[&COSE_KEY_XCOORD] {
+ Value::Bytes(x_coord) if x_coord.len() == 32 => {
+ raw_key[0..32].clone_from_slice(x_coord)
+ }
+ Value::Bytes(x_coord) => {
+ return Err(error::Error::sys()).context(ks_err!(
+ "COSE_Key X-coordinate is not the right length. \
+ Expected: 32; Actual: {}",
+ x_coord.len()
+ ));
+ }
+ _ => {
+ return Err(error::Error::sys())
+ .context(ks_err!("COSE_Key X-coordinate is not a bstr"));
+ }
+ }
+ match &cose_key[&COSE_KEY_YCOORD] {
+ Value::Bytes(y_coord) if y_coord.len() == 32 => {
+ raw_key[32..64].clone_from_slice(y_coord)
+ }
+ Value::Bytes(y_coord) => {
+ return Err(error::Error::sys()).context(ks_err!(
+ "COSE_Key Y-coordinate is not the right length. \
+ Expected: 32; Actual: {}",
+ y_coord.len()
+ ));
+ }
+ _ => {
+ return Err(error::Error::sys())
+ .context(ks_err!("COSE_Key Y-coordinate is not a bstr"));
+ }
+ }
+ Ok(raw_key)
}
/// Submits a request to the Remote Provisioner HAL to generate a signing key pair.
/// `is_test_mode` indicates whether or not the returned public key should be marked as being
/// for testing in order to differentiate them from private keys. If the call is successful,
/// the key pair is then added to the database.
- pub fn generate_key_pair(&self, is_test_mode: bool, sec_level: SecurityLevel) -> Result<()> {
+ pub fn generate_key_pair(
+ &self,
+ db: &mut KeystoreDB,
+ is_test_mode: bool,
+ sec_level: SecurityLevel,
+ ) -> Result<()> {
let (_, _, uuid) = get_keymint_device(&sec_level)?;
- let dev = self.get_dev_by_sec_level(&sec_level)?;
+ let dev = self
+ .get_dev_by_sec_level(&sec_level)
+ .context(ks_err!("Failed to get device for security level {:?}", sec_level))?;
let mut maced_key = MacedPublicKey { macedKey: Vec::new() };
let priv_key =
map_rem_prov_error(dev.generateEcdsaP256KeyPair(is_test_mode, &mut maced_key))
- .context("In generate_key_pair: Failed to generated ECDSA keypair.")?;
- // 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)
- })
+ .context(ks_err!("Failed to generated ECDSA keypair."))?;
+ let raw_key = Self::parse_cose_mac0_for_coords(&maced_key.macedKey)
+ .context(ks_err!("Failed to parse raw key"))?;
+ db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid)
+ .context(ks_err!("Failed to insert attestation key entry"))
}
/// Checks the security level of each available IRemotelyProvisionedComponent hal and returns
@@ -416,6 +446,70 @@
})
}
+/// 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(ks_err!(
+ "Failed to get a key after \
+ attempting to assign one.",
+ ))?
+ .map_or_else(
+ || {
+ Err(Error::sys()).context(ks_err!(
+ "Attempted to assign a \
+ key and failed silently. Something is very wrong.",
+ ))
+ },
+ |(guard, cert_chain)| Ok(Some((guard, cert_chain))),
+ )
+ }
+ _ => Ok(None),
+ }
+}
+
+/// Returns None if an AttestationKey fails to be assigned. Errors if no keys are available.
+fn get_rem_prov_attest_key_helper(
+ domain: Domain,
+ caller_uid: u32,
+ db: &mut KeystoreDB,
+ km_uuid: &Uuid,
+) -> Result<Option<(KeyIdGuard, CertificateChain)>> {
+ let guard_and_chain = db
+ .retrieve_attestation_key_and_cert_chain(domain, caller_uid as i64, km_uuid)
+ .context(ks_err!("Failed to retrieve a key + cert chain"))?;
+ match guard_and_chain {
+ Some((guard, cert_chain)) => Ok(Some((guard, cert_chain))),
+ // Either this app needs to be assigned a key, or the pool is empty. An error will
+ // be thrown if there is no key available to assign. This will indicate that the app
+ // should be nudged to provision more keys so keystore can retry.
+ None => {
+ db.assign_attestation_key(domain, caller_uid as i64, km_uuid)
+ .context(ks_err!("Failed to assign a key"))?;
+ Ok(None)
+ }
+ }
+}
+
impl binder::Interface for RemoteProvisioningService {}
// Implementation of IRemoteProvisioning. See AIDL spec at
@@ -425,7 +519,7 @@
&self,
expired_by: i64,
sec_level: SecurityLevel,
- ) -> binder::public_api::Result<AttestationPoolStatus> {
+ ) -> binder::Result<AttestationPoolStatus> {
let _wp = wd::watch_millis("IRemoteProvisioning::getPoolStatus", 500);
map_or_log_err(get_pool_status(expired_by, sec_level), Ok)
}
@@ -439,7 +533,7 @@
sec_level: SecurityLevel,
protected_data: &mut ProtectedData,
device_info: &mut DeviceInfo,
- ) -> binder::public_api::Result<Vec<u8>> {
+ ) -> binder::Result<Vec<u8>> {
let _wp = wd::watch_millis("IRemoteProvisioning::generateCsr", 500);
map_or_log_err(
self.generate_csr(
@@ -462,30 +556,524 @@
certs: &[u8],
expiration_date: i64,
sec_level: SecurityLevel,
- ) -> binder::public_api::Result<()> {
+ ) -> binder::Result<()> {
let _wp = wd::watch_millis("IRemoteProvisioning::provisionCertChain", 500);
- map_or_log_err(
- self.provision_cert_chain(public_key, batch_cert, certs, expiration_date, sec_level),
- Ok,
- )
+ DB.with::<_, binder::Result<()>>(|db| {
+ map_or_log_err(
+ self.provision_cert_chain(
+ &mut db.borrow_mut(),
+ public_key,
+ batch_cert,
+ certs,
+ expiration_date,
+ sec_level,
+ ),
+ Ok,
+ )
+ })
}
- fn generateKeyPair(
- &self,
- is_test_mode: bool,
- sec_level: SecurityLevel,
- ) -> binder::public_api::Result<()> {
+ fn generateKeyPair(&self, is_test_mode: bool, sec_level: SecurityLevel) -> binder::Result<()> {
let _wp = wd::watch_millis("IRemoteProvisioning::generateKeyPair", 500);
- map_or_log_err(self.generate_key_pair(is_test_mode, sec_level), Ok)
+ DB.with::<_, binder::Result<()>>(|db| {
+ map_or_log_err(
+ self.generate_key_pair(&mut db.borrow_mut(), is_test_mode, sec_level),
+ Ok,
+ )
+ })
}
- fn getImplementationInfo(&self) -> binder::public_api::Result<Vec<ImplInfo>> {
+ fn getImplementationInfo(&self) -> binder::Result<Vec<ImplInfo>> {
let _wp = wd::watch_millis("IRemoteProvisioning::getSecurityLevels", 500);
map_or_log_err(self.get_implementation_info(), Ok)
}
- fn deleteAllKeys(&self) -> binder::public_api::Result<i64> {
+ fn deleteAllKeys(&self) -> binder::Result<i64> {
let _wp = wd::watch_millis("IRemoteProvisioning::deleteAllKeys", 500);
map_or_log_err(self.delete_all_keys(), Ok)
}
}
+
+/// Implementation of the IRemotelyProvisionedKeyPool service.
+#[derive(Default)]
+pub struct RemotelyProvisionedKeyPoolService {
+ unique_id_to_sec_level: HashMap<String, SecurityLevel>,
+}
+
+impl RemotelyProvisionedKeyPoolService {
+ /// Fetches a remotely provisioned certificate chain and key for the given client uid that
+ /// was provisioned using the IRemotelyProvisionedComponent with the given id. The same key
+ /// will be returned for a given caller_uid on every request. If there are no attestation keys
+ /// available, `OUT_OF_KEYS` is returned.
+ fn get_attestation_key(
+ &self,
+ db: &mut KeystoreDB,
+ caller_uid: i32,
+ irpc_id: &str,
+ ) -> Result<RemotelyProvisionedKey> {
+ log::info!("get_attestation_key(self, {}, {}", caller_uid, irpc_id);
+
+ let sec_level = self
+ .unique_id_to_sec_level
+ .get(irpc_id)
+ .ok_or(Error::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context(format!("In get_attestation_key: unknown irpc id '{}'", irpc_id))?;
+ let (_, _, km_uuid) = get_keymint_device(sec_level)?;
+
+ let guard_and_cert_chain =
+ get_rem_prov_attest_key(Domain::APP, caller_uid as u32, db, &km_uuid)
+ .context(ks_err!())?;
+ match guard_and_cert_chain {
+ Some((_, chain)) => Ok(RemotelyProvisionedKey {
+ keyBlob: chain.private_key.to_vec(),
+ encodedCertChain: chain.cert_chain,
+ }),
+ // It should be impossible to get `None`, but handle it just in case as a
+ // precaution against future behavioral changes in `get_rem_prov_attest_key`.
+ None => Err(error::Error::Rc(ResponseCode::OUT_OF_KEYS))
+ .context(ks_err!("No available attestation keys")),
+ }
+ }
+
+ /// Creates a new instance of the remotely provisioned key pool service, used for fetching
+ /// remotely provisioned attestation keys.
+ pub fn new_native_binder() -> Result<Strong<dyn IRemotelyProvisionedKeyPool>> {
+ let mut result: Self = Default::default();
+
+ let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT)
+ .context(ks_err!("Failed to get TEE Remote Provisioner instance."))?;
+ if let Some(id) = dev.getHardwareInfo()?.uniqueId {
+ result.unique_id_to_sec_level.insert(id, SecurityLevel::TRUSTED_ENVIRONMENT);
+ }
+
+ if let Ok(dev) = get_remotely_provisioned_component(&SecurityLevel::STRONGBOX) {
+ if let Some(id) = dev.getHardwareInfo()?.uniqueId {
+ if result.unique_id_to_sec_level.contains_key(&id) {
+ anyhow::bail!("In new_native_binder: duplicate irpc id found: '{}'", id)
+ }
+ result.unique_id_to_sec_level.insert(id, SecurityLevel::STRONGBOX);
+ }
+ }
+
+ // If none of the remotely provisioned components have unique ids, then we shouldn't
+ // bother publishing the service, as it's impossible to match keys with their backends.
+ if result.unique_id_to_sec_level.is_empty() {
+ anyhow::bail!(
+ "In new_native_binder: No remotely provisioned components have unique ids"
+ )
+ }
+
+ Ok(BnRemotelyProvisionedKeyPool::new_binder(
+ result,
+ BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() },
+ ))
+ }
+}
+
+impl binder::Interface for RemotelyProvisionedKeyPoolService {}
+
+// Implementation of IRemotelyProvisionedKeyPool. See AIDL spec at
+// :aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl
+impl IRemotelyProvisionedKeyPool for RemotelyProvisionedKeyPoolService {
+ fn getAttestationKey(
+ &self,
+ caller_uid: i32,
+ irpc_id: &str,
+ ) -> binder::Result<RemotelyProvisionedKey> {
+ let _wp = wd::watch_millis("IRemotelyProvisionedKeyPool::getAttestationKey", 500);
+ map_or_log_err(check_keystore_permission(KeystorePerm::GetAttestationKey), Ok)?;
+ DB.with::<_, binder::Result<RemotelyProvisionedKey>>(|db| {
+ map_or_log_err(self.get_attestation_key(&mut db.borrow_mut(), caller_uid, irpc_id), Ok)
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_cbor::Value;
+ use std::collections::BTreeMap;
+ use std::sync::{Arc, Mutex};
+ use android_hardware_security_rkp::aidl::android::hardware::security::keymint::{
+ RpcHardwareInfo::RpcHardwareInfo,
+ };
+
+ #[derive(Default)]
+ struct MockRemotelyProvisionedComponentValues {
+ hw_info: RpcHardwareInfo,
+ private_key: Vec<u8>,
+ maced_public_key: Vec<u8>,
+ }
+
+ // binder::Interface requires the Send trait, so we have to use a Mutex even though the test
+ // is single threaded.
+ #[derive(Default)]
+ struct MockRemotelyProvisionedComponent(Arc<Mutex<MockRemotelyProvisionedComponentValues>>);
+
+ impl binder::Interface for MockRemotelyProvisionedComponent {}
+
+ impl IRemotelyProvisionedComponent for MockRemotelyProvisionedComponent {
+ fn getHardwareInfo(&self) -> binder::Result<RpcHardwareInfo> {
+ Ok(self.0.lock().unwrap().hw_info.clone())
+ }
+
+ fn generateEcdsaP256KeyPair(
+ &self,
+ test_mode: bool,
+ maced_public_key: &mut MacedPublicKey,
+ ) -> binder::Result<Vec<u8>> {
+ assert!(test_mode);
+ maced_public_key.macedKey = self.0.lock().unwrap().maced_public_key.clone();
+ Ok(self.0.lock().unwrap().private_key.clone())
+ }
+
+ fn generateCertificateRequest(
+ &self,
+ _test_mode: bool,
+ _keys_to_sign: &[MacedPublicKey],
+ _eek: &[u8],
+ _challenge: &[u8],
+ _device_info: &mut DeviceInfo,
+ _protected_data: &mut ProtectedData,
+ ) -> binder::Result<Vec<u8>> {
+ Err(binder::StatusCode::INVALID_OPERATION.into())
+ }
+
+ fn generateCertificateRequestV2(
+ &self,
+ _keys_to_sign: &[MacedPublicKey],
+ _challenge: &[u8],
+ ) -> binder::Result<Vec<u8>> {
+ Err(binder::StatusCode::INVALID_OPERATION.into())
+ }
+ }
+
+ // Hard coded cert that can be parsed -- the content doesn't matter for testing, only that it's valid.
+ fn get_fake_cert() -> Vec<u8> {
+ vec![
+ 0x30, 0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0x61, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02,
+ 0x14, 0x3a, 0xd5, 0x67, 0xce, 0xfe, 0x93, 0xe1, 0xea, 0xb7, 0xe4, 0xbf, 0x64, 0x19,
+ 0xa4, 0x11, 0xe1, 0x87, 0x40, 0x20, 0x37, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48,
+ 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x06, 0x13, 0x02, 0x55, 0x54, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+ 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31,
+ 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67,
+ 0x6c, 0x65, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x32, 0x31, 0x30, 0x32, 0x32,
+ 0x30, 0x38, 0x35, 0x32, 0x5a, 0x17, 0x0d, 0x34, 0x39, 0x30, 0x34, 0x32, 0x36, 0x32,
+ 0x32, 0x30, 0x38, 0x35, 0x32, 0x5a, 0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x54, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
+ 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
+ 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f,
+ 0x67, 0x6c, 0x65, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d,
+ 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42,
+ 0x00, 0x04, 0x1e, 0xac, 0x0c, 0xe0, 0x0d, 0xc5, 0x25, 0x84, 0x1b, 0xd2, 0x77, 0x2d,
+ 0xe7, 0xba, 0xf1, 0xde, 0xa7, 0xf6, 0x39, 0x7f, 0x38, 0x91, 0xbf, 0xa4, 0x58, 0xf5,
+ 0x62, 0x6b, 0xce, 0x06, 0xcf, 0xb9, 0x73, 0x91, 0x0d, 0x8a, 0x60, 0xa0, 0xc6, 0xa2,
+ 0x22, 0xe6, 0x51, 0x2e, 0x58, 0xd6, 0x43, 0x02, 0x80, 0x43, 0x44, 0x29, 0x38, 0x9a,
+ 0x99, 0xf3, 0xa4, 0xdd, 0xd0, 0xb4, 0x6f, 0x8b, 0x44, 0x2d, 0xa3, 0x53, 0x30, 0x51,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xdb, 0x13, 0x68,
+ 0xe0, 0x0e, 0x47, 0x10, 0xf8, 0xcb, 0x88, 0x83, 0xfe, 0x42, 0x3c, 0xd9, 0x3f, 0x1a,
+ 0x33, 0xe9, 0xaa, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xdb, 0x13, 0x68, 0xe0, 0x0e, 0x47, 0x10, 0xf8, 0xcb, 0x88, 0x83, 0xfe,
+ 0x42, 0x3c, 0xd9, 0x3f, 0x1a, 0x33, 0xe9, 0xaa, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d,
+ 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06,
+ 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45,
+ 0x02, 0x20, 0x10, 0xdf, 0x40, 0xc3, 0x20, 0x54, 0x36, 0xb5, 0xc9, 0x3c, 0x70, 0xe3,
+ 0x55, 0x37, 0xd2, 0x04, 0x51, 0xeb, 0x0f, 0x18, 0x83, 0xd0, 0x58, 0xa1, 0x08, 0x77,
+ 0x8d, 0x4d, 0xa4, 0x20, 0xee, 0x33, 0x02, 0x21, 0x00, 0x8d, 0xe3, 0xa6, 0x6c, 0x0d,
+ 0x86, 0x25, 0xdc, 0x59, 0x0d, 0x21, 0x43, 0x22, 0x3a, 0xb9, 0xa1, 0x73, 0x28, 0xc9,
+ 0x16, 0x9e, 0x91, 0x15, 0xc4, 0xc3, 0xd7, 0xeb, 0xe5, 0xce, 0xdc, 0x1c, 0x1b,
+ ]
+ }
+
+ // Generate a fake COSE_Mac0 with a key that's just `byte` repeated
+ fn generate_maced_pubkey(byte: u8) -> Vec<u8> {
+ vec![
+ 0x84, 0x43, 0xA1, 0x01, 0x05, 0xA0, 0x58, 0x4D, 0xA5, 0x01, 0x02, 0x03, 0x26, 0x20,
+ 0x01, 0x21, 0x58, 0x20, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, 0x22, 0x58, 0x20, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, 0x58, 0x20, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte,
+ ]
+ }
+
+ #[test]
+ fn test_parse_cose_mac0_for_coords_raw_bytes() -> Result<()> {
+ let cose_mac0: Vec<u8> = vec![
+ 0x84, 0x01, 0x02, 0x58, 0x4D, 0xA5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58,
+ 0x20, 0x1A, 0xFB, 0xB2, 0xD9, 0x9D, 0xF6, 0x2D, 0xF0, 0xC3, 0xA8, 0xFC, 0x7E, 0xC9,
+ 0x21, 0x26, 0xED, 0xB5, 0x4A, 0x98, 0x9B, 0xF3, 0x0D, 0x91, 0x3F, 0xC6, 0x42, 0x5C,
+ 0x43, 0x22, 0xC8, 0xEE, 0x03, 0x22, 0x58, 0x20, 0x40, 0xB3, 0x9B, 0xFC, 0x47, 0x95,
+ 0x90, 0xA7, 0x5C, 0x5A, 0x16, 0x31, 0x34, 0xAF, 0x0C, 0x5B, 0xF2, 0xB2, 0xD8, 0x2A,
+ 0xA3, 0xB3, 0x1A, 0xB4, 0x4C, 0xA6, 0x3B, 0xE7, 0x22, 0xEC, 0x41, 0xDC, 0x03,
+ ];
+ let raw_key = RemoteProvisioningService::parse_cose_mac0_for_coords(&cose_mac0)?;
+ assert_eq!(
+ raw_key,
+ vec![
+ 0x1A, 0xFB, 0xB2, 0xD9, 0x9D, 0xF6, 0x2D, 0xF0, 0xC3, 0xA8, 0xFC, 0x7E, 0xC9, 0x21,
+ 0x26, 0xED, 0xB5, 0x4A, 0x98, 0x9B, 0xF3, 0x0D, 0x91, 0x3F, 0xC6, 0x42, 0x5C, 0x43,
+ 0x22, 0xC8, 0xEE, 0x03, 0x40, 0xB3, 0x9B, 0xFC, 0x47, 0x95, 0x90, 0xA7, 0x5C, 0x5A,
+ 0x16, 0x31, 0x34, 0xAF, 0x0C, 0x5B, 0xF2, 0xB2, 0xD8, 0x2A, 0xA3, 0xB3, 0x1A, 0xB4,
+ 0x4C, 0xA6, 0x3B, 0xE7, 0x22, 0xEC, 0x41, 0xDC,
+ ]
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_cose_mac0_for_coords_constructed_mac() -> Result<()> {
+ let x_coord: Vec<u8> = vec![0; 32];
+ let y_coord: Vec<u8> = vec![1; 32];
+ let mut expected_key: Vec<u8> = Vec::new();
+ expected_key.extend(&x_coord);
+ expected_key.extend(&y_coord);
+ let key_map: BTreeMap<Value, Value> = BTreeMap::from([
+ (Value::Integer(1), Value::Integer(2)),
+ (Value::Integer(3), Value::Integer(-7)),
+ (Value::Integer(-1), Value::Integer(1)),
+ (Value::Integer(-2), Value::Bytes(x_coord)),
+ (Value::Integer(-3), Value::Bytes(y_coord)),
+ ]);
+ let cose_mac0: Vec<Value> = vec![
+ Value::Integer(0),
+ Value::Integer(1),
+ Value::from(serde_cbor::to_vec(&key_map)?),
+ Value::Integer(2),
+ ];
+ let raw_key = RemoteProvisioningService::parse_cose_mac0_for_coords(&serde_cbor::to_vec(
+ &Value::from(cose_mac0),
+ )?)?;
+ assert_eq!(expected_key, raw_key);
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_payload_from_cose_mac() -> Result<()> {
+ let key_map = Value::Map(BTreeMap::from([(Value::Integer(1), Value::Integer(2))]));
+ let payload = Value::Bytes(serde_cbor::to_vec(&key_map)?);
+ let cose_mac0 =
+ Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]);
+ let extracted_map = RemoteProvisioningService::extract_payload_from_cose_mac(
+ &serde_cbor::to_vec(&cose_mac0)?,
+ )?;
+ assert_eq!(key_map, extracted_map);
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_payload_from_cose_mac_fails_malformed_payload() -> Result<()> {
+ let payload = Value::Bytes(vec![5; 10]);
+ let cose_mac0 =
+ Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]);
+ let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac(
+ &serde_cbor::to_vec(&cose_mac0)?,
+ );
+ assert!(extracted_payload.is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_payload_from_cose_mac_fails_type() -> Result<()> {
+ let payload = Value::Integer(1);
+ let cose_mac0 =
+ Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]);
+ let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac(
+ &serde_cbor::to_vec(&cose_mac0)?,
+ );
+ assert!(extracted_payload.is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_payload_from_cose_mac_fails_length() -> Result<()> {
+ let cose_mac0 = Value::Array(vec![Value::Integer(0), Value::Integer(1)]);
+ let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac(
+ &serde_cbor::to_vec(&cose_mac0)?,
+ );
+ assert!(extracted_payload.is_err());
+ Ok(())
+ }
+
+ #[test]
+ #[ignore] // b/215746308
+ fn test_get_attestation_key_no_keys_provisioned() {
+ let mut db = crate::database::tests::new_test_db().unwrap();
+ let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default();
+ mock_rpc.0.lock().unwrap().hw_info.uniqueId = Some(String::from("mallory"));
+
+ let mut service: RemotelyProvisionedKeyPoolService = Default::default();
+ service
+ .unique_id_to_sec_level
+ .insert(String::from("mallory"), SecurityLevel::TRUSTED_ENVIRONMENT);
+
+ assert_eq!(
+ service
+ .get_attestation_key(&mut db, 0, "mallory")
+ .unwrap_err()
+ .downcast::<error::Error>()
+ .unwrap(),
+ error::Error::Rc(ResponseCode::OUT_OF_KEYS)
+ );
+ }
+
+ #[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 74aba3c..66fcb26 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -18,16 +18,18 @@
use crate::audit_log::{
log_key_deleted, log_key_generated, log_key_imported, log_key_integrity_violation,
};
-use crate::database::{CertificateInfo, KeyIdGuard};
+use crate::database::{BlobInfo, CertificateInfo, KeyIdGuard};
use crate::error::{self, map_km_error, map_or_log_err, Error, ErrorCode};
-use crate::globals::{DB, ENFORCEMENTS, LEGACY_MIGRATOR, SUPER_KEY};
+use crate::globals::{DB, ENFORCEMENTS, LEGACY_IMPORTER, SUPER_KEY};
use crate::key_parameter::KeyParameter as KsKeyParam;
use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
+use crate::ks_err;
use crate::metrics_store::log_key_creation_event_stats;
use crate::remote_provisioning::RemProvState;
use crate::super_key::{KeyBlob, SuperKeyManager};
use crate::utils::{
- check_device_attestation_permissions, check_key_permission, is_device_id_attestation_tag,
+ check_device_attestation_permissions, check_key_permission,
+ check_unique_id_attestation_permissions, is_device_id_attestation_tag,
key_characteristics_to_internal, uid_to_android_user, watchdog as wd,
};
use crate::{
@@ -54,9 +56,11 @@
Domain::Domain, EphemeralStorageKeyResponse::EphemeralStorageKeyResponse,
IKeystoreOperation::IKeystoreOperation, IKeystoreSecurityLevel::BnKeystoreSecurityLevel,
IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
- KeyMetadata::KeyMetadata, KeyParameters::KeyParameters,
+ KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode,
};
use anyhow::{anyhow, Context, Result};
+use std::convert::TryInto;
+use std::time::SystemTime;
/// Implementation of the IKeystoreSecurityLevel Interface.
pub struct KeystoreSecurityLevel {
@@ -86,7 +90,7 @@
id_rotation_state: IdRotationState,
) -> Result<(Strong<dyn IKeystoreSecurityLevel>, Uuid)> {
let (dev, hw_info, km_uuid) = get_keymint_device(&security_level)
- .context("In KeystoreSecurityLevel::new_native_binder.")?;
+ .context(ks_err!("KeystoreSecurityLevel::new_native_binder."))?;
let result = BnKeystoreSecurityLevel::new_binder(
Self {
security_level,
@@ -130,8 +134,7 @@
_ => Some(
certificate_chain
.iter()
- .map(|c| c.encodedCertificate.iter())
- .flatten()
+ .flat_map(|c| c.encodedCertificate.iter())
.copied()
.collect(),
),
@@ -145,7 +148,7 @@
SecurityLevel::SOFTWARE,
));
- let creation_date = DateTime::now().context("Trying to make creation time.")?;
+ let creation_date = DateTime::now().context(ks_err!("Trying to make creation time."))?;
let key = match key.domain {
Domain::BLOB => KeyDescriptor {
@@ -158,16 +161,18 @@
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_MIGRATOR,
+ &LEGACY_IMPORTER,
&(key.domain),
&key_parameters,
flags,
user_id,
&key_blob,
)
- .context("In store_new_key. Failed to handle super encryption.")?;
+ .context(ks_err!("Failed to handle super encryption."))?;
let mut key_metadata = KeyMetaData::new();
key_metadata.add(KeyMetaEntry::CreationDate(creation_date));
@@ -178,19 +183,19 @@
&key,
KeyType::Client,
&key_parameters,
- &(&key_blob, &blob_metadata),
+ &BlobInfo::new(&key_blob, &blob_metadata),
&cert_info,
&key_metadata,
&self.km_uuid,
)
- .context("In store_new_key.")?;
+ .context(ks_err!())?;
Ok(KeyDescriptor {
domain: Domain::KEY_ID,
nspace: key_id.id(),
..Default::default()
})
})
- .context("In store_new_key.")?,
+ .context(ks_err!())?,
};
Ok(KeyMetadata {
@@ -216,21 +221,20 @@
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)
- .context("In create_operation: checking use permission for Domain::BLOB.")?;
+ check_key_permission(KeyPerm::Use, key, &None)
+ .context(ks_err!("checking use permission for Domain::BLOB."))?;
if forced {
- check_key_permission(KeyPerm::req_forced_op(), key, &None).context(
- "In create_operation: checking forced permission for Domain::BLOB.",
- )?;
+ check_key_permission(KeyPerm::ReqForcedOp, key, &None)
+ .context(ks_err!("checking forced permission for Domain::BLOB."))?;
}
(
match &key.blob {
Some(blob) => blob,
None => {
- return Err(Error::sys()).context(concat!(
- "In create_operation: Key blob must be specified when",
- " using Domain::BLOB."
- ))
+ return Err(Error::sys()).context(ks_err!(
+ "Key blob must be specified when \
+ using Domain::BLOB."
+ ));
}
},
None,
@@ -239,30 +243,34 @@
)
}
_ => {
+ 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_MIGRATOR.with_try_migrate(key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().load_key_entry(
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::req_forced_op(), k, &av)?;
+ check_key_permission(KeyPerm::ReqForcedOp, k, &av)?;
}
Ok(())
},
)
})
})
- .context("In create_operation: Failed to load key blob.")?;
+ .context(ks_err!("Failed to load key blob."))?;
let (blob, blob_metadata) =
- key_entry.take_key_blob_info().ok_or_else(Error::sys).context(concat!(
- "In create_operation: Successfully loaded key entry, ",
- "but KM blob was missing."
+ key_entry.take_key_blob_info().ok_or_else(Error::sys).context(ks_err!(
+ "Successfully loaded key entry, \
+ but KM blob was missing."
))?;
scoping_blob = blob;
@@ -277,11 +285,11 @@
let purpose = operation_parameters.iter().find(|p| p.tag == Tag::PURPOSE).map_or(
Err(Error::Km(ErrorCode::INVALID_ARGUMENT))
- .context("In create_operation: No operation purpose specified."),
+ .context(ks_err!("No operation purpose specified.")),
|kp| match kp.value {
KeyParameterValue::KeyPurpose(p) => Ok(p),
_ => Err(Error::Km(ErrorCode::INVALID_ARGUMENT))
- .context("In create_operation: Malformed KeyParameter."),
+ .context(ks_err!("Malformed KeyParameter.")),
},
)?;
@@ -298,18 +306,20 @@
operation_parameters.as_ref(),
self.hw_info.timestampTokenRequired,
)
- .context("In create_operation.")?;
+ .context(ks_err!())?;
let km_blob = SUPER_KEY
+ .read()
+ .unwrap()
.unwrap_key_if_required(&blob_metadata, km_blob)
- .context("In create_operation. Failed to handle super encryption.")?;
+ .context(ks_err!("Failed to handle super encryption."))?;
let (begin_result, upgraded_blob) = self
.upgrade_keyblob_if_required_with(
&*self.keymint,
key_id_guard,
&km_blob,
- &blob_metadata,
+ blob_metadata.km_uuid().copied(),
operation_parameters,
|blob| loop {
match map_km_error({
@@ -344,7 +354,7 @@
}
},
)
- .context("In create_operation: Failed to begin operation.")?;
+ .context(ks_err!("Failed to begin operation."))?;
let operation_challenge = auth_info.finalize_create_authorization(begin_result.challenge);
@@ -359,18 +369,18 @@
LoggingInfo::new(self.security_level, purpose, op_params, upgraded_blob.is_some()),
),
None => {
- return Err(Error::sys()).context(concat!(
- "In create_operation: Begin operation returned successfully, ",
- "but did not return a valid operation."
- ))
+ return Err(Error::sys()).context(ks_err!(
+ "Begin operation returned successfully, \
+ but did not return a valid operation."
+ ));
}
};
- let op_binder: binder::public_api::Strong<dyn IKeystoreOperation> =
+ let op_binder: binder::Strong<dyn IKeystoreOperation> =
KeystoreOperation::new_native_binder(operation)
.as_binder()
.into_interface()
- .context("In create_operation: Failed to create IKeystoreOperation.")?;
+ .context(ks_err!("Failed to create IKeystoreOperation."))?;
Ok(CreateOperationResponse {
iOperation: Some(op_binder),
@@ -386,26 +396,53 @@
})
}
- fn add_certificate_parameters(
+ fn add_required_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(ks_err!(
+ "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(ks_err!(
+ "KeystoreSecurityLevel::add_required_parameters: \
+ Failed to get epoch time."
+ ))?
+ .as_millis()
+ .try_into()
+ .context(ks_err!(
+ "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_certificate_parameters calling: get_aaid",
+ "In KeystoreSecurityLevel::add_required_parameters calling: get_aaid",
500,
);
- keystore2_aaid::get_aaid(uid).map_err(|e| {
- anyhow!(format!(
- "In add_certificate_parameters: get_aaid returned status {}.",
- e
- ))
- })
+ keystore2_aaid::get_aaid(uid)
+ .map_err(|e| anyhow!(ks_err!("get_aaid returned status {}.", e)))
}?;
result.push(KeyParameter {
@@ -415,14 +452,19 @@
}
if params.iter().any(|kp| kp.tag == Tag::INCLUDE_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_certificate_parameters: Call to had_factory_reset_since_id_rotation failed."
- )? {
- result.push(KeyParameter{
+ if check_key_permission(KeyPerm::GenUniqueId, key, &None).is_err()
+ && check_unique_id_attestation_permissions().is_err()
+ {
+ return Err(Error::perm()).context(ks_err!(
+ "Caller does not have the permission to generate a unique ID"
+ ));
+ }
+ if self
+ .id_rotation_state
+ .had_factory_reset_since_id_rotation()
+ .context(ks_err!("Call to had_factory_reset_since_id_rotation failed."))?
+ {
+ result.push(KeyParameter {
tag: Tag::RESET_SINCE_ID_ROTATION,
value: KeyParameterValue::BoolValue(true),
})
@@ -432,8 +474,7 @@
// If the caller requests any device identifier attestation tag, check that they hold the
// correct Android permission.
if params.iter().any(|kp| is_device_id_attestation_tag(kp.tag)) {
- check_device_attestation_permissions().context(concat!(
- "In add_certificate_parameters: ",
+ check_device_attestation_permissions().context(ks_err!(
"Caller does not have the permission to attest device identifiers."
))?;
}
@@ -471,7 +512,7 @@
) -> Result<KeyMetadata> {
if key.domain != Domain::BLOB && key.alias.is_none() {
return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
- .context("In generate_key: Alias must be specified");
+ .context(ks_err!("Alias must be specified"));
}
let caller_uid = ThreadState::get_calling_uid();
@@ -487,7 +528,7 @@
// 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(ks_err!())?;
let attestation_key_info = match (key.domain, attest_key_descriptor) {
(Domain::BLOB, _) => None,
@@ -502,11 +543,11 @@
&mut db.borrow_mut(),
)
})
- .context("In generate_key: Trying to get an attestation key")?,
+ .context(ks_err!("Trying to get an attestation key"))?,
};
let params = self
- .add_certificate_parameters(caller_uid, params, &key)
- .context("In generate_key: Trying to get aaid.")?;
+ .add_required_parameters(caller_uid, params, &key)
+ .context(ks_err!("Trying to get aaid."))?;
let creation_result = match attestation_key_info {
Some(AttestationKeyInfo::UserGenerated {
@@ -519,7 +560,7 @@
&*self.keymint,
Some(key_id_guard),
&KeyBlob::Ref(&blob),
- &blob_metadata,
+ blob_metadata.km_uuid().copied(),
¶ms,
|blob| {
let attest_key = Some(AttestationKey {
@@ -539,25 +580,42 @@
})
},
)
- .context("In generate_key: Using user generated attestation key.")
+ .context(ks_err!("Using user generated attestation key."))
.map(|(result, _)| result),
- 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.
- );
- self.keymint.generateKey(¶ms, Some(&attestation_key))
- })
+ Some(AttestationKeyInfo::RemoteProvisioned {
+ key_id_guard,
+ attestation_key,
+ attestation_certs,
+ }) => self
+ .upgrade_keyblob_if_required_with(
+ &*self.keymint,
+ Some(key_id_guard),
+ &KeyBlob::Ref(&attestation_key.keyBlob),
+ Some(self.rem_prov_state.get_uuid()),
+ &[],
+ |blob| {
+ map_km_error({
+ let _wp = self.watch_millis(
+ concat!(
+ "In KeystoreSecurityLevel::generate_key (RemoteProvisioned): ",
+ "calling generate_key.",
+ ),
+ 5000, // Generate can take a little longer.
+ );
+ let dynamic_attest_key = Some(AttestationKey {
+ keyBlob: blob.to_vec(),
+ attestKeyParams: vec![],
+ issuerSubjectName: attestation_key.issuerSubjectName.clone(),
+ });
+ self.keymint.generateKey(¶ms, dynamic_attest_key.as_ref())
+ })
+ },
+ )
.context("While generating Key with remote provisioned attestation key.")
- .map(|mut creation_result| {
- creation_result.certificateChain.push(attestation_certs);
- creation_result
- })
- }
+ .map(|(mut result, _)| {
+ result.certificateChain.push(attestation_certs);
+ result
+ }),
None => map_km_error({
let _wp = self.watch_millis(
concat!(
@@ -570,10 +628,10 @@
})
.context("While generating Key without explicit attestation key."),
}
- .context("In generate_key.")?;
+ .context(ks_err!())?;
let user_id = uid_to_android_user(caller_uid);
- self.store_new_key(key, creation_result, user_id, Some(flags)).context("In generate_key.")
+ self.store_new_key(key, creation_result, user_id, Some(flags)).context(ks_err!())
}
fn import_key(
@@ -586,7 +644,7 @@
) -> Result<KeyMetadata> {
if key.domain != Domain::BLOB && key.alias.is_none() {
return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
- .context("In import_key: Alias must be specified");
+ .context(ks_err!("Alias must be specified"));
}
let caller_uid = ThreadState::get_calling_uid();
@@ -601,11 +659,11 @@
};
// 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_certificate_parameters(caller_uid, params, &key)
- .context("In import_key: Trying to get aaid.")?;
+ .add_required_parameters(caller_uid, params, &key)
+ .context(ks_err!("Trying to get aaid."))?;
let format = params
.iter()
@@ -619,9 +677,9 @@
KeyParameterValue::Algorithm(Algorithm::RSA)
| KeyParameterValue::Algorithm(Algorithm::EC) => Ok(KeyFormat::PKCS8),
v => Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
- .context(format!("Unknown Algorithm {:?}.", v)),
+ .context(ks_err!("Unknown Algorithm {:?}.", v)),
})
- .context("In import_key.")?;
+ .context(ks_err!())?;
let km_dev = &self.keymint;
let creation_result = map_km_error({
@@ -629,10 +687,10 @@
self.watch_millis("In KeystoreSecurityLevel::import_key: calling importKey.", 500);
km_dev.importKey(¶ms, format, key_data, None /* attestKey */)
})
- .context("In import_key: Trying to call importKey")?;
+ .context(ks_err!("Trying to call importKey"))?;
let user_id = uid_to_android_user(caller_uid);
- self.store_new_key(key, creation_result, user_id, Some(flags)).context("In import_key.")
+ self.store_new_key(key, creation_result, user_id, Some(flags)).context(ks_err!())
}
fn import_wrapped_key(
@@ -649,20 +707,16 @@
domain: Domain::SELINUX, blob: Some(ref blob), alias: Some(_), ..
} => blob,
_ => {
- return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context(format!(
- concat!(
- "In import_wrapped_key: Alias and blob must be specified ",
- "and domain must be APP or SELINUX. {:?}"
- ),
+ return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context(ks_err!(
+ "Alias and blob must be specified and domain must be APP or SELINUX. {:?}",
key
- ))
+ ));
}
};
if wrapping_key.domain == Domain::BLOB {
- return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context(
- "In import_wrapped_key: Import wrapped key not supported for self managed blobs.",
- );
+ return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
+ .context(ks_err!("Import wrapped key not supported for self managed blobs."));
}
let caller_uid = ThreadState::get_calling_uid();
@@ -685,32 +739,35 @@
};
// Import_wrapped_key requires the rebind permission for the new key.
- check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_wrapped_key.")?;
+ check_key_permission(KeyPerm::Rebind, &key, &None).context(ks_err!())?;
+
+ let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id);
let (wrapping_key_id_guard, mut wrapping_key_entry) = DB
.with(|db| {
- LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(&key, caller_uid, super_key, || {
db.borrow_mut().load_key_entry(
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),
)
})
})
.context("Failed to load wrapping key.")?;
- let (wrapping_key_blob, wrapping_blob_metadata) = wrapping_key_entry
- .take_key_blob_info()
- .ok_or_else(error::Error::sys)
- .context("No km_blob after successfully loading key. This should never happen.")?;
-
- 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.",
+ let (wrapping_key_blob, wrapping_blob_metadata) =
+ wrapping_key_entry.take_key_blob_info().ok_or_else(error::Error::sys).context(
+ ks_err!("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(ks_err!("Failed to handle super encryption for wrapping key."))?;
+
// km_dev.importWrappedKey does not return a certificate chain.
// TODO Do we assume that all wrapped keys are symmetric?
// let certificate_chain: Vec<KmCertificate> = Default::default();
@@ -738,7 +795,7 @@
&*self.keymint,
Some(wrapping_key_id_guard),
&wrapping_key_blob,
- &wrapping_blob_metadata,
+ wrapping_blob_metadata.km_uuid().copied(),
&[],
|wrapping_blob| {
let _wp = self.watch_millis(
@@ -756,25 +813,25 @@
Ok(creation_result)
},
)
- .context("In import_wrapped_key.")?;
+ .context(ks_err!())?;
self.store_new_key(key, creation_result, user_id, None)
- .context("In import_wrapped_key: Trying to store the new key.")
+ .context(ks_err!("Trying to store the new key."))
}
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)
- .context("In store_upgraded_keyblob: Failed to handle super encryption.")?;
+ .context(ks_err!("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| {
@@ -786,75 +843,49 @@
Some(&new_blob_metadata),
)
})
- .context("In store_upgraded_keyblob: Failed to insert upgraded blob into the database.")
+ .context(ks_err!("Failed to insert upgraded blob into the database."))
}
fn upgrade_keyblob_if_required_with<T, F>(
&self,
km_dev: &dyn IKeyMintDevice,
- key_id_guard: Option<KeyIdGuard>,
+ mut key_id_guard: Option<KeyIdGuard>,
key_blob: &KeyBlob,
- blob_metadata: &BlobMetaData,
+ km_uuid: Option<Uuid>,
params: &[KeyParameter],
f: F,
) -> Result<(T, Option<Vec<u8>>)>
where
F: Fn(&[u8]) -> Result<T, Error>,
{
- 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))
+ 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(ks_err!("store_upgraded_keyblob failed"))
+ } else {
+ Ok(())
}
- .context("In upgrade_keyblob_if_required_with: Upgrade failed.")?;
+ },
+ )
+ .context(ks_err!())?;
- if let Some(kid) = key_id_guard {
- Self::store_upgraded_keyblob(
- kid,
- blob_metadata.km_uuid(),
- key_blob,
- &upgraded_blob,
- )
- .context(
- "In upgrade_keyblob_if_required_with: store_upgraded_keyblob failed",
- )?;
- }
-
- 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.")
+ // 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(ks_err!("store_upgraded_keyblob failed in forced reencrypt"))?;
}
}
+ Ok((v, upgraded_blob))
}
fn convert_storage_key_to_ephemeral(
@@ -862,22 +893,18 @@
storage_key: &KeyDescriptor,
) -> Result<EphemeralStorageKeyResponse> {
if storage_key.domain != Domain::BLOB {
- return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT)).context(concat!(
- "In IKeystoreSecurityLevel convert_storage_key_to_ephemeral: ",
- "Key must be of Domain::BLOB"
- ));
+ return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
+ .context(ks_err!("Key must be of Domain::BLOB"));
}
let key_blob = storage_key
.blob
.as_ref()
.ok_or(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
- .context(
- "In IKeystoreSecurityLevel convert_storage_key_to_ephemeral: No key blob specified",
- )?;
+ .context(ks_err!("No key blob specified"))?;
// convert_storage_key_to_ephemeral requires the associated permission
- check_key_permission(KeyPerm::convert_storage_key_to_ephemeral(), storage_key, &None)
- .context("In convert_storage_key_to_ephemeral: Check permission")?;
+ check_key_permission(KeyPerm::ConvertStorageKeyToEphemeral, storage_key, &None)
+ .context(ks_err!("Check permission"))?;
let km_dev = &self.keymint;
match {
@@ -901,7 +928,7 @@
);
map_km_error(km_dev.upgradeKey(key_blob, &[]))
}
- .context("In convert_storage_key_to_ephemeral: Failed to upgrade key blob.")?;
+ .context(ks_err!("Failed to upgrade key blob."))?;
let ephemeral_key = {
let _wp = self.watch_millis(
"In convert_storage_key_to_ephemeral: calling convertStorageKeyToEphemeral (2)",
@@ -909,8 +936,7 @@
);
map_km_error(km_dev.convertStorageKeyToEphemeral(&upgraded_blob))
}
- .context(concat!(
- "In convert_storage_key_to_ephemeral: ",
+ .context(ks_err!(
"Failed to retrieve ephemeral key (after upgrade)."
))?;
Ok(EphemeralStorageKeyResponse {
@@ -918,31 +944,30 @@
upgradedBlob: Some(upgraded_blob),
})
}
- Err(e) => Err(e)
- .context("In convert_storage_key_to_ephemeral: Failed to retrieve ephemeral key."),
+ Err(e) => Err(e).context(ks_err!("Failed to retrieve ephemeral key.")),
}
}
fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {
if key.domain != Domain::BLOB {
return Err(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
- .context("In IKeystoreSecurityLevel delete_key: Key must be of Domain::BLOB");
+ .context(ks_err!("delete_key: Key must be of Domain::BLOB"));
}
let key_blob = key
.blob
.as_ref()
.ok_or(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
- .context("In IKeystoreSecurityLevel delete_key: No key blob specified")?;
+ .context(ks_err!("delete_key: No key blob specified"))?;
- check_key_permission(KeyPerm::delete(), key, &None)
- .context("In IKeystoreSecurityLevel delete_key: Checking delete permissions")?;
+ check_key_permission(KeyPerm::Delete, key, &None)
+ .context(ks_err!("delete_key: Checking delete permissions"))?;
let km_dev = &self.keymint;
{
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(ks_err!("keymint device deleteKey"))
}
}
}
@@ -955,7 +980,7 @@
key: &KeyDescriptor,
operation_parameters: &[KeyParameter],
forced: bool,
- ) -> binder::public_api::Result<CreateOperationResponse> {
+ ) -> binder::Result<CreateOperationResponse> {
let _wp = self.watch_millis("IKeystoreSecurityLevel::createOperation", 500);
map_or_log_err(self.create_operation(key, operation_parameters, forced), Ok)
}
@@ -966,7 +991,7 @@
params: &[KeyParameter],
flags: i32,
entropy: &[u8],
- ) -> binder::public_api::Result<KeyMetadata> {
+ ) -> binder::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);
@@ -982,7 +1007,7 @@
params: &[KeyParameter],
flags: i32,
key_data: &[u8],
- ) -> binder::public_api::Result<KeyMetadata> {
+ ) -> binder::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);
@@ -996,7 +1021,7 @@
masking_key: Option<&[u8]>,
params: &[KeyParameter],
authenticators: &[AuthenticatorSpec],
- ) -> binder::public_api::Result<KeyMetadata> {
+ ) -> binder::Result<KeyMetadata> {
let _wp = self.watch_millis("IKeystoreSecurityLevel::importWrappedKey", 500);
let result =
self.import_wrapped_key(key, wrapping_key, masking_key, params, authenticators);
@@ -1007,11 +1032,11 @@
fn convertStorageKeyToEphemeral(
&self,
storage_key: &KeyDescriptor,
- ) -> binder::public_api::Result<EphemeralStorageKeyResponse> {
+ ) -> binder::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::public_api::Result<()> {
+ fn deleteKey(&self, key: &KeyDescriptor) -> binder::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 b35fe36..f43ba5c 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -18,15 +18,16 @@
use std::collections::HashMap;
use crate::audit_log::log_key_deleted;
+use crate::ks_err;
use crate::permission::{KeyPerm, KeystorePerm};
use crate::security_level::KeystoreSecurityLevel;
use crate::utils::{
check_grant_permission, check_key_permission, check_keystore_permission,
- key_parameters_to_authorizations, watchdog as wd,
+ key_parameters_to_authorizations, list_key_entries, uid_to_android_user, watchdog as wd,
};
use crate::{
database::Uuid,
- globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_MIGRATOR},
+ globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_IMPORTER, SUPER_KEY},
};
use crate::{database::KEYSTORE_UUID, permission};
use crate::{
@@ -65,10 +66,7 @@
SecurityLevel::TRUSTED_ENVIRONMENT,
id_rotation_state.clone(),
)
- .context(concat!(
- "In KeystoreService::new_native_binder: ",
- "Trying to construct mandatory security level TEE."
- ))?;
+ .context(ks_err!("Trying to construct mandatory security level TEE."))?;
result.i_sec_level_by_uuid.insert(uuid, dev);
result.uuid_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid);
@@ -81,13 +79,11 @@
}
let uuid_by_sec_level = result.uuid_by_sec_level.clone();
- LEGACY_MIGRATOR
+ LEGACY_IMPORTER
.set_init(move || {
(create_thread_local_db(), uuid_by_sec_level, LEGACY_BLOB_LOADER.clone())
})
- .context(
- "In KeystoreService::new_native_binder: Trying to initialize the legacy migrator.",
- )?;
+ .context(ks_err!("Trying to initialize the legacy migrator."))?;
Ok(BnKeystoreService::new_binder(
result,
@@ -107,8 +103,7 @@
if let Some(dev) = self.i_sec_level_by_uuid.get(uuid) {
Ok(dev.clone())
} else {
- Err(error::Error::sys())
- .context("In get_i_sec_level_by_uuid: KeyMint instance for key not found.")
+ Err(error::Error::sys()).context(ks_err!("KeyMint instance for key not found."))
}
}
@@ -124,30 +119,34 @@
Ok(dev.clone())
} else {
Err(error::Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
- .context("In get_security_level: No such security level.")
+ .context(ks_err!("No such security level."))
}
}
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_MIGRATOR.with_try_migrate(key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().load_key_entry(
key,
KeyType::Client,
KeyEntryLoadBits::PUBLIC,
caller_uid,
- |k, av| check_key_permission(KeyPerm::get_info(), k, &av),
+ |k, av| check_key_permission(KeyPerm::GetInfo, k, &av),
)
})
})
- .context("In get_key_entry, while trying to load key info.")?;
+ .context(ks_err!("while trying to load key info."))?;
let i_sec_level = if !key_entry.pure_cert() {
Some(
self.get_i_sec_level_by_uuid(key_entry.km_uuid())
- .context("In get_key_entry: Trying to get security level proxy.")?,
+ .context(ks_err!("Trying to get security level proxy."))?,
)
} else {
None
@@ -169,7 +168,7 @@
.creation_date()
.map(|d| d.to_millis_epoch())
.ok_or(Error::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In get_key_entry: Trying to get creation date.")?,
+ .context(ks_err!("Trying to get creation date."))?,
authorizations: key_parameters_to_authorizations(key_entry.into_key_parameters()),
},
})
@@ -182,17 +181,17 @@
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_MIGRATOR.with_try_migrate(key, caller_uid, || {
+ let entry = match LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().load_key_entry(
key,
KeyType::Client,
KeyEntryLoadBits::NONE,
caller_uid,
- |k, av| {
- check_key_permission(KeyPerm::update(), k, &av)
- .context("In update_subcomponent.")
- },
+ |k, av| check_key_permission(KeyPerm::Update, k, &av).context(ks_err!()),
)
}) {
Err(e) => match e.root_cause().downcast_ref::<Error>() {
@@ -201,7 +200,7 @@
},
Ok(v) => Ok(Some(v)),
}
- .context("Failed to load key entry.")?;
+ .context(ks_err!("Failed to load key entry."))?;
let mut db = db.borrow_mut();
if let Some((key_id_guard, _key_entry)) = entry {
@@ -236,7 +235,7 @@
};
// 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(
@@ -248,7 +247,7 @@
.context("Failed to insert new certificate.")?;
Ok(())
})
- .context("In update_subcomponent.")
+ .context(ks_err!())
}
fn list_entries(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
@@ -258,10 +257,12 @@
nspace: ThreadState::get_calling_uid() as u64 as i64,
..Default::default()
},
- Domain::SELINUX => KeyDescriptor{domain, nspace: namespace, ..Default::default()},
- _ => return Err(Error::perm()).context(
- "In list_entries: List entries is only supported for Domain::APP and Domain::SELINUX."
- ),
+ Domain::SELINUX => KeyDescriptor { domain, nspace: namespace, ..Default::default() },
+ _ => {
+ return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!(
+ "List entries is only supported for Domain::APP and Domain::SELINUX."
+ ))
+ }
};
// First we check if the caller has the info permission for the selected domain/namespace.
@@ -269,51 +270,36 @@
// If the first check fails we check if the caller has the list permission allowing to list
// any namespace. In that case we also adjust the queried namespace if a specific uid was
// selected.
- match check_key_permission(KeyPerm::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.")?;
+ 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(ks_err!("While checking keystore permission."))?;
+ if namespace != -1 {
+ k.nspace = namespace;
}
+ } else {
+ return Err(e).context(ks_err!("While checking key permission."))?;
}
- Ok(()) => {}
- };
+ }
- 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)
+ DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace))
}
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_MIGRATOR.with_try_migrate(key, caller_uid, || {
+ 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.")
+ check_key_permission(KeyPerm::Delete, k, &av).context("During delete_key.")
})
})
})
- .context("In delete_key: Trying to unbind the key.")?;
+ .context(ks_err!("Trying to unbind the key."))?;
Ok(())
}
@@ -324,8 +310,11 @@
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_MIGRATOR.with_try_migrate(key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().grant(
key,
caller_uid,
@@ -335,16 +324,16 @@
)
})
})
- .context("In KeystoreService::grant.")
+ .context(ks_err!("KeystoreService::grant."))
}
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)
+ check_key_permission(KeyPerm::Grant, k, &None)
})
})
- .context("In KeystoreService::ungrant.")
+ .context(ks_err!("KeystoreService::ungrant."))
}
}
@@ -356,13 +345,13 @@
fn getSecurityLevel(
&self,
security_level: SecurityLevel,
- ) -> binder::public_api::Result<Strong<dyn IKeystoreSecurityLevel>> {
+ ) -> binder::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::public_api::Result<KeyEntryResponse> {
+ fn getKeyEntry(&self, key: &KeyDescriptor) -> binder::Result<KeyEntryResponse> {
let _wp = wd::watch_millis("IKeystoreService::get_key_entry", 500);
map_or_log_err(self.get_key_entry(key), Ok)
}
@@ -371,19 +360,15 @@
key: &KeyDescriptor,
public_cert: Option<&[u8]>,
certificate_chain: Option<&[u8]>,
- ) -> binder::public_api::Result<()> {
+ ) -> binder::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::public_api::Result<Vec<KeyDescriptor>> {
+ fn listEntries(&self, domain: Domain, namespace: i64) -> binder::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::public_api::Result<()> {
+ fn deleteKey(&self, key: &KeyDescriptor) -> binder::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());
@@ -394,11 +379,11 @@
key: &KeyDescriptor,
grantee_uid: i32,
access_vector: i32,
- ) -> binder::public_api::Result<KeyDescriptor> {
+ ) -> binder::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::public_api::Result<()> {
+ fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> binder::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 1862f73..42d38d2 100644
--- a/keystore2/src/shared_secret_negotiation.rs
+++ b/keystore2/src/shared_secret_negotiation.rs
@@ -15,6 +15,7 @@
//! 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::{
@@ -43,6 +44,10 @@
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();
});
}
@@ -278,3 +283,48 @@
}
}
}
+
+/// 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 a1e4c48..f000213 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -25,12 +25,11 @@
error::Error,
error::ResponseCode,
key_parameter::{KeyParameter, KeyParameterValue},
+ ks_err,
legacy_blob::LegacyBlobLoader,
- legacy_migrator::LegacyMigrator,
+ legacy_importer::LegacyImporter,
raw_device::KeyMintDevice,
- try_insert::TryInsert,
- utils::watchdog as wd,
- utils::AID_KEYSTORE,
+ utils::{watchdog as wd, AesGcm, AID_KEYSTORE},
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, BlockMode::BlockMode, HardwareAuthToken::HardwareAuthToken,
@@ -50,7 +49,7 @@
use std::{
collections::HashMap,
sync::Arc,
- sync::{Mutex, Weak},
+ sync::{Mutex, RwLock, Weak},
};
use std::{convert::TryFrom, ops::Deref};
@@ -75,9 +74,9 @@
/// 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 {
- /// Alias used to look the key up in the `persistent.keyentry` table.
- pub alias: &'static str,
+pub struct SuperKeyType<'a> {
+ /// Alias used to look up the key in the `persistent.keyentry` table.
+ pub alias: &'a str,
/// Encryption algorithm
pub algorithm: SuperEncryptionAlgorithm,
}
@@ -155,15 +154,20 @@
reencrypt_with: Option<Arc<SuperKey>>,
}
-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> {
+impl AesGcm for SuperKey {
+ fn 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 aes_gcm_decrypt: decryption failed")
+ aes_gcm_decrypt(data, iv, tag, &self.key).context(ks_err!("Decryption failed."))
} else {
- Err(Error::sys()).context("In aes_gcm_decrypt: Key is not an AES key")
+ Err(Error::sys()).context(ks_err!("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(ks_err!("Encryption failed."))
+ } else {
+ Err(Error::sys()).context(ks_err!("Key is not an AES key."))
}
}
}
@@ -198,7 +202,7 @@
.as_ref()
.map(|(key_blob, _)| KeyBlob::Ref(key_blob))
.ok_or(Error::Rc(ResponseCode::KEY_NOT_FOUND))
- .context("In LockedKey::decrypt: Missing key blob info.")?;
+ .context(ks_err!("Missing key blob info."))?;
let key_params = vec![
KeyParameterValue::Algorithm(Algorithm::AES),
KeyParameterValue::KeySize(256),
@@ -256,7 +260,7 @@
struct SkmState {
user_keys: HashMap<UserId, UserSuperKeys>,
key_index: HashMap<i64, Weak<SuperKey>>,
- boot_level_key_cache: Option<BootLevelKeyCache>,
+ boot_level_key_cache: Option<Mutex<BootLevelKeyCache>>,
}
impl SkmState {
@@ -265,34 +269,31 @@
self.key_index.insert(id, Arc::downgrade(super_key));
Ok(())
} else {
- Err(Error::sys()).context(format!(
- "In add_key_to_key_index: cannot add key with ID {:?}",
- super_key.id
- ))
+ Err(Error::sys()).context(ks_err!("Cannot add key with ID {:?}", super_key.id))
}
}
}
#[derive(Default)]
pub struct SuperKeyManager {
- data: Mutex<SkmState>,
+ data: SkmState,
}
impl SuperKeyManager {
- 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() {
+ 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() {
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")?;
- data.boot_level_key_cache = Some(BootLevelKeyCache::new(level_zero_key));
+ let level_zero_key =
+ get_level_zero_key(db).context(ks_err!("get_level_zero_key failed"))?;
+ skm_guard.data.boot_level_key_cache =
+ Some(Mutex::new(BootLevelKeyCache::new(level_zero_key)));
log::info!("Starting boot level watcher.");
- let clone = self.clone();
+ let clone = skm.clone();
std::thread::spawn(move || {
- clone
- .watch_boot_level()
+ Self::watch_boot_level(clone)
.unwrap_or_else(|e| log::error!("watch_boot_level failed:\n{:?}", e));
});
Ok(())
@@ -300,70 +301,81 @@
/// 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(&self) -> Result<()> {
+ fn watch_boot_level(skm: Arc<RwLock<Self>>) -> Result<()> {
let mut w = PropertyWatcher::new("keystore.boot_level")
- .context("In watch_boot_level: PropertyWatcher::new failed")?;
+ .context(ks_err!("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")?;
- // 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
+ .context(ks_err!("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
.as_mut()
- .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;
+ .ok_or_else(Error::sys)
+ .context(ks_err!("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(ks_err!("advance_boot_level failed"))?;
+ } else {
+ log::info!(
+ "keystore.boot_level {} hits maximum {}, finishing.",
+ level,
+ MAX_MAX_BOOT_LEVEL
+ );
+ boot_level_key_cache.finish();
+ break;
+ }
}
- w.wait().context("In watch_boot_level: property wait failed")?;
+ w.wait().context(ks_err!("property wait failed"))?;
}
Ok(())
}
pub fn level_accessible(&self, boot_level: i32) -> bool {
self.data
- .lock()
- .unwrap()
.boot_level_key_cache
.as_ref()
- .map_or(false, |c| c.level_accessible(boot_level as usize))
+ .map_or(false, |c| c.lock().unwrap().level_accessible(boot_level as usize))
}
- pub fn forget_all_keys_for_user(&self, user: UserId) {
- let mut data = self.data.lock().unwrap();
- data.user_keys.remove(&user);
+ pub fn forget_all_keys_for_user(&mut self, user: UserId) {
+ self.data.user_keys.remove(&user);
}
- 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")?;
- data.user_keys.entry(user).or_default().per_boot = Some(super_key);
+ 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)
+ .context(ks_err!("add_key_to_key_index failed"))?;
+ self.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) => data.key_index.get(id).and_then(|k| k.upgrade()),
- SuperKeyIdentifier::BootLevel(level) => data
+ SuperKeyIdentifier::DatabaseId(id) => {
+ self.data.key_index.get(id).and_then(|k| k.upgrade())
+ }
+ SuperKeyIdentifier::BootLevel(level) => self
+ .data
.boot_level_key_cache
- .as_mut()
- .map(|b| b.aes_key(*level as usize))
+ .as_ref()
+ .map(|b| b.lock().unwrap().aes_key(*level as usize))
.transpose()
- .context("In lookup_key: aes_key failed")?
+ .context(ks_err!("aes_key failed"))?
.flatten()
.map(|key| {
Arc::new(SuperKey {
@@ -376,9 +388,16 @@
})
}
- 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())
+ 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())
}
/// This function unlocks the super keys for a given user.
@@ -386,7 +405,7 @@
/// 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(
- &self,
+ &mut self,
db: &mut KeystoreDB,
user: UserId,
pw: &Password,
@@ -402,12 +421,12 @@
// For backward compatibility we need to check if there is a super key present.
let super_key = legacy_blob_loader
.load_super_key(user, pw)
- .context("In create_new_key: Failed to load legacy key blob.")?;
+ .context(ks_err!("Failed to load legacy key blob."))?;
let super_key = match super_key {
None => {
// No legacy file was found. So we generate a new key.
generate_aes256_key()
- .context("In create_new_key: Failed to generate AES 256 key.")?
+ .context(ks_err!("Failed to generate AES 256 key."))?
}
Some(key) => key,
};
@@ -419,10 +438,10 @@
Self::encrypt_with_password(&super_key, pw).context("In create_new_key.")
},
)
- .context("In unlock_user_key: Failed to get key id.")?;
+ .context(ks_err!("Failed to get key id."))?;
self.populate_cache_from_super_key_blob(user, USER_SUPER_KEY.algorithm, entry, pw)
- .context("In unlock_user_key.")?;
+ .context(ks_err!())?;
Ok(())
}
@@ -436,12 +455,12 @@
Ok(if let Some(key_id) = SuperKeyIdentifier::from_metadata(metadata) {
let super_key = self
.lookup_key(&key_id)
- .context("In unwrap_key: lookup_key failed")?
+ .context(ks_err!("lookup_key failed"))?
.ok_or(Error::Rc(ResponseCode::LOCKED))
- .context("In unwrap_key: Required super decryption key is not in memory.")?;
+ .context(ks_err!("Required super decryption key is not in memory."))?;
KeyBlob::Sensitive {
key: Self::unwrap_key_with_key(blob, metadata, &super_key)
- .context("In unwrap_key: unwrap_key_with_key failed")?,
+ .context(ks_err!("unwrap_key_with_key failed"))?,
reencrypt_with: super_key.reencrypt_with.as_ref().unwrap_or(&super_key).clone(),
force_reencrypt: super_key.reencrypt_with.is_some(),
}
@@ -454,14 +473,11 @@
fn unwrap_key_with_key(blob: &[u8], metadata: &BlobMetaData, key: &SuperKey) -> Result<ZVec> {
match key.algorithm {
SuperEncryptionAlgorithm::Aes256Gcm => match (metadata.iv(), metadata.aead_tag()) {
- (Some(iv), Some(tag)) => key
- .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!(
- "In unwrap_key_with_key: Key has incomplete metadata.",
- "Present: iv: {}, aead_tag: {}."
- ),
+ (Some(iv), Some(tag)) => {
+ key.decrypt(blob, iv, tag).context(ks_err!("Failed to decrypt the key blob."))
+ }
+ (iv, tag) => Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!(
+ "Key has incomplete metadata. Present: iv: {}, aead_tag: {}.",
iv.is_some(),
tag.is_some(),
)),
@@ -471,14 +487,12 @@
(Some(public_key), Some(salt), Some(iv), Some(aead_tag)) => {
ECDHPrivateKey::from_private_key(&key.key)
.and_then(|k| k.decrypt_message(public_key, salt, iv, blob, aead_tag))
- .context(
- "In unwrap_key_with_key: Failed to decrypt the key blob with ECDH.",
- )
+ .context(ks_err!("Failed to decrypt the key blob with ECDH."))
}
(public_key, salt, iv, aead_tag) => {
- Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
+ Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!(
concat!(
- "In unwrap_key_with_key: Key has incomplete metadata.",
+ "Key has incomplete metadata. ",
"Present: public_key: {}, salt: {}, iv: {}, aead_tag: {}."
),
public_key.is_some(),
@@ -493,21 +507,22 @@
}
/// Checks if user has setup LSKF, even when super key cache is empty for the user.
- pub fn super_key_exists_in_db_for_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,
db: &mut KeystoreDB,
- legacy_migrator: &LegacyMigrator,
+ legacy_importer: &LegacyImporter,
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)
- .context("In super_key_exists_in_db_for_user.")?;
+ .context(ks_err!())?;
if key_in_db {
Ok(key_in_db)
} else {
- legacy_migrator
- .has_super_key(user_id)
- .context("In super_key_exists_in_db_for_user: Trying to query legacy db.")
+ legacy_importer.has_super_key(user_id).context(ks_err!("Trying to query legacy db."))
}
}
@@ -515,22 +530,22 @@
/// 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(
- &self,
+ &mut self,
db: &mut KeystoreDB,
- legacy_migrator: &LegacyMigrator,
+ legacy_importer: &LegacyImporter,
user_id: UserId,
pw: &Password,
) -> Result<UserState> {
let alias = &USER_SUPER_KEY;
- 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")?;
+ let result = legacy_importer
+ .with_try_import_super_key(user_id, pw, || db.load_super_key(alias, user_id))
+ .context(ks_err!("Failed to load super key"))?;
match result {
Some((_, entry)) => {
let super_key = self
.populate_cache_from_super_key_blob(user_id, alias.algorithm, entry, pw)
- .context("In check_and_unlock_super_key.")?;
+ .context(ks_err!())?;
Ok(UserState::LskfUnlocked(super_key))
}
None => Ok(UserState::Uninitialized),
@@ -544,26 +559,25 @@
/// and return LskfUnlocked state.
/// If the password is not provided, return Uninitialized state.
pub fn check_and_initialize_super_key(
- &self,
+ &mut self,
db: &mut KeystoreDB,
- legacy_migrator: &LegacyMigrator,
+ legacy_importer: &LegacyImporter,
user_id: UserId,
pw: Option<&Password>,
) -> Result<UserState> {
- let super_key_exists_in_db =
- Self::super_key_exists_in_db_for_user(db, legacy_migrator, 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_importer, user_id)
+ .context(ks_err!("Failed to check if super key exists."))?;
if super_key_exists_in_db {
Ok(UserState::LskfLocked)
} else if let Some(pw) = pw {
- //generate a new super key.
- let super_key = generate_aes256_key()
- .context("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.
- let (encrypted_super_key, blob_metadata) = Self::encrypt_with_password(&super_key, pw)
- .context("In check_and_initialize_super_key.")?;
+ // Generate a new super key.
+ let super_key =
+ generate_aes256_key().context(ks_err!("Failed to generate AES 256 key."))?;
+ // Derive an AES256 key from the password and re-encrypt the super key
+ // before we insert it in the database.
+ let (encrypted_super_key, blob_metadata) =
+ Self::encrypt_with_password(&super_key, pw).context(ks_err!())?;
let key_entry = db
.store_super_key(
@@ -573,7 +587,7 @@
&blob_metadata,
&KeyMetaData::new(),
)
- .context("In check_and_initialize_super_key. Failed to store super key.")?;
+ .context(ks_err!("Failed to store super key."))?;
let super_key = self
.populate_cache_from_super_key_blob(
@@ -582,30 +596,28 @@
key_entry,
pw,
)
- .context("In check_and_initialize_super_key.")?;
+ .context(ks_err!())?;
Ok(UserState::LskfUnlocked(super_key))
} else {
Ok(UserState::Uninitialized)
}
}
- //helper function to populate super key cache from the super key blob loaded from the database
+ // Helper function to populate super key cache from the super key blob loaded from the database.
fn populate_cache_from_super_key_blob(
- &self,
+ &mut self,
user_id: UserId,
algorithm: SuperEncryptionAlgorithm,
entry: KeyEntry,
pw: &Password,
) -> Result<Arc<SuperKey>> {
let super_key = Self::extract_super_key_from_key_entry(algorithm, entry, pw, None)
- .context(
- "In populate_cache_from_super_key_blob. Failed to extract super key from key entry",
- )?;
+ .context(ks_err!("Failed to extract super key from key entry"))?;
self.install_per_boot_key_for_user(user_id, super_key.clone())?;
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,
@@ -620,21 +632,20 @@
metadata.aead_tag(),
) {
(Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => {
- // Note that password encryption is AES no matter the value of algorithm
- let key = pw.derive_key(Some(salt), AES_256_KEY_LENGTH).context(
- "In extract_super_key_from_key_entry: Failed to generate key from password.",
- )?;
+ // Note that password encryption is AES no matter the value of algorithm.
+ let key = pw
+ .derive_key(salt, AES_256_KEY_LENGTH)
+ .context(ks_err!("Failed to generate key from password."))?;
- aes_gcm_decrypt(blob, iv, tag, &key).context(
- "In extract_super_key_from_key_entry: Failed to decrypt key blob.",
- )?
+ aes_gcm_decrypt(blob, iv, tag, &key)
+ .context(ks_err!("Failed to decrypt key blob."))?
}
(enc_by, salt, iv, tag) => {
- return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
+ return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!(
concat!(
- "In extract_super_key_from_key_entry: Super key has incomplete metadata.",
- "encrypted_by: {:?}; Present: salt: {}, iv: {}, aead_tag: {}."
- ),
+ "Super key has incomplete metadata.",
+ "encrypted_by: {:?}; Present: salt: {}, iv: {}, aead_tag: {}."
+ ),
enc_by,
salt.is_some(),
iv.is_some(),
@@ -649,8 +660,7 @@
reencrypt_with,
}))
} else {
- Err(Error::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In extract_super_key_from_key_entry: No key blob info.")
+ Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!("No key blob info."))
}
}
@@ -661,13 +671,13 @@
) -> Result<(Vec<u8>, BlobMetaData)> {
let salt = generate_salt().context("In encrypt_with_password: Failed to generate salt.")?;
let derived_key = pw
- .derive_key(Some(&salt), AES_256_KEY_LENGTH)
- .context("In encrypt_with_password: Failed to derive password.")?;
+ .derive_key(&salt, AES_256_KEY_LENGTH)
+ .context(ks_err!("Failed to derive password."))?;
let mut metadata = BlobMetaData::new();
metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
metadata.add(BlobMetaEntry::Salt(salt));
let (encrypted_key, iv, tag) = aes_gcm_encrypt(super_key, &derived_key)
- .context("In encrypt_with_password: Failed to encrypt new super key.")?;
+ .context(ks_err!("Failed to encrypt new super key."))?;
metadata.add(BlobMetaEntry::Iv(iv));
metadata.add(BlobMetaEntry::AeadTag(tag));
Ok((encrypted_key, metadata))
@@ -680,39 +690,39 @@
fn super_encrypt_on_key_init(
&self,
db: &mut KeystoreDB,
- legacy_migrator: &LegacyMigrator,
+ legacy_importer: &LegacyImporter,
user_id: UserId,
key_blob: &[u8],
) -> Result<(Vec<u8>, BlobMetaData)> {
- match UserState::get(db, legacy_migrator, self, user_id)
- .context("In super_encrypt. Failed to get user state.")?
+ match self
+ .get_user_state(db, legacy_importer, user_id)
+ .context(ks_err!("Failed to get user state."))?
{
UserState::LskfUnlocked(super_key) => {
Self::encrypt_with_aes_super_key(key_blob, &super_key)
- .context("In super_encrypt_on_key_init. Failed to encrypt the key.")
+ .context(ks_err!("Failed to encrypt the key."))
}
UserState::LskfLocked => {
- Err(Error::Rc(ResponseCode::LOCKED)).context("In super_encrypt. Device is locked.")
+ Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked."))
}
UserState::Uninitialized => Err(Error::Rc(ResponseCode::UNINITIALIZED))
- .context("In super_encrypt. LSKF is not setup for the user."),
+ .context(ks_err!("LSKF is not setup for the user.")),
}
}
- //Helper function to encrypt a key with the given super key. Callers should select which super
- //key to be used. This is called when a key is super encrypted at its creation as well as at its
- //upgrade.
+ // 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,
) -> Result<(Vec<u8>, BlobMetaData)> {
if super_key.algorithm != SuperEncryptionAlgorithm::Aes256Gcm {
- return Err(Error::sys())
- .context("In encrypt_with_aes_super_key: unexpected algorithm");
+ return Err(Error::sys()).context(ks_err!("unexpected algorithm"));
}
let mut metadata = BlobMetaData::new();
let (encrypted_key, iv, tag) = aes_gcm_encrypt(key_blob, &(super_key.key))
- .context("In encrypt_with_aes_super_key: Failed to encrypt new super key.")?;
+ .context(ks_err!("Failed to encrypt new super key."))?;
metadata.add(BlobMetaEntry::Iv(iv));
metadata.add(BlobMetaEntry::AeadTag(tag));
super_key.id.add_to_metadata(&mut metadata);
@@ -725,7 +735,7 @@
pub fn handle_super_encryption_on_key_init(
&self,
db: &mut KeystoreDB,
- legacy_migrator: &LegacyMigrator,
+ legacy_importer: &LegacyImporter,
domain: &Domain,
key_parameters: &[KeyParameter],
flags: Option<i32>,
@@ -735,38 +745,30 @@
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_migrator, user_id, key_blob)
- .context(concat!(
- "In handle_super_encryption_on_key_init. ",
- "Failed to super encrypt with LskfBound key."
- )),
+ .super_encrypt_on_key_init(db, legacy_importer, user_id, key_blob)
+ .context(ks_err!("Failed to super encrypt with LskfBound key.")),
SuperEncryptionType::ScreenLockBound => {
- let 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."
- ))
+ let entry =
+ self.data.user_keys.get(&user_id).and_then(|e| e.screen_lock_bound.as_ref());
+ if let Some(super_key) = entry {
+ Self::encrypt_with_aes_super_key(key_blob, super_key)
+ .context(ks_err!("Failed to encrypt with ScreenLockBound key."))
} else {
// Symmetric key is not available, use public key encryption
- let loaded =
- db.load_super_key(&USER_SCREEN_LOCK_BOUND_P521_KEY, user_id).context(
- "In handle_super_encryption_on_key_init: load_super_key failed.",
- )?;
- let (key_id_guard, key_entry) = loaded.ok_or_else(Error::sys).context(
- "In handle_super_encryption_on_key_init: User ECDH key missing.",
- )?;
- let public_key =
- key_entry.metadata().sec1_public_key().ok_or_else(Error::sys).context(
- "In handle_super_encryption_on_key_init: sec1_public_key missing.",
- )?;
+ let loaded = db
+ .load_super_key(&USER_SCREEN_LOCK_BOUND_P521_KEY, user_id)
+ .context(ks_err!("load_super_key failed."))?;
+ let (key_id_guard, key_entry) =
+ loaded.ok_or_else(Error::sys).context(ks_err!("User ECDH key missing."))?;
+ let public_key = key_entry
+ .metadata()
+ .sec1_public_key()
+ .ok_or_else(Error::sys)
+ .context(ks_err!("sec1_public_key missing."))?;
let mut metadata = BlobMetaData::new();
let (ephem_key, salt, iv, encrypted_key, aead_tag) =
- ECDHPrivateKey::encrypt_message(public_key, key_blob).context(concat!(
- "In handle_super_encryption_on_key_init: ",
- "ECDHPrivateKey::encrypt_message failed."
- ))?;
+ ECDHPrivateKey::encrypt_message(public_key, key_blob)
+ .context(ks_err!("ECDHPrivateKey::encrypt_message failed."))?;
metadata.add(BlobMetaEntry::PublicKey(ephem_key));
metadata.add(BlobMetaEntry::Salt(salt));
metadata.add(BlobMetaEntry::Iv(iv));
@@ -780,13 +782,11 @@
let key_id = SuperKeyIdentifier::BootLevel(level);
let super_key = self
.lookup_key(&key_id)
- .context("In handle_super_encryption_on_key_init: lookup_key failed")?
+ .context(ks_err!("lookup_key failed"))?
.ok_or(Error::Rc(ResponseCode::LOCKED))
- .context("In handle_super_encryption_on_key_init: Boot stage key absent")?;
- Self::encrypt_with_aes_super_key(key_blob, &super_key).context(concat!(
- "In handle_super_encryption_on_key_init: ",
- "Failed to encrypt with BootLevel key."
- ))
+ .context(ks_err!("Boot stage key absent"))?;
+ Self::encrypt_with_aes_super_key(key_blob, &super_key)
+ .context(ks_err!("Failed to encrypt with BootLevel key."))
}
}
}
@@ -802,7 +802,7 @@
KeyBlob::Sensitive { reencrypt_with: super_key, .. } => {
let (key, metadata) =
Self::encrypt_with_aes_super_key(key_after_upgrade, super_key)
- .context("In reencrypt_if_required: Failed to re-super-encrypt key.")?;
+ .context(ks_err!("Failed to re-super-encrypt key."))?;
Ok((KeyBlob::NonSensitive(key), Some(metadata)))
}
_ => Ok((KeyBlob::Ref(key_after_upgrade), None)),
@@ -813,6 +813,7 @@
/// 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,
@@ -830,28 +831,22 @@
} else {
let (super_key, public_key) = match key_type.algorithm {
SuperEncryptionAlgorithm::Aes256Gcm => (
- generate_aes256_key()
- .context("In get_or_create_super_key: Failed to generate AES 256 key.")?,
+ generate_aes256_key().context(ks_err!("Failed to generate AES 256 key."))?,
None,
),
SuperEncryptionAlgorithm::EcdhP521 => {
let key = ECDHPrivateKey::generate()
- .context("In get_or_create_super_key: Failed to generate ECDH key")?;
+ .context(ks_err!("Failed to generate ECDH key"))?;
(
- key.private_key()
- .context("In get_or_create_super_key: private_key failed")?,
- Some(
- key.public_key()
- .context("In get_or_create_super_key: public_key failed")?,
- ),
+ key.private_key().context(ks_err!("private_key failed"))?,
+ Some(key.public_key().context(ks_err!("public_key failed"))?),
)
}
};
- //derive an AES256 key from the password and re-encrypt the super key
- //before we insert it in the database.
+ // 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.")?;
+ Self::encrypt_with_password(&super_key, password).context(ks_err!())?;
let mut key_metadata = KeyMetaData::new();
if let Some(pk) = public_key {
key_metadata.add(KeyMetaEntry::Sec1PublicKey(pk));
@@ -864,7 +859,7 @@
&blob_metadata,
&key_metadata,
)
- .context("In get_or_create_super_key. Failed to store super key.")?;
+ .context(ks_err!("Failed to store super key."))?;
Ok(Arc::new(SuperKey {
algorithm: key_type.algorithm,
key: super_key,
@@ -876,52 +871,64 @@
/// Decrypt the screen-lock bound keys for this user using the password and store in memory.
pub fn unlock_screen_lock_bound_key(
- &self,
+ &mut self,
db: &mut KeystoreDB,
user_id: UserId,
password: &Password,
) -> Result<()> {
- 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)?;
+ 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(ks_err!("Trying to get or create symmetric key."))?
+ };
+
+ let ecdh = if let Some(screen_lock_bound_private) = screen_lock_bound_private {
+ // This is weird. If this point is reached only one of the screen locked keys was
+ // initialized. This should never happen.
+ screen_lock_bound_private
+ } else {
+ self.get_or_create_super_key(
+ db,
+ user_id,
+ &USER_SCREEN_LOCK_BOUND_P521_KEY,
+ password,
+ Some(aes.clone()),
+ )
+ .context(ks_err!("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);
Ok(())
}
/// Wipe the screen-lock bound keys for this user from memory.
pub fn lock_screen_lock_bound_key(
- &self,
+ &mut self,
db: &mut KeystoreDB,
user_id: UserId,
unlocking_sids: &[i64],
) {
log::info!("Locking screen bound for user {} sids {:?}", user_id, unlocking_sids);
- let mut data = self.data.lock().unwrap();
- let mut entry = data.user_keys.entry(user_id).or_default();
+ let mut entry = self.data.user_keys.entry(user_id).or_default();
if !unlocking_sids.is_empty() {
if let (Some(aes), Some(ecdh)) = (
entry.screen_lock_bound.as_ref().cloned(),
@@ -935,7 +942,7 @@
let encrypting_key = generate_aes256_key()?;
let km_dev: KeyMintDevice =
KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
- .context("In lock_screen_lock_bound_key: KeyMintDevice::get failed")?;
+ .context(ks_err!("KeyMintDevice::get failed"))?;
let mut key_params = vec![
KeyParameterValue::Algorithm(Algorithm::AES),
KeyParameterValue::KeySize(256),
@@ -993,12 +1000,11 @@
/// 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(
- &self,
+ &mut self,
db: &mut KeystoreDB,
user_id: UserId,
) -> Result<()> {
- let mut data = self.data.lock().unwrap();
- let mut entry = data.user_keys.entry(user_id).or_default();
+ let mut entry = self.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(
@@ -1008,9 +1014,9 @@
AID_KEYSTORE,
|_, _| Ok(()),
)
- .context("In try_unlock_user_with_biometric: load_key_entry failed")?;
+ .context(ks_err!("load_key_entry failed"))?;
let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
- .context("In try_unlock_user_with_biometric: KeyMintDevice::get failed")?;
+ .context(ks_err!("KeyMintDevice::get failed"))?;
for sid in &biometric.sids {
if let Some((auth_token_entry, _)) = db.find_auth_token_entry(|entry| {
entry.auth_token().userId == *sid || entry.auth_token().authenticatorId == *sid
@@ -1038,16 +1044,13 @@
Ok((slb, slbp)) => {
entry.screen_lock_bound = Some(slb.clone());
entry.screen_lock_bound_private = Some(slbp.clone());
- 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"
- ));
+ self.data.add_key_to_key_index(&slb)?;
+ self.data.add_key_to_key_index(&slbp)?;
+ log::info!("Successfully unlocked with biometric");
return Ok(());
}
Err(e) => {
- log::warn!("In try_unlock_user_with_biometric: attempt failed: {:?}", e)
+ log::warn!("attempt failed: {:?}", e)
}
}
}
@@ -1055,6 +1058,121 @@
}
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,
+ db: &mut KeystoreDB,
+ legacy_importer: &LegacyImporter,
+ user_id: UserId,
+ ) -> Result<UserState> {
+ match self.get_per_boot_key_by_user_id_internal(user_id) {
+ Some(super_key) => Ok(UserState::LskfUnlocked(super_key)),
+ 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(ks_err!())?
+ {
+ Ok(UserState::LskfLocked)
+ } else {
+ Ok(UserState::Uninitialized)
+ }
+ }
+ }
+ }
+
+ /// 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,
+ db: &mut KeystoreDB,
+ legacy_importer: &LegacyImporter,
+ user_id: UserId,
+ password: Option<&Password>,
+ ) -> Result<UserState> {
+ match self.get_per_boot_key_by_user_id_internal(user_id) {
+ Some(_) if password.is_none() => {
+ // Transitioning to swiping, delete only the super key in database and cache,
+ // and super-encrypted keys in database (and in KM).
+ self.reset_user(db, legacy_importer, user_id, true)
+ .context(ks_err!("Trying to delete keys from the db."))?;
+ // Lskf is now removed in Keystore.
+ Ok(UserState::Uninitialized)
+ }
+ 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))
+ }
+ 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)
+ }
+ }
+ }
+
+ /// 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,
+ db: &mut KeystoreDB,
+ legacy_importer: &LegacyImporter,
+ user_id: UserId,
+ password: &Password,
+ ) -> Result<UserState> {
+ match self.get_per_boot_key_by_user_id_internal(user_id) {
+ Some(super_key) => {
+ log::info!("Trying to unlock when already unlocked.");
+ Ok(UserState::LskfUnlocked(super_key))
+ }
+ None => {
+ // Check if a super key exists in the database or legacy database.
+ // If not, return Uninitialized state.
+ // Otherwise, try to unlock the super key and if successful,
+ // return LskfUnlocked.
+ self.check_and_unlock_super_key(db, legacy_importer, user_id, password)
+ .context(ks_err!("Failed to unlock super key."))
+ }
+ }
+ }
+
+ /// Delete all the keys created on behalf of the user.
+ /// If 'keep_non_super_encrypted_keys' is set to true, delete only the super key and super
+ /// encrypted keys.
+ pub fn reset_user(
+ &mut self,
+ db: &mut KeystoreDB,
+ legacy_importer: &LegacyImporter,
+ user_id: UserId,
+ keep_non_super_encrypted_keys: bool,
+ ) -> Result<()> {
+ // Mark keys created on behalf of the user as unreferenced.
+ legacy_importer
+ .bulk_delete_user(user_id, keep_non_super_encrypted_keys)
+ .context(ks_err!("Trying to delete legacy keys."))?;
+ db.unbind_keys_for_user(user_id, keep_non_super_encrypted_keys)
+ .context(ks_err!("Error in unbinding keys."))?;
+
+ // Delete super key in cache, if exists.
+ self.forget_all_keys_for_user(user_id);
+ Ok(())
+ }
}
/// This enum represents different states of the user's life cycle in the device.
@@ -1072,110 +1190,6 @@
Uninitialized,
}
-impl UserState {
- pub fn get(
- db: &mut KeystoreDB,
- legacy_migrator: &LegacyMigrator,
- skm: &SuperKeyManager,
- user_id: UserId,
- ) -> Result<UserState> {
- 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 SuperKeyManager::super_key_exists_in_db_for_user(db, legacy_migrator, user_id)
- .context("In get.")?
- {
- Ok(UserState::LskfLocked)
- } else {
- Ok(UserState::Uninitialized)
- }
- }
- }
- }
-
- /// Queries user state when serving password change requests.
- pub fn get_with_password_changed(
- db: &mut KeystoreDB,
- legacy_migrator: &LegacyMigrator,
- skm: &SuperKeyManager,
- user_id: UserId,
- password: Option<&Password>,
- ) -> Result<UserState> {
- match skm.get_per_boot_key_by_user_id(user_id) {
- Some(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.
- skm.check_and_initialize_super_key(db, legacy_migrator, user_id, password)
- }
- }
- }
-
- /// Queries user state when serving password unlock requests.
- pub fn get_with_password_unlock(
- db: &mut KeystoreDB,
- legacy_migrator: &LegacyMigrator,
- skm: &SuperKeyManager,
- user_id: UserId,
- password: &Password,
- ) -> Result<UserState> {
- match skm.get_per_boot_key_by_user_id(user_id) {
- Some(super_key) => {
- 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 state
- skm.check_and_unlock_super_key(db, legacy_migrator, user_id, password)
- .context("In get_with_password_unlock. Failed to unlock super key.")
- }
- }
- }
-
- /// Delete all the keys created on behalf of the user.
- /// If 'keep_non_super_encrypted_keys' is set to true, delete only the super key and super
- /// encrypted keys.
- pub fn reset_user(
- db: &mut KeystoreDB,
- 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_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
- skm.forget_all_keys_for_user(user_id);
- Ok(())
- }
-}
-
/// 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.
diff --git a/keystore2/src/try_insert.rs b/keystore2/src/try_insert.rs
deleted file mode 100644
index 6dd3962..0000000
--- a/keystore2/src/try_insert.rs
+++ /dev/null
@@ -1,100 +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.
-
-//! 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 f6d92ee..75d98e2 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -15,11 +15,18 @@
//! This module implements utility functions used by the Keystore 2.0 service
//! implementation.
-use crate::error::{map_binder_status, Error, ErrorCode};
+use crate::error::{map_binder_status, map_km_error, Error, ErrorCode};
+use crate::key_parameter::KeyParameter;
+use crate::ks_err;
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::{
- KeyCharacteristics::KeyCharacteristics, Tag::Tag,
+ IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics,
+ KeyParameter::KeyParameter as KmKeyParameter, Tag::Tag,
};
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_security_apc::aidl::android::security::apc::{
@@ -27,15 +34,17 @@
ResponseCode::ResponseCode as ApcResponseCode,
};
use android_system_keystore2::aidl::android::system::keystore2::{
- Authorization::Authorization, KeyDescriptor::KeyDescriptor,
+ Authorization::Authorization, Domain::Domain, KeyDescriptor::KeyDescriptor,
};
-use anyhow::Context;
+use anyhow::{Context, Result};
use binder::{Strong, 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;
/// This function uses its namesake in the permission module and in
/// combination with with_calling_sid from the binder crate to check
@@ -43,9 +52,9 @@
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(
- "In check_keystore_permission: Cannot check permission without calling_sid.",
- )?,
+ calling_sid
+ .ok_or_else(Error::sys)
+ .context(ks_err!("Cannot check permission without calling_sid."))?,
perm,
)
})
@@ -57,9 +66,9 @@
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(
- "In check_grant_permission: Cannot check permission without calling_sid.",
- )?,
+ calling_sid
+ .ok_or_else(Error::sys)
+ .context(ks_err!("Cannot check permission without calling_sid."))?,
access_vec,
key,
)
@@ -79,7 +88,7 @@
ThreadState::get_calling_uid(),
calling_sid
.ok_or_else(Error::sys)
- .context("In check_key_permission: Cannot check permission without calling_sid.")?,
+ .context(ks_err!("Cannot check permission without calling_sid."))?,
perm,
key,
access_vector,
@@ -95,13 +104,25 @@
| Tag::ATTESTATION_ID_MEID
| Tag::ATTESTATION_ID_SERIAL
| Tag::DEVICE_UNIQUE_ATTESTATION
+ | Tag::ATTESTATION_ID_SECOND_IMEI
)
}
/// This function checks whether the calling app has the Android permissions needed to attest device
-/// identifiers. It throws an error if the permissions cannot be verified, or if the caller doesn't
-/// have the right permissions, and returns silently otherwise.
+/// identifiers. It throws an error if the permissions cannot be verified or if the caller doesn't
+/// have the right permissions. Otherwise it returns silently.
pub fn check_device_attestation_permissions() -> anyhow::Result<()> {
+ check_android_permission("android.permission.READ_PRIVILEGED_PHONE_STATE")
+}
+
+/// This function checks whether the calling app has the Android permissions needed to attest the
+/// device-unique identifier. It throws an error if the permissions cannot be verified or if the
+/// caller doesn't have the right permissions. Otherwise it returns silently.
+pub fn check_unique_id_attestation_permissions() -> anyhow::Result<()> {
+ check_android_permission("android.permission.REQUEST_UNIQUE_ID_ATTESTATION")
+}
+
+fn check_android_permission(permission: &str) -> anyhow::Result<()> {
let permission_controller: Strong<dyn IPermissionController::IPermissionController> =
binder::get_interface("permission")?;
@@ -111,19 +132,17 @@
500,
);
permission_controller.checkPermission(
- "android.permission.READ_PRIVILEGED_PHONE_STATE",
+ permission,
ThreadState::get_calling_pid(),
ThreadState::get_calling_uid() as i32,
)
};
- let has_permissions = map_binder_status(binder_result)
- .context("In check_device_attestation_permissions: checkPermission failed")?;
+ let has_permissions =
+ map_binder_status(binder_result).context(ks_err!("checkPermission failed"))?;
match has_permissions {
true => Ok(()),
- false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)).context(concat!(
- "In check_device_attestation_permissions: ",
- "caller does not have the permission to attest device IDs"
- )),
+ false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS))
+ .context(ks_err!("caller does not have the permission to attest device IDs")),
}
}
@@ -131,18 +150,57 @@
/// representation of the keystore service.
pub fn key_characteristics_to_internal(
key_characteristics: Vec<KeyCharacteristics>,
-) -> Vec<crate::key_parameter::KeyParameter> {
+) -> Vec<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| {
- crate::key_parameter::KeyParameter::new(aidl_kp.into(), sec_level)
- })
+ aidl_key_char
+ .authorizations
+ .into_iter()
+ .map(move |aidl_kp| 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(ks_err!("Upgrade failed."))?;
+
+ new_blob_handler(&upgraded_blob).context(ks_err!("calling new_blob_handler."))?;
+
+ km_op(&upgraded_blob)
+ .map(|v| (v, Some(upgraded_blob)))
+ .context(ks_err!("Calling km_op after upgrade."))
+ }
+ r => r.map(|v| (v, None)).context(ks_err!("Calling km_op.")),
+ }
+}
+
/// 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(
@@ -199,6 +257,28 @@
rustutils::users::multiuser_get_user_id(uid)
}
+/// List all key aliases for a given domain + namespace.
+pub fn list_key_entries(
+ db: &mut KeystoreDB,
+ domain: Domain,
+ namespace: i64,
+) -> Result<Vec<KeyDescriptor>> {
+ let mut result = Vec::new();
+ result.append(
+ &mut LEGACY_IMPORTER
+ .list_uid(domain, namespace)
+ .context(ks_err!("Trying to list legacy keys."))?,
+ );
+ result.append(
+ &mut db
+ .list(domain, namespace, KeyType::Client)
+ .context(ks_err!("Trying to list keystore database."))?,
+ );
+ result.sort_unstable();
+ result.dedup();
+ Ok(result)
+}
+
/// This module provides helpers for simplified use of the watchdog module.
#[cfg(feature = "watchdog")]
pub mod watchdog {
@@ -229,6 +309,35 @@
}
}
+/// 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(ks_err!("Decryption failed"))
+ }
+
+ fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
+ aes_gcm_encrypt(plaintext, self.key()).context(ks_err!("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/watchdog.rs b/keystore2/src/watchdog.rs
index 9cca171..a26b632 100644
--- a/keystore2/src/watchdog.rs
+++ b/keystore2/src/watchdog.rs
@@ -111,11 +111,44 @@
}
self.last_report = Instant::now();
self.has_overdue = has_overdue;
- log::warn!("Keystore Watchdog report:");
- log::warn!("Overdue records:");
+ log::warn!("### Keystore Watchdog report - BEGIN ###");
+
let now = Instant::now();
- for (i, r) in self.records.iter() {
- if r.deadline.saturating_duration_since(now) == Duration::new(0, 0) {
+ 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() {
match &r.callback {
Some(cb) => {
log::warn!(
@@ -139,6 +172,7 @@
}
}
}
+ log::warn!("### Keystore Watchdog report - END ###");
true
}
diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs
new file mode 100644
index 0000000..c2f0279
--- /dev/null
+++ b/keystore2/test_utils/authorizations.rs
@@ -0,0 +1,172 @@
+// 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, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
+ KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose,
+ PaddingMode::PaddingMode, Tag::Tag,
+};
+
+/// Helper struct to create set of Authorizations.
+#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
+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
+ }
+
+ /// Add No_auth_required.
+ pub fn no_auth_required(mut self) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::NO_AUTH_REQUIRED,
+ value: KeyParameterValue::BoolValue(true),
+ });
+ self
+ }
+
+ /// Add RSA_public_exponent.
+ pub fn rsa_public_exponent(mut self, e: i64) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::RSA_PUBLIC_EXPONENT,
+ value: KeyParameterValue::LongInteger(e),
+ });
+ self
+ }
+
+ /// Add key size.
+ pub fn key_size(mut self, s: i32) -> Self {
+ self.0.push(KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(s) });
+ self
+ }
+
+ /// Add block mode.
+ pub fn block_mode(mut self, b: BlockMode) -> Self {
+ self.0.push(KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(b) });
+ self
+ }
+
+ /// Add certificate_not_before.
+ pub fn cert_not_before(mut self, b: i64) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::CERTIFICATE_NOT_BEFORE,
+ value: KeyParameterValue::DateTime(b),
+ });
+ self
+ }
+
+ /// Add certificate_not_after.
+ pub fn cert_not_after(mut self, a: i64) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::CERTIFICATE_NOT_AFTER,
+ value: KeyParameterValue::DateTime(a),
+ });
+ self
+ }
+
+ /// Add padding mode.
+ pub fn padding_mode(mut self, p: PaddingMode) -> Self {
+ self.0.push(KeyParameter { tag: Tag::PADDING, value: KeyParameterValue::PaddingMode(p) });
+ self
+ }
+
+ /// Add mgf_digest.
+ pub fn mgf_digest(mut self, d: Digest) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::RSA_OAEP_MGF_DIGEST,
+ value: KeyParameterValue::Digest(d),
+ });
+ self
+ }
+
+ /// Add nonce.
+ pub fn nonce(mut self, b: Vec<u8>) -> Self {
+ self.0.push(KeyParameter { tag: Tag::NONCE, value: KeyParameterValue::Blob(b) });
+ self
+ }
+
+ /// Add MAC length.
+ pub fn mac_length(mut self, l: i32) -> Self {
+ self.0.push(KeyParameter { tag: Tag::MAC_LENGTH, value: KeyParameterValue::Integer(l) });
+ self
+ }
+
+ /// Add min MAC length.
+ pub fn min_mac_length(mut self, l: i32) -> Self {
+ self.0
+ .push(KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KeyParameterValue::Integer(l) });
+ self
+ }
+}
+
+impl Deref for AuthSetBuilder {
+ type Target = Vec<KeyParameter>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs
new file mode 100644
index 0000000..175d8bb
--- /dev/null
+++ b/keystore2/test_utils/key_generations.rs
@@ -0,0 +1,838 @@
+// 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 anyhow::Result;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
+ ErrorCode::ErrorCode, KeyOrigin::KeyOrigin, KeyParameter::KeyParameter,
+ KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
+ Tag::Tag,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Authorization::Authorization, Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
+ KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, ResponseCode::ResponseCode,
+};
+
+use crate::authorizations::AuthSetBuilder;
+use android_system_keystore2::binder::{ExceptionCode, Result as BinderResult};
+
+/// Shell namespace.
+pub const SELINUX_SHELL_NAMESPACE: i64 = 1;
+/// Vold namespace.
+pub const SELINUX_VOLD_NAMESPACE: i64 = 100;
+
+/// SU context.
+pub const TARGET_SU_CTX: &str = "u:r:su:s0";
+
+/// Vold context
+pub const TARGET_VOLD_CTX: &str = "u:r:vold:s0";
+
+/// Key parameters to generate a key.
+pub struct KeyParams {
+ /// Key Size.
+ pub key_size: i32,
+ /// Key Purposes.
+ pub purpose: Vec<KeyPurpose>,
+ /// Padding Mode.
+ pub padding: Option<PaddingMode>,
+ /// Digest.
+ pub digest: Option<Digest>,
+ /// MFG Digest.
+ pub mgf_digest: Option<Digest>,
+ /// Block Mode.
+ pub block_mode: Option<BlockMode>,
+ /// Attestation challenge.
+ pub att_challenge: Option<Vec<u8>>,
+ /// Attestation app id.
+ pub att_app_id: Option<Vec<u8>>,
+}
+
+/// DER-encoded PKCS#8 format RSA key. Generated using:
+/// openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt -outform der | hexdump -e '30/1 "%02X" "\n"'
+pub static RSA_2048_KEY: &[u8] = &[
+ 0x30, 0x82, 0x04, 0xBD, 0x02, 0x01, 0x00, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7,
+ 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x04, 0xA7, 0x30, 0x82, 0x04, 0xA3, 0x02, 0x01,
+ 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xE5, 0x14, 0xE3, 0xC2, 0x43, 0xF3, 0x0F, 0xCC, 0x22, 0x73,
+ 0x9C, 0x84, 0xCC, 0x1B, 0x6C, 0x97, 0x4B, 0xC9, 0xDF, 0x1F, 0xE2, 0xB8, 0x80, 0x85, 0xF9, 0x27,
+ 0xAB, 0x97, 0x94, 0x58, 0x4B, 0xC9, 0x40, 0x94, 0x5A, 0xB4, 0xD4, 0xF8, 0xD0, 0x36, 0xC4, 0x86,
+ 0x17, 0x7D, 0xA2, 0x48, 0x6D, 0x40, 0xF0, 0xB9, 0x61, 0x4F, 0xCE, 0x65, 0x80, 0x88, 0x81, 0x59,
+ 0x95, 0x11, 0x24, 0xF4, 0x36, 0xB7, 0xB7, 0x37, 0x44, 0xF4, 0x6C, 0x1C, 0xEB, 0x04, 0x19, 0x78,
+ 0xB2, 0x29, 0x4D, 0x21, 0x44, 0x16, 0x57, 0x58, 0x6D, 0x7D, 0x56, 0xB5, 0x99, 0xDD, 0xD2, 0xAD,
+ 0x02, 0x9A, 0x72, 0x16, 0x67, 0xD6, 0x00, 0x9F, 0x69, 0xE0, 0x25, 0xEE, 0x7C, 0x86, 0x54, 0x27,
+ 0x4B, 0x50, 0xEF, 0x60, 0x52, 0x60, 0x82, 0xAA, 0x09, 0x15, 0x72, 0xD2, 0xEB, 0x01, 0x52, 0x04,
+ 0x39, 0x60, 0xBC, 0x5E, 0x95, 0x07, 0xC8, 0xC2, 0x3A, 0x3A, 0xE2, 0xA4, 0x99, 0x6B, 0x27, 0xE3,
+ 0xA3, 0x55, 0x69, 0xC4, 0xB3, 0x2D, 0x19, 0xC4, 0x34, 0x76, 0xFC, 0x27, 0xDA, 0x22, 0xB2, 0x62,
+ 0x69, 0x25, 0xDE, 0x0D, 0xE7, 0x54, 0x3C, 0xBB, 0x61, 0xD2, 0x20, 0xDA, 0x7B, 0x6E, 0x63, 0xBD,
+ 0x9A, 0x4B, 0xCD, 0x75, 0xC6, 0xA1, 0x5E, 0x1C, 0x3E, 0xD5, 0x63, 0x59, 0x22, 0x7E, 0xE0, 0x6C,
+ 0x98, 0x25, 0x63, 0x97, 0x56, 0xDF, 0x71, 0xF5, 0x4C, 0x78, 0xE9, 0xE1, 0xD5, 0xFC, 0xF8, 0x5A,
+ 0x5B, 0xF6, 0x1D, 0xFA, 0x5A, 0x99, 0x4C, 0x99, 0x19, 0x21, 0x1D, 0xF5, 0x24, 0x07, 0xEF, 0x8A,
+ 0xC9, 0x9F, 0xE7, 0x3F, 0xBB, 0x46, 0x1A, 0x16, 0x96, 0xC6, 0xD6, 0x12, 0x7E, 0xDA, 0xCB, 0xEB,
+ 0x2F, 0x1D, 0x3B, 0x31, 0xCC, 0x55, 0x63, 0xA2, 0x6F, 0x8A, 0xDE, 0x35, 0x52, 0x40, 0x04, 0xBF,
+ 0xE0, 0x82, 0x32, 0xE1, 0x6D, 0x8B, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x2D,
+ 0x1F, 0x71, 0x41, 0x79, 0xBA, 0xED, 0xD8, 0xAA, 0xCC, 0x94, 0xFE, 0xFF, 0x69, 0x43, 0x79, 0x85,
+ 0xBF, 0x2C, 0xC9, 0x0E, 0x12, 0x83, 0x96, 0x60, 0x1E, 0x75, 0x49, 0x35, 0x3A, 0x33, 0x2B, 0x60,
+ 0x22, 0x18, 0xBF, 0xD7, 0xD7, 0x6E, 0xC3, 0xEA, 0xEF, 0xF2, 0xBE, 0x97, 0x71, 0xA6, 0xBB, 0x8C,
+ 0xEF, 0x27, 0x00, 0xDE, 0x49, 0xD6, 0x08, 0x8D, 0x5A, 0x04, 0xE7, 0xCC, 0x9C, 0xA2, 0x0E, 0x8B,
+ 0xF3, 0x42, 0x0C, 0xD7, 0x22, 0xD7, 0x14, 0x06, 0xA4, 0x64, 0x8B, 0x88, 0x1A, 0xCE, 0x5B, 0x8C,
+ 0x36, 0xE9, 0xD2, 0x2F, 0x7B, 0x33, 0xE4, 0xA2, 0xB3, 0xDB, 0x78, 0x6A, 0x92, 0x89, 0x3F, 0x78,
+ 0xFD, 0xED, 0x8F, 0xEE, 0x48, 0xCC, 0x94, 0x75, 0x0D, 0x0C, 0x63, 0xD3, 0xD2, 0xE8, 0x47, 0x04,
+ 0x55, 0xD3, 0xD6, 0x3A, 0xB8, 0xDA, 0xFB, 0x76, 0x99, 0x48, 0x68, 0x0A, 0x92, 0xA2, 0xCD, 0xF7,
+ 0x45, 0x8B, 0x50, 0xFE, 0xF9, 0x1A, 0x33, 0x24, 0x3C, 0x2E, 0xDE, 0x88, 0xAD, 0xB2, 0x5B, 0x9F,
+ 0x44, 0xEA, 0xD1, 0x9F, 0xC7, 0x9F, 0x02, 0x5E, 0x31, 0x61, 0xB3, 0xD6, 0xE2, 0xE1, 0xBC, 0xFB,
+ 0x1C, 0xDB, 0xBD, 0xB2, 0x9A, 0xE5, 0xEF, 0xDA, 0xCD, 0x29, 0xA5, 0x45, 0xCC, 0x67, 0x01, 0x8B,
+ 0x1C, 0x1D, 0x0E, 0x8F, 0x73, 0x69, 0x4D, 0x4D, 0xF6, 0x9D, 0xA6, 0x6C, 0x9A, 0x1C, 0xF4, 0x5C,
+ 0xE4, 0x83, 0x9A, 0x77, 0x12, 0x01, 0xBD, 0xCE, 0x66, 0x3A, 0x4B, 0x3D, 0x6E, 0xE0, 0x6E, 0x82,
+ 0x98, 0xDE, 0x74, 0x11, 0x47, 0xEC, 0x7A, 0x3A, 0xA9, 0xD8, 0x48, 0x00, 0x26, 0x64, 0x47, 0x7B,
+ 0xAE, 0x55, 0x9D, 0x29, 0x22, 0xB4, 0xB3, 0xB9, 0xB1, 0x64, 0xEA, 0x3B, 0x5A, 0xD3, 0x3F, 0x8D,
+ 0x0F, 0x14, 0x7E, 0x4E, 0xB8, 0x1B, 0x06, 0xFC, 0xB1, 0x7E, 0xCD, 0xB9, 0x1A, 0x4E, 0xA1, 0x02,
+ 0x81, 0x81, 0x00, 0xF9, 0xDE, 0xEE, 0xED, 0x13, 0x2F, 0xBB, 0xE7, 0xE2, 0xB3, 0x2D, 0x98, 0xD2,
+ 0xE8, 0x25, 0x07, 0x5A, 0x1E, 0x51, 0x0A, 0xC8, 0xAD, 0x50, 0x4B, 0x80, 0xC6, 0x22, 0xF5, 0x9B,
+ 0x08, 0xE6, 0x3D, 0x01, 0xC6, 0x3E, 0xC8, 0xD2, 0x54, 0x9F, 0x91, 0x77, 0x95, 0xCD, 0xCA, 0xC7,
+ 0xE7, 0x47, 0x94, 0xA9, 0x5F, 0x4E, 0xBE, 0x31, 0x3D, 0xB4, 0xAF, 0x43, 0x0F, 0xDC, 0x8D, 0x9C,
+ 0x1E, 0x52, 0x7B, 0x72, 0x21, 0x34, 0xB3, 0x96, 0x7C, 0x9C, 0xB8, 0x51, 0x65, 0x60, 0xAC, 0x3D,
+ 0x11, 0x32, 0xB8, 0xD6, 0x34, 0x35, 0x66, 0xD0, 0x30, 0xB9, 0xE9, 0x67, 0x2C, 0x87, 0x73, 0x43,
+ 0x9C, 0x12, 0x16, 0x7D, 0x4A, 0xD9, 0xA3, 0x4C, 0x24, 0x64, 0x6A, 0x32, 0x8E, 0xC3, 0xD8, 0x00,
+ 0x90, 0x5C, 0x4D, 0x65, 0x01, 0x53, 0x8A, 0xD0, 0x87, 0xCE, 0x96, 0xEF, 0xFA, 0x73, 0x03, 0xF1,
+ 0xDC, 0x1B, 0x9B, 0x02, 0x81, 0x81, 0x00, 0xEA, 0xB3, 0x69, 0x00, 0x11, 0x0E, 0x50, 0xAA, 0xD3,
+ 0x22, 0x51, 0x78, 0x9D, 0xFF, 0x05, 0x62, 0xBC, 0x9A, 0x67, 0x86, 0xE1, 0xC5, 0x02, 0x2D, 0x14,
+ 0x11, 0x29, 0x30, 0xE7, 0x90, 0x5D, 0x72, 0x6F, 0xC5, 0x62, 0xEB, 0xD4, 0xB0, 0x3F, 0x3D, 0xDC,
+ 0xB9, 0xFC, 0x2B, 0x5C, 0xBD, 0x9E, 0x71, 0x81, 0x5C, 0xC5, 0xFE, 0xDF, 0x69, 0x73, 0x12, 0x66,
+ 0x92, 0x06, 0xD4, 0xD5, 0x8F, 0xDF, 0x14, 0x2E, 0x9C, 0xD0, 0x4C, 0xC2, 0x4D, 0x31, 0x2E, 0x47,
+ 0xA5, 0xDC, 0x8A, 0x83, 0x7B, 0xE8, 0xA5, 0xC3, 0x03, 0x98, 0xD8, 0xBF, 0xF4, 0x7D, 0x6E, 0x87,
+ 0x55, 0xE4, 0x0F, 0x15, 0x10, 0xC8, 0x76, 0x4F, 0xAD, 0x1D, 0x1C, 0x95, 0x41, 0x9D, 0x88, 0xEC,
+ 0x8C, 0xDA, 0xBA, 0x90, 0x7F, 0x8D, 0xD9, 0x8B, 0x47, 0x6C, 0x0C, 0xFF, 0xBA, 0x73, 0x00, 0x20,
+ 0x1F, 0xF7, 0x7E, 0x5F, 0xF4, 0xEC, 0xD1, 0x02, 0x81, 0x80, 0x16, 0xB7, 0x43, 0xB5, 0x5D, 0xD7,
+ 0x2B, 0x18, 0x0B, 0xAE, 0x0A, 0x69, 0x28, 0x53, 0x5E, 0x7A, 0x6A, 0xA0, 0xF2, 0xF1, 0x2E, 0x09,
+ 0x43, 0x91, 0x79, 0xA5, 0x89, 0xAC, 0x16, 0x6A, 0x1A, 0xB4, 0x55, 0x22, 0xF6, 0xB6, 0x3F, 0x18,
+ 0xDE, 0x60, 0xD5, 0x24, 0x53, 0x4F, 0x2A, 0x19, 0x46, 0x92, 0xA7, 0x4B, 0x38, 0xD7, 0x65, 0x96,
+ 0x9C, 0x84, 0x8A, 0x6E, 0x38, 0xB8, 0xCF, 0x06, 0x9A, 0xAD, 0x0A, 0x55, 0x26, 0x7B, 0x65, 0x24,
+ 0xF3, 0x02, 0x76, 0xB3, 0xE6, 0xB4, 0x01, 0xE1, 0x3C, 0x61, 0x3D, 0x68, 0x05, 0xAA, 0xD1, 0x26,
+ 0x7C, 0xE0, 0x51, 0x36, 0xE5, 0x21, 0x7F, 0x76, 0x02, 0xD6, 0xF4, 0x91, 0x07, 0x74, 0x27, 0x09,
+ 0xEF, 0xEF, 0x0F, 0xA5, 0x96, 0xFC, 0x5E, 0x20, 0xC1, 0xA3, 0x6F, 0x99, 0x4D, 0x45, 0x03, 0x6C,
+ 0x35, 0x45, 0xD7, 0x8F, 0x47, 0x41, 0x86, 0x8D, 0x62, 0x1D, 0x02, 0x81, 0x81, 0x00, 0xC3, 0x93,
+ 0x85, 0xA7, 0xFC, 0x8E, 0x85, 0x42, 0x14, 0x76, 0xC0, 0x95, 0x56, 0x73, 0xB0, 0xB5, 0x3A, 0x9D,
+ 0x20, 0x30, 0x11, 0xEA, 0xED, 0x89, 0x4A, 0xF3, 0x91, 0xF3, 0xA2, 0xC3, 0x76, 0x5B, 0x6A, 0x30,
+ 0x7D, 0xE2, 0x2F, 0x76, 0x3E, 0xFC, 0xF9, 0xF6, 0x31, 0xE0, 0xA0, 0x83, 0x92, 0x88, 0xDB, 0x57,
+ 0xC7, 0xD6, 0x3F, 0xAD, 0xCB, 0xAA, 0x45, 0xB6, 0xE1, 0xE2, 0x71, 0xA4, 0x56, 0x2C, 0xA7, 0x3B,
+ 0x1D, 0x89, 0x19, 0x50, 0xE1, 0xEE, 0xC2, 0xDD, 0xC0, 0x0D, 0xDC, 0xCB, 0x60, 0x6E, 0xE1, 0x37,
+ 0x1A, 0x23, 0x64, 0xB2, 0x03, 0xE4, 0x1A, 0xFA, 0xC3, 0xF4, 0x9D, 0x85, 0x42, 0xC6, 0xF4, 0x56,
+ 0x39, 0xB0, 0x1B, 0xE0, 0x75, 0xBA, 0x28, 0x04, 0xA8, 0x30, 0x57, 0x41, 0x33, 0x9F, 0x58, 0xA4,
+ 0xC7, 0xB1, 0x7D, 0x58, 0x8D, 0x84, 0x49, 0x40, 0xDA, 0x28, 0x81, 0x25, 0xC4, 0x41, 0x02, 0x81,
+ 0x80, 0x13, 0x20, 0x65, 0xD5, 0x96, 0x98, 0x8D, 0x16, 0x73, 0xA1, 0x31, 0x73, 0x79, 0xBA, 0xEC,
+ 0xB0, 0xD9, 0x0C, 0xF6, 0xEF, 0x2F, 0xC2, 0xE7, 0x96, 0x9B, 0xA1, 0x2D, 0xE9, 0xFB, 0x45, 0xB9,
+ 0xD0, 0x30, 0xE2, 0xBD, 0x30, 0x4F, 0xB6, 0xFE, 0x24, 0x02, 0xCF, 0x8D, 0x51, 0x48, 0x45, 0xD9,
+ 0xF7, 0x20, 0x53, 0x1C, 0x0B, 0xA9, 0x7E, 0xC2, 0xA2, 0x65, 0xCC, 0x3E, 0x0E, 0x0D, 0xF1, 0x62,
+ 0xDD, 0x5F, 0xBC, 0x55, 0x9B, 0x58, 0x26, 0x40, 0x6A, 0xEE, 0x02, 0x55, 0x36, 0xE9, 0xBA, 0x82,
+ 0x5A, 0xFD, 0x3C, 0xDF, 0xA6, 0x26, 0x32, 0x81, 0xA9, 0x5E, 0x46, 0xBE, 0xBA, 0xDC, 0xD3, 0x2A,
+ 0x3A, 0x3B, 0xC1, 0x4E, 0xF7, 0x1A, 0xDC, 0x4B, 0xAF, 0x67, 0x1B, 0x3A, 0x83, 0x0D, 0x04, 0xDE,
+ 0x27, 0x47, 0xFC, 0xE6, 0x39, 0x89, 0x7B, 0x66, 0xF9, 0x50, 0x4D, 0xF1, 0xAC, 0x20, 0x43, 0x7E,
+ 0xEE,
+];
+
+/// DER-encoded PKCS#8 format EC key. Generated using:
+/// openssl ecparam -name prime256v1 -genkey | openssl pkcs8 -topk8 -nocrypt -outform der | hexdump -e '30/1 "%02X" "\n"'
+pub static EC_P_256_KEY: &[u8] = &[
+ 0x30, 0x81, 0x87, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+ 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x04, 0x6D, 0x30, 0x6B, 0x02,
+ 0x01, 0x01, 0x04, 0x20, 0xB9, 0x1D, 0xAF, 0x50, 0xFD, 0xD8, 0x6A, 0x40, 0xAB, 0x2C, 0xCB, 0x54,
+ 0x4E, 0xED, 0xF1, 0x64, 0xBC, 0x30, 0x25, 0xFB, 0xC4, 0x69, 0x00, 0x34, 0x1A, 0x82, 0xA3, 0x72,
+ 0x5D, 0xC7, 0xA9, 0x85, 0xA1, 0x44, 0x03, 0x42, 0x00, 0x04, 0xE8, 0x53, 0x0A, 0xF2, 0xD3, 0x68,
+ 0x40, 0x48, 0x8C, 0xB4, 0x2F, 0x11, 0x34, 0xD7, 0xF4, 0x4A, 0x5C, 0x33, 0xFF, 0xF6, 0x2B, 0xF7,
+ 0x98, 0x0F, 0x02, 0xA5, 0xD7, 0x4F, 0xF9, 0xDE, 0x60, 0x9C, 0x6E, 0xB0, 0x45, 0xDA, 0x3F, 0xF4,
+ 0x34, 0x23, 0x9B, 0x4C, 0x3A, 0x09, 0x9C, 0x5E, 0x5D, 0x37, 0x96, 0xAC, 0x4A, 0xE7, 0x65, 0x2B,
+ 0xD6, 0x84, 0x98, 0xEA, 0x96, 0x91, 0xFB, 0x78, 0xED, 0x86,
+];
+
+/// To map Keystore errors.
+#[derive(thiserror::Error, Debug, Eq, PartialEq)]
+pub enum Error {
+ /// Keystore2 error code
+ #[error("ResponseCode {0:?}")]
+ Rc(ResponseCode),
+ /// Keymint error code
+ #[error("ErrorCode {0:?}")]
+ Km(ErrorCode),
+ /// Exception
+ #[error("Binder exception {0:?}")]
+ Binder(ExceptionCode),
+ /// This is returned if the C implementation of extractSubjectFromCertificate failed.
+ #[error("Failed to validate certificate chain.")]
+ ValidateCertChainFailed,
+}
+
+/// Keystore2 error mapping.
+pub fn map_ks_error<T>(r: BinderResult<T>) -> Result<T, Error> {
+ r.map_err(|s| {
+ match s.exception_code() {
+ ExceptionCode::SERVICE_SPECIFIC => {
+ match s.service_specific_error() {
+ se if se < 0 => {
+ // Negative service specific errors are KM error codes.
+ Error::Km(ErrorCode(se))
+ }
+ se => {
+ // Positive service specific errors are KS response codes.
+ Error::Rc(ResponseCode(se))
+ }
+ }
+ }
+ // We create `Error::Binder` to preserve the exception code
+ // for logging.
+ e_code => Error::Binder(e_code),
+ }
+ })
+}
+
+/// Generate EC Key using given security level and domain with below key parameters and
+/// optionally allow the generated key to be attested with factory provisioned attest key using
+/// given challenge and application id -
+/// Purposes: SIGN and VERIFY
+/// Digest: SHA_2_256
+/// Curve: P_256
+pub fn generate_ec_p256_signing_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ att_challenge: Option<&[u8]>,
+ att_app_id: Option<&[u8]>,
+) -> binder::Result<KeyMetadata> {
+ let mut key_attest = false;
+ let mut gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256);
+
+ if let Some(challenge) = att_challenge {
+ key_attest = true;
+ gen_params = gen_params.clone().attestation_challenge(challenge.to_vec());
+ }
+
+ if let Some(app_id) = att_app_id {
+ key_attest = true;
+ gen_params = gen_params.clone().attestation_app_id(app_id.to_vec());
+ }
+
+ match sec_level.generateKey(
+ &KeyDescriptor { domain, nspace, alias, blob: None },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ ) {
+ Ok(key_metadata) => {
+ assert!(key_metadata.certificate.is_some());
+ if key_attest {
+ assert!(key_metadata.certificateChain.is_some());
+ }
+ if domain == Domain::BLOB {
+ assert!(key_metadata.key.blob.is_some());
+ }
+
+ Ok(key_metadata)
+ }
+ Err(e) => Err(e),
+ }
+}
+
+/// Generate EC signing key.
+pub fn generate_ec_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ ec_curve: EcCurve,
+ digest: Digest,
+) -> binder::Result<KeyMetadata> {
+ let gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(digest)
+ .ec_curve(ec_curve);
+
+ let key_metadata = sec_level.generateKey(
+ &KeyDescriptor { domain, nspace, alias, blob: None },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )?;
+
+ // Must have a public key.
+ assert!(key_metadata.certificate.is_some());
+
+ // Should not have an attestation record.
+ assert!(key_metadata.certificateChain.is_none());
+
+ if domain == Domain::BLOB {
+ assert!(key_metadata.key.blob.is_some());
+ } else {
+ assert!(key_metadata.key.blob.is_none());
+ }
+ Ok(key_metadata)
+}
+
+/// Generate a RSA key with the given key parameters, alias, domain and namespace.
+pub fn generate_rsa_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ key_params: &KeyParams,
+ attest_key: Option<&KeyDescriptor>,
+) -> binder::Result<KeyMetadata> {
+ let mut gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::RSA)
+ .rsa_public_exponent(65537)
+ .key_size(key_params.key_size);
+
+ for purpose in &key_params.purpose {
+ gen_params = gen_params.purpose(*purpose);
+ }
+ if let Some(value) = key_params.digest {
+ gen_params = gen_params.digest(value)
+ }
+ if let Some(value) = key_params.padding {
+ gen_params = gen_params.padding_mode(value);
+ }
+ if let Some(value) = key_params.mgf_digest {
+ gen_params = gen_params.mgf_digest(value);
+ }
+ if let Some(value) = key_params.block_mode {
+ gen_params = gen_params.block_mode(value)
+ }
+ if let Some(value) = &key_params.att_challenge {
+ gen_params = gen_params.attestation_challenge(value.to_vec())
+ }
+ if let Some(value) = &key_params.att_app_id {
+ gen_params = gen_params.attestation_app_id(value.to_vec())
+ }
+
+ let key_metadata = sec_level.generateKey(
+ &KeyDescriptor { domain, nspace, alias, blob: None },
+ attest_key,
+ &gen_params,
+ 0,
+ b"entropy",
+ )?;
+
+ // Must have a public key.
+ assert!(key_metadata.certificate.is_some());
+
+ if attest_key.is_none() && key_params.att_challenge.is_some() && key_params.att_app_id.is_some()
+ {
+ // Should have an attestation record.
+ assert!(key_metadata.certificateChain.is_some());
+ } else {
+ // Should not have an attestation record.
+ assert!(key_metadata.certificateChain.is_none());
+ }
+
+ assert!(
+ (domain == Domain::BLOB && key_metadata.key.blob.is_some())
+ || key_metadata.key.blob.is_none()
+ );
+
+ Ok(key_metadata)
+}
+
+/// Generate AES/3DES key.
+pub fn generate_sym_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ algorithm: Algorithm,
+ size: i32,
+ alias: &str,
+ padding_mode: &PaddingMode,
+ block_mode: &BlockMode,
+ min_mac_len: Option<i32>,
+) -> binder::Result<KeyMetadata> {
+ let mut gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(algorithm)
+ .purpose(KeyPurpose::ENCRYPT)
+ .purpose(KeyPurpose::DECRYPT)
+ .key_size(size)
+ .padding_mode(*padding_mode)
+ .block_mode(*block_mode);
+
+ if let Some(val) = min_mac_len {
+ gen_params = gen_params.min_mac_length(val);
+ }
+
+ let key_metadata = sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )?;
+
+ // Should not have public certificate.
+ assert!(key_metadata.certificate.is_none());
+
+ // Should not have an attestation record.
+ assert!(key_metadata.certificateChain.is_none());
+ Ok(key_metadata)
+}
+
+/// Generate HMAC key.
+pub fn generate_hmac_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ alias: &str,
+ key_size: i32,
+ min_mac_len: i32,
+ digest: Digest,
+) -> binder::Result<KeyMetadata> {
+ let gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::HMAC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .key_size(key_size)
+ .min_mac_length(min_mac_len)
+ .digest(digest);
+
+ let key_metadata = sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )?;
+
+ // Should not have public certificate.
+ assert!(key_metadata.certificate.is_none());
+
+ // Should not have an attestation record.
+ assert!(key_metadata.certificateChain.is_none());
+
+ Ok(key_metadata)
+}
+
+/// Generate RSA or EC attestation keys using below parameters -
+/// Purpose: ATTEST_KEY
+/// Digest: Digest::SHA_2_256
+/// Padding: PaddingMode::RSA_PKCS1_1_5_SIGN
+/// RSA-Key-Size: 2048
+/// EC-Curve: EcCurve::P_256
+pub fn generate_attestation_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ algorithm: Algorithm,
+ att_challenge: &[u8],
+ att_app_id: &[u8],
+) -> binder::Result<KeyMetadata> {
+ assert!(algorithm == Algorithm::RSA || algorithm == Algorithm::EC);
+
+ if algorithm == Algorithm::RSA {
+ let alias = "ks_rsa_attest_test_key";
+ let metadata = generate_rsa_key(
+ sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ATTEST_KEY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: Some(att_challenge.to_vec()),
+ att_app_id: Some(att_app_id.to_vec()),
+ },
+ None,
+ )
+ .unwrap();
+ Ok(metadata)
+ } else {
+ let metadata = generate_ec_attestation_key(
+ sec_level,
+ att_challenge,
+ att_app_id,
+ Digest::SHA_2_256,
+ EcCurve::P_256,
+ )
+ .unwrap();
+
+ Ok(metadata)
+ }
+}
+
+/// Generate EC attestation key with the given
+/// curve, attestation-challenge and attestation-app-id.
+pub fn generate_ec_attestation_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ att_challenge: &[u8],
+ att_app_id: &[u8],
+ digest: Digest,
+ ec_curve: EcCurve,
+) -> binder::Result<KeyMetadata> {
+ let alias = "ks_attest_ec_test_key";
+ let gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::ATTEST_KEY)
+ .ec_curve(ec_curve)
+ .digest(digest)
+ .attestation_challenge(att_challenge.to_vec())
+ .attestation_app_id(att_app_id.to_vec());
+
+ let attestation_key_metadata = sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )?;
+
+ // Should have public certificate.
+ assert!(attestation_key_metadata.certificate.is_some());
+ // Should have an attestation record.
+ assert!(attestation_key_metadata.certificateChain.is_some());
+
+ Ok(attestation_key_metadata)
+}
+
+/// Generate EC-P-256 key and attest it with given attestation key.
+pub fn generate_ec_256_attested_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ alias: Option<String>,
+ att_challenge: &[u8],
+ att_app_id: &[u8],
+ attest_key: &KeyDescriptor,
+) -> binder::Result<KeyMetadata> {
+ let ec_gen_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256)
+ .attestation_challenge(att_challenge.to_vec())
+ .attestation_app_id(att_app_id.to_vec());
+
+ let ec_key_metadata = sec_level
+ .generateKey(
+ &KeyDescriptor { domain: Domain::APP, nspace: -1, alias, blob: None },
+ Some(attest_key),
+ &ec_gen_params,
+ 0,
+ b"entropy",
+ )
+ .unwrap();
+
+ // Should have public certificate.
+ assert!(ec_key_metadata.certificate.is_some());
+ // Shouldn't have an attestation record.
+ assert!(ec_key_metadata.certificateChain.is_none());
+
+ Ok(ec_key_metadata)
+}
+
+fn check_key_param(authorizations: &[Authorization], key_param: KeyParameter) -> bool {
+ for authrization in authorizations {
+ if authrization.keyParameter == key_param {
+ return true;
+ }
+ }
+
+ false
+}
+
+/// Imports above defined RSA key - `RSA_2048_KEY` and validates imported key parameters.
+pub fn import_rsa_2048_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ import_params: AuthSetBuilder,
+) -> binder::Result<KeyMetadata> {
+ let key_metadata = sec_level
+ .importKey(
+ &KeyDescriptor { domain, nspace, alias, blob: None },
+ None,
+ &import_params,
+ 0,
+ RSA_2048_KEY,
+ )
+ .unwrap();
+
+ assert!(key_metadata.certificate.is_some());
+ assert!(key_metadata.certificateChain.is_none());
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::RSA) }
+ ));
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(2048) }
+ ));
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) }
+ ));
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter {
+ tag: Tag::RSA_PUBLIC_EXPONENT,
+ value: KeyParameterValue::LongInteger(65537)
+ }
+ ));
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter {
+ tag: Tag::PADDING,
+ value: KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS)
+ }
+ ));
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) }
+ ));
+
+ Ok(key_metadata)
+}
+
+/// Imports above defined EC key - `EC_P_256_KEY` and validates imported key parameters.
+pub fn import_ec_p_256_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ import_params: AuthSetBuilder,
+) -> binder::Result<KeyMetadata> {
+ let key_metadata = sec_level
+ .importKey(
+ &KeyDescriptor { domain, nspace, alias, blob: None },
+ None,
+ &import_params,
+ 0,
+ EC_P_256_KEY,
+ )
+ .unwrap();
+
+ assert!(key_metadata.certificate.is_some());
+ assert!(key_metadata.certificateChain.is_none());
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::EC) }
+ ));
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::EC_CURVE, value: KeyParameterValue::EcCurve(EcCurve::P_256) }
+ ));
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) }
+ ));
+
+ Ok(key_metadata)
+}
+
+/// Import sample AES key and validate its key parameters.
+pub fn import_aes_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+) -> binder::Result<KeyMetadata> {
+ static AES_KEY: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ let key_size = AES_KEY.len() * 8;
+
+ let import_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::AES)
+ .block_mode(BlockMode::ECB)
+ .key_size(key_size.try_into().unwrap())
+ .purpose(KeyPurpose::ENCRYPT)
+ .purpose(KeyPurpose::DECRYPT)
+ .padding_mode(PaddingMode::PKCS7);
+
+ let key_metadata = sec_level.importKey(
+ &KeyDescriptor { domain, nspace, alias, blob: None },
+ None,
+ &import_params,
+ 0,
+ AES_KEY,
+ )?;
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::AES) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter {
+ tag: Tag::PADDING,
+ value: KeyParameterValue::PaddingMode(PaddingMode::PKCS7)
+ }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(BlockMode::ECB) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) }
+ ));
+
+ Ok(key_metadata)
+}
+
+/// Import sample 3DES key and validate its key parameters.
+pub fn import_3des_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+) -> binder::Result<KeyMetadata> {
+ static TRIPLE_DES_KEY: &[u8] = &[
+ 0xa4, 0x9d, 0x75, 0x64, 0x19, 0x9e, 0x97, 0xcb, 0x52, 0x9d, 0x2c, 0x9d, 0x97, 0xbf, 0x2f,
+ 0x98, 0xd3, 0x5e, 0xdf, 0x57, 0xba, 0x1f, 0x73, 0x58,
+ ];
+
+ let import_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::TRIPLE_DES)
+ .block_mode(BlockMode::ECB)
+ .key_size(168)
+ .purpose(KeyPurpose::ENCRYPT)
+ .purpose(KeyPurpose::DECRYPT)
+ .padding_mode(PaddingMode::PKCS7);
+
+ let key_metadata = sec_level.importKey(
+ &KeyDescriptor { domain, nspace, alias, blob: None },
+ None,
+ &import_params,
+ 0,
+ TRIPLE_DES_KEY,
+ )?;
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter {
+ tag: Tag::ALGORITHM,
+ value: KeyParameterValue::Algorithm(Algorithm::TRIPLE_DES)
+ }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(168) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter {
+ tag: Tag::PADDING,
+ value: KeyParameterValue::PaddingMode(PaddingMode::PKCS7)
+ }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::BLOCK_MODE, value: KeyParameterValue::BlockMode(BlockMode::ECB) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) }
+ ));
+
+ Ok(key_metadata)
+}
+
+/// Import sample HMAC key and validate its key parameters.
+pub fn import_hmac_key(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+) -> binder::Result<KeyMetadata> {
+ static HMAC_KEY: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ let key_size = HMAC_KEY.len() * 8;
+
+ let import_params = AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::HMAC)
+ .key_size(key_size.try_into().unwrap())
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .min_mac_length(256);
+
+ let key_metadata = sec_level.importKey(
+ &KeyDescriptor { domain, nspace, alias, blob: None },
+ None,
+ &import_params,
+ 0,
+ HMAC_KEY,
+ )?;
+
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::HMAC) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(128) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) }
+ ));
+ assert!(check_key_param(
+ &key_metadata.authorizations,
+ KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(KeyOrigin::IMPORTED) }
+ ));
+
+ Ok(key_metadata)
+}
diff --git a/keystore2/test_utils/lib.rs b/keystore2/test_utils/lib.rs
index a355544..c63bfac 100644
--- a/keystore2/test_utils/lib.rs
+++ b/keystore2/test_utils/lib.rs
@@ -19,8 +19,14 @@
use std::path::{Path, PathBuf};
use std::{env::temp_dir, ops::Deref};
+use android_system_keystore2::aidl::android::system::keystore2::IKeystoreService::IKeystoreService;
+
+pub mod authorizations;
+pub mod key_generations;
pub mod run_as;
+static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default";
+
/// Represents the lifecycle of a temporary directory for testing.
#[derive(Debug)]
pub struct TempDir {
@@ -104,3 +110,8 @@
&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
index d42303d..2485ab5 100644
--- a/keystore2/test_utils/run_as.rs
+++ b/keystore2/test_utils/run_as.rs
@@ -30,9 +30,11 @@
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, Uid,
+ 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) {
@@ -48,17 +50,10 @@
/// reads from the pipe into an expending vector, until no more data can be read.
struct PipeReader(RawFd);
-impl PipeReader {
- pub fn read_all(&self) -> Result<Vec<u8>, nix::Error> {
- let mut buffer = [0u8; 128];
- let mut result = Vec::<u8>::new();
- loop {
- let bytes = nix_read(self.0, &mut buffer)?;
- if bytes == 0 {
- return Ok(result);
- }
- result.extend_from_slice(&buffer[0..bytes]);
- }
+impl Read for PipeReader {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ let bytes = nix_read(self.0, buf)?;
+ Ok(bytes)
}
}
@@ -73,46 +68,264 @@
/// writes the given buffer into the pipe, returning the number of bytes written.
struct PipeWriter(RawFd);
-impl PipeWriter {
- pub fn write(&self, data: &[u8]) -> Result<usize, nix::Error> {
- nix_write(self.0, data)
- }
-}
-
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`.
-pub fn run_as<F, R>(se_context: &str, uid: Uid, gid: Gid, f: F) -> R
+///
+/// # 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 (reader, writer) = pipe().expect("Failed to create pipe.");
+ let (mut reader, mut writer) = pipe_channel::<R>().expect("Failed to create pipe.");
- match unsafe { fork() } {
+ 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.");
+ // let serialized_result =
+ // reader.read_all().expect("Failed to read result from child.");
// Deserialize the result and return it.
- serde_cbor::from_slice(&serialized_result).expect("Failed to deserialize result.")
+ reader.recv()
} else {
panic!("Child did not exit as expected {:?}", status);
}
@@ -125,10 +338,7 @@
let result = f();
// Serialize the result of the closure.
- let vec = serde_cbor::to_vec(&result).expect("Result serialization failed");
-
- // Send the result to the parent using the pipe.
- writer.write(&vec).expect("Failed to send serialized result to parent.");
+ writer.send(&result);
// Set exit status to `0`.
std::process::exit(0);
@@ -151,9 +361,13 @@
#[test]
#[should_panic]
fn test_run_as_panics_on_closure_panic() {
- run_as(selinux::getcon().unwrap().to_str().unwrap(), getuid(), getgid(), || {
- panic!("Closure must 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);
@@ -163,11 +377,15 @@
/// Tests that the closure is running as the target identity.
#[test]
fn test_transition_to_untrusted_app() {
- 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());
- });
+ // 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)]
@@ -185,7 +403,72 @@
c: "supercalifragilisticexpialidocious".to_owned(),
};
let test_result_clone = test_result.clone();
- let result = run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || 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/Android.bp b/keystore2/tests/Android.bp
new file mode 100644
index 0000000..dd5d782
--- /dev/null
+++ b/keystore2/tests/Android.bp
@@ -0,0 +1,83 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_test {
+ name: "keystore2_client_tests",
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ "keystore2_use_latest_aidl_rust",
+ ],
+ srcs: ["keystore2_client_tests.rs"],
+ test_suites: [
+ "general-tests",
+ ],
+ test_config: "AndroidTest.xml",
+
+ rustlibs: [
+ "librustutils",
+ "libkeystore2_test_utils",
+ "packagemanager_aidl-rust",
+ "libnix",
+ "libanyhow",
+ "libbinder_rs",
+ "liblazy_static",
+ "liblibc",
+ "libserde",
+ "libthiserror",
+ "libcxx",
+ "libopenssl",
+ ],
+ static_libs: [
+ "libkeystore2_ffi_test_utils",
+ "libgtest",
+ "libkeymint_vts_test_utils",
+ ],
+ shared_libs: [
+ "libcrypto",
+ ],
+ require_root: true,
+}
+
+cc_library_static {
+ name: "libkeystore2_ffi_test_utils",
+ srcs: ["ffi_test_utils.cpp"],
+ defaults: [
+ "keymint_vts_defaults",
+ "hidl_defaults",
+ ],
+ generated_headers: [
+ "cxx-bridge-header",
+ ],
+ generated_sources: ["libkeystore2_ffi_test_utils_bridge_code"],
+ static_libs: [
+ "libkeymint_vts_test_utils",
+ ],
+}
+
+genrule {
+ name: "libkeystore2_ffi_test_utils_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["keystore2_client_attest_key_tests.rs"],
+ out: ["libkeystore2_test_utils_cxx_generated.cc"],
+}
diff --git a/keystore2/test_utils/AndroidTest.xml b/keystore2/tests/AndroidTest.xml
similarity index 60%
copy from keystore2/test_utils/AndroidTest.xml
copy to keystore2/tests/AndroidTest.xml
index 24e277a..7db36f7 100644
--- a/keystore2/test_utils/AndroidTest.xml
+++ b/keystore2/tests/AndroidTest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<!-- 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.
@@ -13,20 +13,27 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<configuration description="Config to run keystore2_client_tests device tests.">
-<configuration description="Config to run keystore2_test_utils_test device tests.">
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+ <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_test_utils_test->/data/local/tmp/keystore2_test_utils_test"
+ value="keystore2_client_tests->/data/local/tmp/keystore2_client_tests"
/>
</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_test_utils_test" />
+ <option name="module-name" value="keystore2_client_tests" />
+ <!-- When we run run multiple tests by default they run in parallel.
+ This will create issue as we create various child/user contexts
+ in a test leading to issues with IPC.
+ Serializing tests with below configuration to avoid IPC issues.
+ -->
+ <option name="native-test-flag" value="--test-threads=1" />
</test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/keystore2/tests/ffi_test_utils.cpp b/keystore2/tests/ffi_test_utils.cpp
new file mode 100644
index 0000000..fb5a7d2
--- /dev/null
+++ b/keystore2/tests/ffi_test_utils.cpp
@@ -0,0 +1,120 @@
+#include "ffi_test_utils.hpp"
+
+#include <iostream>
+
+#include <KeyMintAidlTestBase.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+
+#include <vector>
+
+using aidl::android::hardware::security::keymint::ErrorCode;
+
+#define TAG_SEQUENCE 0x30
+#define LENGTH_MASK 0x80
+#define LENGTH_VALUE_MASK 0x7F
+
+/* This function extracts a certificate from the certs_chain_buffer at the given
+ * offset. Each DER encoded certificate starts with TAG_SEQUENCE followed by the
+ * total length of the certificate. The length of the certificate is determined
+ * as per ASN.1 encoding rules for the length octets.
+ *
+ * @param certs_chain_buffer: buffer containing DER encoded X.509 certificates
+ * arranged sequentially.
+ * @data_size: Length of the DER encoded X.509 certificates buffer.
+ * @index: DER encoded X.509 certificates buffer offset.
+ * @cert: Encoded certificate to be extracted from buffer as outcome.
+ * @return: ErrorCode::OK on success, otherwise ErrorCode::UNKNOWN_ERROR.
+ */
+ErrorCode
+extractCertFromCertChainBuffer(uint8_t* certs_chain_buffer, int certs_chain_buffer_size, int& index,
+ aidl::android::hardware::security::keymint::Certificate& cert) {
+ if (index >= certs_chain_buffer_size) {
+ return ErrorCode::UNKNOWN_ERROR;
+ }
+
+ uint32_t length = 0;
+ std::vector<uint8_t> cert_bytes;
+ if (certs_chain_buffer[index] == TAG_SEQUENCE) {
+ // Short form. One octet. Bit 8 has value "0" and bits 7-1 give the length.
+ if (0 == (certs_chain_buffer[index + 1] & LENGTH_MASK)) {
+ length = (uint32_t)certs_chain_buffer[index];
+ // Add SEQ and Length fields
+ length += 2;
+ } else {
+ // Long form. Two to 127 octets. Bit 8 of first octet has value "1" and
+ // bits 7-1 give the number of additional length octets. Second and following
+ // octets give the actual length.
+ int additionalBytes = certs_chain_buffer[index + 1] & LENGTH_VALUE_MASK;
+ if (additionalBytes == 0x01) {
+ length = certs_chain_buffer[index + 2];
+ // Add SEQ and Length fields
+ length += 3;
+ } else if (additionalBytes == 0x02) {
+ length = (certs_chain_buffer[index + 2] << 8 | certs_chain_buffer[index + 3]);
+ // Add SEQ and Length fields
+ length += 4;
+ } else if (additionalBytes == 0x04) {
+ length = certs_chain_buffer[index + 2] << 24;
+ length |= certs_chain_buffer[index + 3] << 16;
+ length |= certs_chain_buffer[index + 4] << 8;
+ length |= certs_chain_buffer[index + 5];
+ // Add SEQ and Length fields
+ length += 6;
+ } else {
+ // Length is larger than uint32_t max limit.
+ return ErrorCode::UNKNOWN_ERROR;
+ }
+ }
+ cert_bytes.insert(cert_bytes.end(), (certs_chain_buffer + index),
+ (certs_chain_buffer + index + length));
+ index += length;
+
+ for (int i = 0; i < cert_bytes.size(); i++) {
+ cert.encodedCertificate = std::move(cert_bytes);
+ }
+ } else {
+ // SEQUENCE TAG MISSING.
+ return ErrorCode::UNKNOWN_ERROR;
+ }
+
+ return ErrorCode::OK;
+}
+
+ErrorCode getCertificateChain(
+ rust::Vec<rust::u8>& chainBuffer,
+ std::vector<aidl::android::hardware::security::keymint::Certificate>& certChain) {
+ uint8_t* data = chainBuffer.data();
+ int index = 0;
+ int data_size = chainBuffer.size();
+
+ while (index < data_size) {
+ aidl::android::hardware::security::keymint::Certificate cert =
+ aidl::android::hardware::security::keymint::Certificate();
+ if (extractCertFromCertChainBuffer(data, data_size, index, cert) != ErrorCode::OK) {
+ return ErrorCode::UNKNOWN_ERROR;
+ }
+ certChain.push_back(std::move(cert));
+ }
+ return ErrorCode::OK;
+}
+
+bool validateCertChain(rust::Vec<rust::u8> cert_buf, uint32_t cert_len, bool strict_issuer_check) {
+ std::vector<aidl::android::hardware::security::keymint::Certificate> cert_chain =
+ std::vector<aidl::android::hardware::security::keymint::Certificate>();
+ if (cert_len <= 0) {
+ return false;
+ }
+ if (getCertificateChain(cert_buf, cert_chain) != ErrorCode::OK) {
+ return false;
+ }
+
+ for (int i = 0; i < cert_chain.size(); i++) {
+ std::cout << cert_chain[i].toString() << "\n";
+ }
+ auto result = aidl::android::hardware::security::keymint::test::ChainSignaturesAreValid(
+ cert_chain, strict_issuer_check);
+
+ if (result == testing::AssertionSuccess()) return true;
+
+ return false;
+}
diff --git a/keystore2/tests/ffi_test_utils.hpp b/keystore2/tests/ffi_test_utils.hpp
new file mode 100644
index 0000000..7f5c3b2
--- /dev/null
+++ b/keystore2/tests/ffi_test_utils.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "rust/cxx.h"
+
+bool validateCertChain(rust::Vec<rust::u8> cert_buf, uint32_t cert_len, bool strict_issuer_check);
diff --git a/keystore2/tests/keystore2_client_3des_key_tests.rs b/keystore2/tests/keystore2_client_3des_key_tests.rs
new file mode 100644
index 0000000..eda24db
--- /dev/null
+++ b/keystore2/tests/keystore2_client_3des_key_tests.rs
@@ -0,0 +1,218 @@
+// 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BlockMode::BlockMode, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose,
+ PaddingMode::PaddingMode, SecurityLevel::SecurityLevel,
+};
+
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error,
+};
+
+use crate::keystore2_client_test_utils::{
+ perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT,
+};
+
+/// Generate a 3DES key. Create encryption and decryption operations using the generated key.
+fn create_3des_key_and_operation(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ padding_mode: PaddingMode,
+ block_mode: BlockMode,
+ nonce: &mut Option<Vec<u8>>,
+) -> Result<(), binder::Status> {
+ let alias = format!("ks_3des_test_key_{}{}", block_mode.0, padding_mode.0);
+
+ let key_metadata = key_generations::generate_sym_key(
+ sec_level,
+ Algorithm::TRIPLE_DES,
+ 168,
+ &alias,
+ &padding_mode,
+ &block_mode,
+ None,
+ )?;
+
+ // Encrypts `SAMPLE_PLAIN_TEXT` whose length is multiple of DES block size.
+ let cipher_text = perform_sample_sym_key_encrypt_op(
+ sec_level,
+ padding_mode,
+ block_mode,
+ nonce,
+ None,
+ &key_metadata.key,
+ )?;
+ assert!(cipher_text.is_some());
+
+ let plain_text = perform_sample_sym_key_decrypt_op(
+ sec_level,
+ &cipher_text.unwrap(),
+ padding_mode,
+ block_mode,
+ nonce,
+ None,
+ &key_metadata.key,
+ )
+ .unwrap();
+ assert!(plain_text.is_some());
+ assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec());
+ Ok(())
+}
+
+/// Generate 3DES keys with various block modes and paddings.
+/// - Block Modes: ECB, CBC
+/// - Padding Modes: NONE, PKCS7
+/// Test should generate keys and perform operation successfully.
+#[test]
+fn keystore2_3des_ecb_cbc_generate_key_success() {
+ let keystore2 = get_keystore_service();
+ let block_modes = [BlockMode::ECB, BlockMode::CBC];
+ let padding_modes = [PaddingMode::PKCS7, PaddingMode::NONE];
+
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ for block_mode in block_modes {
+ for padding_mode in padding_modes {
+ assert_eq!(
+ Ok(()),
+ create_3des_key_and_operation(&sec_level, padding_mode, block_mode, &mut None)
+ );
+ }
+ }
+}
+
+/// Try to generate 3DES key with invalid key size. Test should fail to generate a key with
+/// an error code `UNSUPPORTED_KEY_SIZE`.
+#[test]
+fn keystore2_3des_key_fails_unsupported_key_size() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "3des_key_test_invalid_1";
+ let invalid_key_size = 128;
+
+ let result = key_generations::map_ks_error(key_generations::generate_sym_key(
+ &sec_level,
+ Algorithm::TRIPLE_DES,
+ invalid_key_size,
+ alias,
+ &PaddingMode::PKCS7,
+ &BlockMode::CBC,
+ None,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE), result.unwrap_err());
+}
+
+/// Generate a 3DES key without providing padding mode and try to use the generated key to create
+/// an operation. Test should fail to create an operation with an error code
+/// `UNSUPPORTED_PADDING_MODE`.
+#[test]
+fn keystore2_3des_key_fails_missing_padding() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "3des_key_test_missing_padding";
+
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::TRIPLE_DES)
+ .purpose(KeyPurpose::ENCRYPT)
+ .purpose(KeyPurpose::DECRYPT)
+ .key_size(168)
+ .block_mode(BlockMode::ECB);
+
+ let key_metadata = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )
+ .unwrap();
+
+ let op_params = authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::ENCRYPT)
+ .block_mode(BlockMode::ECB);
+
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &op_params,
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err());
+}
+
+/// Generate a 3DES key with padding mode NONE. Try to encrypt a text whose length isn't a
+/// multiple of the DES block size.
+#[test]
+fn keystore2_3des_key_encrypt_fails_invalid_input_length() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "3des_key_test_invalid_input_len";
+
+ let key_metadata = key_generations::generate_sym_key(
+ &sec_level,
+ Algorithm::TRIPLE_DES,
+ 168,
+ alias,
+ &PaddingMode::NONE,
+ &BlockMode::ECB,
+ None,
+ )
+ .unwrap();
+
+ let op_params = authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::ENCRYPT)
+ .padding_mode(PaddingMode::NONE)
+ .block_mode(BlockMode::ECB);
+
+ let op_response = sec_level
+ .createOperation(&key_metadata.key, &op_params, false)
+ .expect("Error in creation of operation using rebound key.");
+ assert!(op_response.iOperation.is_some());
+
+ let op = op_response.iOperation.unwrap();
+ // 3DES expects input should be multiple of DES block size (64-bits) length. Try with invalid
+ // length of input.
+ let invalid_block_size_msg = b"my message 111";
+ let result = key_generations::map_ks_error(op.finish(Some(invalid_block_size_msg), None));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_INPUT_LENGTH), result.unwrap_err());
+}
+
+/// Try to generate 3DES key with BlockMode::CTR. Test should fail to create an operation with an
+/// error code `UNSUPPORTED_BLOCK_MODE`.
+#[test]
+fn keystore2_3des_key_fails_unsupported_block_mode() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let result = key_generations::map_ks_error(create_3des_key_and_operation(
+ &sec_level,
+ PaddingMode::NONE,
+ BlockMode::CTR,
+ &mut None,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_BLOCK_MODE), result.unwrap_err());
+}
diff --git a/keystore2/tests/keystore2_client_aes_key_tests.rs b/keystore2/tests/keystore2_client_aes_key_tests.rs
new file mode 100644
index 0000000..885cbf5
--- /dev/null
+++ b/keystore2/tests/keystore2_client_aes_key_tests.rs
@@ -0,0 +1,478 @@
+// 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BlockMode::BlockMode, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose,
+ PaddingMode::PaddingMode, SecurityLevel::SecurityLevel,
+};
+
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error,
+};
+
+use crate::keystore2_client_test_utils::{
+ has_trusty_keymint, perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op,
+ SAMPLE_PLAIN_TEXT,
+};
+
+/// Generate a AES key. Create encrypt and decrypt operations using the generated key.
+fn create_aes_key_and_operation(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ key_size: i32,
+ padding_mode: PaddingMode,
+ block_mode: BlockMode,
+ mac_len: Option<i32>,
+ min_mac_len: Option<i32>,
+ nonce: &mut Option<Vec<u8>>,
+) -> Result<(), binder::Status> {
+ let alias = format!("ks_aes_test_key_{}{}{}", key_size, block_mode.0, padding_mode.0);
+
+ let key_metadata = key_generations::generate_sym_key(
+ sec_level,
+ Algorithm::AES,
+ key_size,
+ &alias,
+ &padding_mode,
+ &block_mode,
+ min_mac_len,
+ )?;
+
+ let cipher_text = perform_sample_sym_key_encrypt_op(
+ sec_level,
+ padding_mode,
+ block_mode,
+ nonce,
+ mac_len,
+ &key_metadata.key,
+ )?;
+
+ assert!(cipher_text.is_some());
+
+ let plain_text = perform_sample_sym_key_decrypt_op(
+ sec_level,
+ &cipher_text.unwrap(),
+ padding_mode,
+ block_mode,
+ nonce,
+ mac_len,
+ &key_metadata.key,
+ )
+ .unwrap();
+ assert!(plain_text.is_some());
+ assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec());
+ Ok(())
+}
+
+/// Generate AES keys with various block modes and paddings.
+/// - Block Modes: ECB, CBC
+/// - Padding Modes: NONE, PKCS7
+/// Test should generate keys and perform operation successfully.
+#[test]
+fn keystore2_aes_ecb_cbc_generate_key() {
+ let keystore2 = get_keystore_service();
+ let key_sizes = [128, 256];
+ let block_modes = [BlockMode::ECB, BlockMode::CBC];
+ let padding_modes = [PaddingMode::PKCS7, PaddingMode::NONE];
+
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ for key_size in key_sizes {
+ for block_mode in block_modes {
+ for padding_mode in padding_modes {
+ assert_eq!(
+ Ok(()),
+ create_aes_key_and_operation(
+ &sec_level,
+ key_size,
+ padding_mode,
+ block_mode,
+ None,
+ None,
+ &mut None,
+ )
+ );
+ }
+ }
+ }
+}
+
+/// Generate AES keys with -
+/// - Block Modes: `CTR, GCM`
+/// - Padding Modes: `NONE`
+/// Test should generate keys and perform operation successfully.
+#[test]
+fn keystore2_aes_ctr_gcm_generate_key_success() {
+ let keystore2 = get_keystore_service();
+ let key_sizes = [128, 256];
+ let key_params = [(BlockMode::CTR, None, None), (BlockMode::GCM, Some(128), Some(128))];
+
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ for key_size in key_sizes {
+ for (block_mode, mac_len, min_mac_len) in key_params {
+ let result = key_generations::map_ks_error(create_aes_key_and_operation(
+ &sec_level,
+ key_size,
+ PaddingMode::NONE,
+ block_mode,
+ mac_len,
+ min_mac_len,
+ &mut None,
+ ));
+
+ assert_eq!(Ok(()), result);
+ } // End of block mode.
+ } // End of key size.
+}
+
+/// Generate AES keys with -
+/// - Block Modes: `CTR, GCM`
+/// - Padding Modes: `PKCS7`
+/// Try to create an operation using generated keys, test should fail to create an operation
+/// with an error code `INCOMPATIBLE_PADDING_MODE`.
+#[test]
+fn keystore2_aes_ctr_gcm_generate_key_fails_incompatible() {
+ let keystore2 = get_keystore_service();
+ let key_sizes = [128, 256];
+ let key_params = [(BlockMode::CTR, None, None), (BlockMode::GCM, Some(128), Some(128))];
+
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ for key_size in key_sizes {
+ for (block_mode, mac_len, min_mac_len) in key_params {
+ let result = key_generations::map_ks_error(create_aes_key_and_operation(
+ &sec_level,
+ key_size,
+ PaddingMode::PKCS7,
+ block_mode,
+ mac_len,
+ min_mac_len,
+ &mut None,
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PADDING_MODE), result.unwrap_err());
+ } // End of block mode.
+ } // End of key size.
+}
+
+/// Try to generate AES key with invalid key size. Test should fail to generate a key with
+/// an error code `UNSUPPORTED_KEY_SIZE`.
+#[test]
+fn keystore2_aes_key_fails_unsupported_key_size() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "aes_key_test_invalid_1";
+
+ let result = key_generations::map_ks_error(key_generations::generate_sym_key(
+ &sec_level,
+ Algorithm::AES,
+ 1024,
+ alias,
+ &PaddingMode::NONE,
+ &BlockMode::ECB,
+ None,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE), result.unwrap_err());
+}
+
+/// Try to generate AES key with GCM block mode without providing `MIN_MAC_LENGTH`.
+/// Test should fail to generate a key with an error code `MISSING_MIN_MAC_LENGTH`.
+#[test]
+fn keystore2_aes_gcm_key_fails_missing_min_mac_len() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "aes_key_test_invalid_1";
+
+ let result = key_generations::map_ks_error(key_generations::generate_sym_key(
+ &sec_level,
+ Algorithm::AES,
+ 128,
+ alias,
+ &PaddingMode::NONE,
+ &BlockMode::GCM,
+ None,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::MISSING_MIN_MAC_LENGTH), result.unwrap_err());
+}
+
+/// Try to create an operation using AES key with multiple block modes. Test should fail to create
+/// an operation with `UNSUPPORTED_BLOCK_MODE` error code.
+#[test]
+fn keystore2_aes_key_op_fails_multi_block_modes() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "aes_key_test_invalid_1";
+
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::AES)
+ .purpose(KeyPurpose::ENCRYPT)
+ .purpose(KeyPurpose::DECRYPT)
+ .key_size(128)
+ .block_mode(BlockMode::ECB)
+ .block_mode(BlockMode::CBC)
+ .padding_mode(PaddingMode::NONE);
+
+ let key_metadata = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )
+ .unwrap();
+
+ let op_params = authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::ENCRYPT)
+ .block_mode(BlockMode::ECB)
+ .block_mode(BlockMode::CBC)
+ .padding_mode(PaddingMode::NONE);
+
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &op_params,
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_BLOCK_MODE), result.unwrap_err());
+}
+
+/// Try to create an operation using AES key with multiple padding modes. Test should fail to create
+/// an operation with `UNSUPPORTED_PADDING_MODE` error code.
+#[test]
+fn keystore2_aes_key_op_fails_multi_padding_modes() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "aes_key_test_invalid_1";
+
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::AES)
+ .purpose(KeyPurpose::ENCRYPT)
+ .purpose(KeyPurpose::DECRYPT)
+ .key_size(128)
+ .block_mode(BlockMode::ECB)
+ .padding_mode(PaddingMode::PKCS7)
+ .padding_mode(PaddingMode::NONE);
+
+ let key_metadata = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ )
+ .unwrap();
+
+ let op_params = authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::ENCRYPT)
+ .block_mode(BlockMode::ECB)
+ .padding_mode(PaddingMode::PKCS7)
+ .padding_mode(PaddingMode::NONE);
+
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &op_params,
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err());
+}
+
+/// Generate a AES-ECB key with unpadded mode. Try to create an operation using generated key
+/// with PKCS7 padding mode. Test should fail to create an Operation with
+/// `INCOMPATIBLE_PADDING_MODE` error code.
+#[test]
+fn keystore2_aes_key_op_fails_incompatible_padding() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "aes_key_test_invalid_1";
+
+ let key_metadata = key_generations::generate_sym_key(
+ &sec_level,
+ Algorithm::AES,
+ 128,
+ alias,
+ &PaddingMode::NONE,
+ &BlockMode::ECB,
+ None,
+ )
+ .unwrap();
+
+ let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op(
+ &sec_level,
+ PaddingMode::PKCS7,
+ BlockMode::ECB,
+ &mut None,
+ None,
+ &key_metadata.key,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PADDING_MODE), result.unwrap_err());
+}
+
+/// Generate a AES-ECB key with unpadded mode. Try to create an operation using generated key
+/// with CBC block mode. Test should fail to create an Operation with
+/// `INCOMPATIBLE_BLOCK_MODE` error code.
+#[test]
+fn keystore2_aes_key_op_fails_incompatible_blockmode() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "aes_key_test_invalid_1";
+
+ let key_metadata = key_generations::generate_sym_key(
+ &sec_level,
+ Algorithm::AES,
+ 128,
+ alias,
+ &PaddingMode::NONE,
+ &BlockMode::ECB,
+ None,
+ )
+ .unwrap();
+
+ let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op(
+ &sec_level,
+ PaddingMode::NONE,
+ BlockMode::CBC,
+ &mut None,
+ None,
+ &key_metadata.key,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_BLOCK_MODE), result.unwrap_err());
+}
+
+/// Generate a AES-GCM key with `MIN_MAC_LENGTH`. Try to create an operation using this
+/// generated key without providing `MAC_LENGTH`. Test should fail to create an operation with
+/// `MISSING_MAC_LENGTH` error code.
+#[test]
+fn keystore2_aes_gcm_op_fails_missing_mac_len() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let mac_len = None;
+ let min_mac_len = Some(128);
+
+ let result = key_generations::map_ks_error(create_aes_key_and_operation(
+ &sec_level,
+ 128,
+ PaddingMode::NONE,
+ BlockMode::GCM,
+ mac_len,
+ min_mac_len,
+ &mut None,
+ ));
+ assert!(result.is_err());
+
+ if has_trusty_keymint() {
+ assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::MISSING_MAC_LENGTH));
+ } else {
+ assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::UNSUPPORTED_MAC_LENGTH));
+ }
+}
+
+/// Generate a AES-GCM key with `MIN_MAC_LENGTH`. Try to create an operation using this
+/// generated key and provide `MAC_LENGTH` < key's `MIN_MAC_LENGTH`. Test should fail to create
+/// an operation with `INVALID_MAC_LENGTH` error code.
+#[test]
+fn keystore2_aes_gcm_op_fails_invalid_mac_len() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let mac_len = Some(96);
+ let min_mac_len = Some(104);
+
+ let result = key_generations::map_ks_error(create_aes_key_and_operation(
+ &sec_level,
+ 128,
+ PaddingMode::NONE,
+ BlockMode::GCM,
+ mac_len,
+ min_mac_len,
+ &mut None,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_MAC_LENGTH), result.unwrap_err());
+}
+
+/// Generate a AES-GCM key with `MIN_MAC_LENGTH`. Try to create an operation using this
+/// generated key and provide `MAC_LENGTH` > 128. Test should fail to create an operation with
+/// `UNSUPPORTED_MAC_LENGTH` error code.
+#[test]
+fn keystore2_aes_gcm_op_fails_unsupported_mac_len() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let result = key_generations::map_ks_error(create_aes_key_and_operation(
+ &sec_level,
+ 128,
+ PaddingMode::NONE,
+ BlockMode::GCM,
+ Some(256),
+ Some(128),
+ &mut None,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_MAC_LENGTH), result.unwrap_err());
+}
+
+/// Generate a AES-CBC-PKCS7 key without `CALLER_NONCE` authorization. Try to set nonce while
+/// creating an operation using this generated key. Test should fail to create an operation with
+/// `CALLER_NONCE_PROHIBITED` error code.
+#[test]
+fn keystore2_aes_key_op_fails_nonce_prohibited() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "aes_key_test_nonce_1";
+ let mut nonce = Some(vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+ let key_metadata = key_generations::generate_sym_key(
+ &sec_level,
+ Algorithm::AES,
+ 128,
+ alias,
+ &PaddingMode::PKCS7,
+ &BlockMode::CBC,
+ None,
+ )
+ .unwrap();
+
+ let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op(
+ &sec_level,
+ PaddingMode::NONE,
+ BlockMode::CBC,
+ &mut nonce,
+ None,
+ &key_metadata.key,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::CALLER_NONCE_PROHIBITED), result.unwrap_err());
+}
diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs
new file mode 100644
index 0000000..fc3148c
--- /dev/null
+++ b/keystore2/tests/keystore2_client_attest_key_tests.rs
@@ -0,0 +1,526 @@
+// 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;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
+ ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
+ SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error,
+};
+
+use crate::{
+ keystore2_client_test_utils::app_attest_key_feature_exists,
+ skip_test_if_no_app_attest_key_feature,
+};
+
+#[cxx::bridge]
+mod ffi {
+ unsafe extern "C++" {
+ include!("ffi_test_utils.hpp");
+ fn validateCertChain(cert_buf: Vec<u8>, cert_len: u32, strict_issuer_check: bool) -> bool;
+ }
+}
+
+/// Validate given certificate chain.
+pub fn validate_certchain(cert_buf: &[u8]) -> Result<bool, Error> {
+ if ffi::validateCertChain(cert_buf.to_vec(), cert_buf.len().try_into().unwrap(), true) {
+ return Ok(true);
+ }
+
+ Err(Error::ValidateCertChainFailed)
+}
+
+/// Generate RSA and EC attestation keys and use them for signing RSA-signing keys.
+/// Test should be able to generate attestation keys and use them successfully.
+#[test]
+fn keystore2_attest_rsa_signing_key_success() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+
+ for algo in [Algorithm::RSA, Algorithm::EC] {
+ // Create attestation key.
+ let attestation_key_metadata =
+ key_generations::generate_attestation_key(&sec_level, algo, att_challenge, att_app_id)
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+ validate_certchain(&cert_chain).expect("Error while validating cert chain.");
+
+ // Create RSA signing key and use attestation key to sign it.
+ let sign_key_alias = format!("ks_attest_rsa_signing_key_{}", getuid());
+ let sign_key_metadata = key_generations::generate_rsa_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(sign_key_alias),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: Some(att_challenge.to_vec()),
+ att_app_id: Some(att_app_id.to_vec()),
+ },
+ Some(&attestation_key_metadata.key),
+ )
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(sign_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+ validate_certchain(&cert_chain).expect("Error while validating cert chain");
+ }
+}
+
+/// Generate RSA and EC attestation keys and use them for signing RSA encrypt/decrypt keys.
+/// Test should be able to generate attestation keys and use them successfully.
+#[test]
+fn keystore2_attest_rsa_encrypt_key_success() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+
+ for algo in [Algorithm::RSA, Algorithm::EC] {
+ // Create attestation key.
+ let attestation_key_metadata =
+ key_generations::generate_attestation_key(&sec_level, algo, att_challenge, att_app_id)
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+ validate_certchain(&cert_chain).expect("Error while validating cert chain.");
+
+ // Create RSA encrypt/decrypt key and use attestation key to sign it.
+ let decrypt_key_alias = format!("ks_attest_rsa_encrypt_key_{}", getuid());
+ let decrypt_key_metadata = key_generations::generate_rsa_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(decrypt_key_alias),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: Some(att_challenge.to_vec()),
+ att_app_id: Some(att_app_id.to_vec()),
+ },
+ Some(&attestation_key_metadata.key),
+ )
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(decrypt_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+
+ validate_certchain(&cert_chain).expect("Error while validating cert chain.");
+ }
+}
+
+/// Generate RSA and EC attestation keys and use them for signing EC keys.
+/// Test should be able to generate attestation keys and use them successfully.
+#[test]
+fn keystore2_attest_ec_key_success() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+
+ for algo in [Algorithm::RSA, Algorithm::EC] {
+ // Create attestation key.
+ let attestation_key_metadata =
+ key_generations::generate_attestation_key(&sec_level, algo, att_challenge, att_app_id)
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+ validate_certchain(&cert_chain).expect("Error while validating cert chain.");
+
+ // Create EC key and use attestation key to sign it.
+ let ec_key_alias = format!("ks_ec_attested_test_key_{}", getuid());
+ let ec_key_metadata = key_generations::generate_ec_256_attested_key(
+ &sec_level,
+ Some(ec_key_alias),
+ att_challenge,
+ att_app_id,
+ &attestation_key_metadata.key,
+ )
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(ec_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+
+ validate_certchain(&cert_chain).expect("Error while validating cert chain.");
+ }
+}
+
+/// Generate EC-CURVE_25519 attestation key and use it for signing RSA-signing keys.
+/// Test should be able to generate RSA signing key with EC-CURVE_25519 as attestation key
+/// successfully.
+#[test]
+fn keystore2_attest_rsa_signing_key_with_ec_25519_key_success() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+
+ // Create EcCurve::CURVE_25519 attestation key.
+ let attestation_key_metadata = key_generations::generate_ec_attestation_key(
+ &sec_level,
+ att_challenge,
+ att_app_id,
+ Digest::NONE,
+ EcCurve::CURVE_25519,
+ )
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+ validate_certchain(&cert_chain).expect("Error while validating cert chain.");
+
+ // Create RSA signing key and use attestation key to sign it.
+ let sign_key_alias = format!("ksrsa_attested_sign_test_key_{}", getuid());
+ let sign_key_metadata = key_generations::generate_rsa_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(sign_key_alias),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: Some(att_challenge.to_vec()),
+ att_app_id: Some(att_app_id.to_vec()),
+ },
+ Some(&attestation_key_metadata.key),
+ )
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(sign_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+ validate_certchain(&cert_chain).expect("Error while validating cert chain");
+}
+
+/// Try to generate RSA attestation key with multiple purposes. Test should fail with error code
+/// `INCOMPATIBLE_PURPOSE` to generate an attestation key.
+#[test]
+fn keystore2_generate_rsa_attest_key_with_multi_purpose_fail() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let digest = Digest::SHA_2_256;
+ let padding = PaddingMode::RSA_PKCS1_1_5_SIGN;
+ let key_size = 2048;
+
+ let attest_key_alias =
+ format!("ksrsa_attest_multipurpose_key_{}{}{}", getuid(), key_size, digest.0);
+
+ let attest_gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::RSA)
+ .purpose(KeyPurpose::ATTEST_KEY)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(digest)
+ .key_size(key_size)
+ .rsa_public_exponent(65537)
+ .padding_mode(padding);
+
+ let result = key_generations::map_ks_error(sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(attest_key_alias),
+ blob: None,
+ },
+ None,
+ &attest_gen_params,
+ 0,
+ b"entropy",
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+}
+
+/// Try to generate EC attestation key with multiple purposes. Test should fail with error code
+/// `INCOMPATIBLE_PURPOSE` to generate an attestation key.
+#[test]
+fn keystore2_ec_attest_key_with_multi_purpose_fail() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let attest_key_alias = format!("ks_ec_attest_multipurpose_key_{}", getuid());
+
+ let attest_gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::ATTEST_KEY)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256);
+
+ let result = key_generations::map_ks_error(sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(attest_key_alias),
+ blob: None,
+ },
+ None,
+ &attest_gen_params,
+ 0,
+ b"entropy",
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+}
+
+/// Generate RSA attestation key and try to use it for signing RSA key without providing
+/// attestation challenge. Test should fail to generate a key with error code
+/// `ATTESTATION_CHALLENGE_MISSING`.
+#[test]
+fn keystore2_attest_key_fails_missing_challenge() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+
+ // Create RSA attestation key.
+ let attestation_key_metadata = key_generations::generate_attestation_key(
+ &sec_level,
+ Algorithm::RSA,
+ att_challenge,
+ att_app_id,
+ )
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+ validate_certchain(&cert_chain).expect("Error while validating cert chain.");
+
+ // Try to attest RSA signing key without providing attestation challenge.
+ let sign_key_alias = format!("ksrsa_attested_test_key_missing_challenge{}", getuid());
+ let result = key_generations::map_ks_error(key_generations::generate_rsa_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(sign_key_alias),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: Some(att_app_id.to_vec()),
+ },
+ Some(&attestation_key_metadata.key),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::ATTESTATION_CHALLENGE_MISSING), result.unwrap_err());
+}
+
+/// Generate an asymmetric key which doesn't possess ATTEST_KEY purpose. Try to use this key as
+/// attestation key while generating RSA key. Test should fail to generate a key with error
+/// code `INCOMPATIBLE_PURPOSE`.
+#[test]
+fn keystore2_attest_rsa_key_with_non_attest_key_fails_incompat_purpose_error() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+
+ let alias = format!("non_attest_key_{}", getuid());
+ let non_attest_key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias),
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Try to generate RSA signing key with non-attestation key to sign it.
+ let sign_key_alias = format!("ksrsa_attested_sign_test_key_non_attest_{}", getuid());
+ let result = key_generations::map_ks_error(key_generations::generate_rsa_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(sign_key_alias),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: Some(att_challenge.to_vec()),
+ att_app_id: Some(att_app_id.to_vec()),
+ },
+ Some(&non_attest_key_metadata.key),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+}
+
+/// Generate a symmetric key. Try to use this symmetric key as attestation key while generating RSA
+/// key. Test should fail to generate a key with response code `INVALID_ARGUMENT`.
+#[test]
+fn keystore2_attest_rsa_key_with_symmetric_key_fails_sys_error() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+
+ let alias = "aes_attest_key";
+ let sym_key_metadata = key_generations::generate_sym_key(
+ &sec_level,
+ Algorithm::AES,
+ 128,
+ alias,
+ &PaddingMode::NONE,
+ &BlockMode::ECB,
+ None,
+ )
+ .unwrap();
+
+ // Try to generate RSA signing key with symmetric key as attestation key.
+ let sign_key_alias = format!("ksrsa_attested_sign_test_key_sym_attest_{}", getuid());
+ let result = key_generations::map_ks_error(key_generations::generate_rsa_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(sign_key_alias),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: Some(att_challenge.to_vec()),
+ att_app_id: Some(att_app_id.to_vec()),
+ },
+ Some(&sym_key_metadata.key),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
+}
+
+/// Generate RSA attestation key and try to use it as attestation key while generating symmetric
+/// key. Test should generate symmetric key successfully. Verify that generated symmetric key
+/// should not have attestation record or certificate.
+#[test]
+fn keystore2_attest_symmetric_key_fail_sys_error() {
+ skip_test_if_no_app_attest_key_feature!();
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+
+ // Create attestation key.
+ let attestation_key_metadata = key_generations::generate_attestation_key(
+ &sec_level,
+ Algorithm::RSA,
+ att_challenge,
+ att_app_id,
+ )
+ .unwrap();
+
+ let mut cert_chain: Vec<u8> = Vec::new();
+ cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
+ cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
+ validate_certchain(&cert_chain).expect("Error while validating cert chain.");
+
+ // Generate symmetric key with above generated key as attestation key.
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::AES)
+ .purpose(KeyPurpose::ENCRYPT)
+ .purpose(KeyPurpose::DECRYPT)
+ .key_size(128)
+ .padding_mode(PaddingMode::NONE)
+ .block_mode(BlockMode::ECB)
+ .attestation_challenge(att_challenge.to_vec())
+ .attestation_app_id(att_app_id.to_vec());
+
+ let alias = format!("ks_test_sym_key_attest_{}", getuid());
+ let aes_key_metadata = sec_level
+ .generateKey(
+ &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
+ Some(&attestation_key_metadata.key),
+ &gen_params,
+ 0,
+ b"entropy",
+ )
+ .unwrap();
+
+ // Should not have public certificate.
+ assert!(aes_key_metadata.certificate.is_none());
+
+ // Should not have an attestation record.
+ assert!(aes_key_metadata.certificateChain.is_none());
+}
diff --git a/keystore2/tests/keystore2_client_ec_key_tests.rs b/keystore2/tests/keystore2_client_ec_key_tests.rs
new file mode 100644
index 0000000..726d61c
--- /dev/null
+++ b/keystore2/tests/keystore2_client_ec_key_tests.rs
@@ -0,0 +1,518 @@
+// 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode,
+ KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ CreateOperationResponse::CreateOperationResponse, Domain::Domain,
+ IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
+ ResponseCode::ResponseCode,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error, run_as,
+};
+
+use crate::keystore2_client_test_utils::{
+ delete_app_key, execute_op_run_as_child, perform_sample_sign_operation, BarrierReached,
+ ForcedOp, TestOutcome,
+};
+
+macro_rules! test_ec_sign_key_op_success {
+ ( $test_name:ident, $digest:expr, $ec_curve:expr ) => {
+ #[test]
+ fn $test_name() {
+ perform_ec_sign_key_op_success(stringify!($test_name), $digest, $ec_curve);
+ }
+ };
+}
+
+macro_rules! test_ec_sign_key_op_with_none_or_md5_digest {
+ ( $test_name:ident, $digest:expr, $ec_curve:expr ) => {
+ #[test]
+ fn $test_name() {
+ perform_ec_sign_key_op_with_none_or_md5_digest(
+ stringify!($test_name),
+ $digest,
+ $ec_curve,
+ );
+ }
+ };
+}
+
+fn create_ec_key_and_operation(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ digest: Digest,
+ ec_curve: EcCurve,
+) -> binder::Result<CreateOperationResponse> {
+ let key_metadata =
+ key_generations::generate_ec_key(sec_level, domain, nspace, alias, ec_curve, digest)?;
+
+ sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest),
+ false,
+ )
+}
+
+fn perform_ec_sign_key_op_success(alias: &str, digest: Digest, ec_curve: EcCurve) {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let op_response = create_ec_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ digest,
+ ec_curve,
+ )
+ .unwrap();
+
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+
+ delete_app_key(&keystore2, alias).unwrap();
+}
+
+fn perform_ec_sign_key_op_with_none_or_md5_digest(alias: &str, digest: Digest, ec_curve: EcCurve) {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ match key_generations::map_ks_error(create_ec_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ digest,
+ ec_curve,
+ )) {
+ Ok(op_response) => {
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+ }
+ Err(e) => {
+ assert_eq!(e, Error::Km(ErrorCode::UNSUPPORTED_DIGEST));
+ assert!(digest == Digest::NONE || digest == Digest::MD5);
+ }
+ }
+
+ delete_app_key(&keystore2, alias).unwrap();
+}
+
+// Below macros generate tests for generating EC keys with curves EcCurve::P_224, EcCurve::P_256,
+// EcCurve::P_384, EcCurve::P_521 and various digest modes. Tests tries to create operations using
+// the generated keys. Operations with digest modes `SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and
+// SHA-2 512` should be created successfully. Creation of operations with digest modes NONE and
+// MD5 should fail with an error code `UNSUPPORTED_DIGEST`.
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_none_ec_p224,
+ Digest::NONE,
+ EcCurve::P_224
+);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_md5_ec_p224,
+ Digest::MD5,
+ EcCurve::P_224
+);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p224, Digest::SHA1, EcCurve::P_224);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p224, Digest::SHA_2_224, EcCurve::P_224);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p224, Digest::SHA_2_256, EcCurve::P_224);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p224, Digest::SHA_2_384, EcCurve::P_224);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p224, Digest::SHA_2_512, EcCurve::P_224);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_none_ec_p256,
+ Digest::NONE,
+ EcCurve::P_256
+);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_md5_ec_p256,
+ Digest::MD5,
+ EcCurve::P_256
+);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p256, Digest::SHA1, EcCurve::P_256);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p256, Digest::SHA_2_224, EcCurve::P_256);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p256, Digest::SHA_2_256, EcCurve::P_256);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p256, Digest::SHA_2_384, EcCurve::P_256);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p256, Digest::SHA_2_512, EcCurve::P_256);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_none_ec_p384,
+ Digest::NONE,
+ EcCurve::P_384
+);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_md5_ec_p384,
+ Digest::MD5,
+ EcCurve::P_384
+);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p384, Digest::SHA1, EcCurve::P_384);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p384, Digest::SHA_2_224, EcCurve::P_384);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p384, Digest::SHA_2_256, EcCurve::P_384);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p384, Digest::SHA_2_384, EcCurve::P_384);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p384, Digest::SHA_2_512, EcCurve::P_384);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_none_ec_p521,
+ Digest::NONE,
+ EcCurve::P_521
+);
+test_ec_sign_key_op_with_none_or_md5_digest!(
+ sign_ec_key_op_md5_ec_p521,
+ Digest::MD5,
+ EcCurve::P_521
+);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha1_ec_p521, Digest::SHA1, EcCurve::P_521);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha224_ec_p521, Digest::SHA_2_224, EcCurve::P_521);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha256_ec_p521, Digest::SHA_2_256, EcCurve::P_521);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha384_ec_p521, Digest::SHA_2_384, EcCurve::P_521);
+test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p521, Digest::SHA_2_512, EcCurve::P_521);
+
+/// This test will try to load the key with Domain::BLOB.
+/// INVALID_ARGUMENT error is expected.
+#[test]
+fn keystore2_get_key_entry_blob_fail() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ // Generate a key with domain as BLOB.
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::BLOB,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ None,
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Try to load the key using above generated KeyDescriptor.
+ let result = key_generations::map_ks_error(keystore2.getKeyEntry(&key_metadata.key));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
+
+ // Delete the generated key blob.
+ sec_level.deleteKey(&key_metadata.key).unwrap();
+}
+
+/// Try to generate a key with invalid Domain. `INVALID_ARGUMENT` error response is expected.
+#[test]
+fn keystore2_generate_key_invalid_domain() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_invalid_test_key_{}", getuid());
+
+ let result = key_generations::map_ks_error(key_generations::generate_ec_key(
+ &sec_level,
+ Domain(99), // Invalid domain.
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
+}
+
+/// Try to generate a EC key without providing the curve.
+/// `UNSUPPORTED_EC_CURVE or UNSUPPORTED_KEY_SIZE` error response is expected.
+#[test]
+fn keystore2_generate_ec_key_missing_curve() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_ec_no_curve_test_key_{}", getuid());
+
+ // Don't provide EC curve.
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256);
+
+ let result = key_generations::map_ks_error(sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: key_generations::SELINUX_SHELL_NAMESPACE,
+ alias: Some(alias),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ ));
+ assert!(result.is_err());
+ let err = result.unwrap_err();
+ assert!(matches!(
+ err,
+ Error::Km(ErrorCode::UNSUPPORTED_EC_CURVE) | Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE)
+ ));
+}
+
+/// Try to generate a EC key with curve `CURVE_25519` having `SIGN and AGREE_KEY` purposes.
+/// `INCOMPATIBLE_PURPOSE` error response is expected.
+#[test]
+fn keystore2_generate_ec_key_25519_multi_purpose() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_ec_no_curve_test_key_{}", getuid());
+
+ // Specify `SIGN and AGREE_KEY` purposes.
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .ec_curve(EcCurve::CURVE_25519)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::AGREE_KEY)
+ .digest(Digest::SHA_2_256);
+
+ let result = key_generations::map_ks_error(sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: key_generations::SELINUX_SHELL_NAMESPACE,
+ alias: Some(alias),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+}
+
+/// Generate EC key with curve `CURVE_25519` and digest mode NONE. Try to create an operation using
+/// generated key. `CURVE_25519` key should support `Digest::NONE` digest mode and test should be
+/// able to create an operation successfully.
+#[test]
+fn keystore2_ec_25519_generate_key_success() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_ec_25519_none_test_key_gen_{}", getuid());
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias),
+ EcCurve::CURVE_25519,
+ Digest::NONE,
+ )
+ .unwrap();
+
+ let op_response = sec_level
+ .createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::NONE),
+ false,
+ )
+ .unwrap();
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+}
+
+/// Generate EC keys with curve `CURVE_25519` and digest modes `MD5, SHA1, SHA-2 224, SHA-2 256,
+/// SHA-2 384 and SHA-2 512`. Try to create operations using generated keys. `CURVE_25519` keys
+/// shouldn't support these digest modes. Test should fail to create operations with an error
+/// `UNSUPPORTED_DIGEST`.
+#[test]
+fn keystore2_ec_25519_generate_key_fail() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let digests = [
+ Digest::MD5,
+ Digest::SHA1,
+ Digest::SHA_2_224,
+ Digest::SHA_2_256,
+ Digest::SHA_2_384,
+ Digest::SHA_2_512,
+ ];
+
+ for digest in digests {
+ let alias = format!("ks_ec_25519_test_key_gen_{}{}", getuid(), digest.0);
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ EcCurve::CURVE_25519,
+ digest,
+ )
+ .unwrap();
+
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest),
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err());
+ }
+}
+
+/// Generate a EC key with `SHA_2_256` digest mode. Try to create an operation with digest mode
+/// other than `SHA_2_256`. Creation of an operation with generated key should fail with
+/// `INCOMPATIBLE_DIGEST` error as there is a mismatch of digest mode in key authorizations.
+#[test]
+fn keystore2_create_op_with_incompatible_key_digest() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_ec_test_incomp_key_digest";
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .unwrap();
+
+ let digests =
+ [Digest::NONE, Digest::SHA1, Digest::SHA_2_224, Digest::SHA_2_384, Digest::SHA_2_512];
+
+ for digest in digests {
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest),
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_DIGEST), result.unwrap_err());
+ }
+}
+
+/// Generate a key in client#1 and try to use it in other client#2.
+/// Client#2 should fail to load the key as the it doesn't own the client#1 generated key.
+#[test]
+fn keystore2_key_owner_validation() {
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID_1: u32 = 10601;
+
+ let uid1 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_1;
+ let gid1 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_1;
+ let alias = "ks_owner_check_test_key";
+
+ // Client#1: Generate a key and create an operation using generated key.
+ // Wait until the parent notifies to continue. Once the parent notifies, this operation
+ // is expected to be completed successfully.
+ let mut child_handle = execute_op_run_as_child(
+ TARGET_CTX,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ Uid::from_raw(uid1),
+ Gid::from_raw(gid1),
+ ForcedOp(false),
+ );
+
+ // Wait until (client#1) child process notifies us to continue, so that there will be a key
+ // generated by client#1.
+ child_handle.recv();
+
+ // Client#2: This child will try to load the key generated by client#1.
+ const APPLICATION_ID_2: u32 = 10602;
+ let uid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2;
+ let gid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2;
+ unsafe {
+ run_as::run_as(TARGET_CTX, Uid::from_raw(uid2), Gid::from_raw(gid2), move || {
+ let keystore2_inst = get_keystore_service();
+ let result =
+ key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ }));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+ });
+ };
+
+ // Notify the child process (client#1) to resume and finish.
+ child_handle.send(&BarrierReached {});
+ assert!(
+ (child_handle.get_result() == TestOutcome::Ok),
+ "Client#1 failed to complete the operation."
+ );
+}
+
+/// Generate EC key with BLOB as domain. Generated key should be returned to caller as key blob.
+/// Verify that `blob` field in the `KeyDescriptor` is not empty and should have the key blob.
+/// Try to use this key for performing a sample operation and the operation should complete
+/// successfully.
+#[test]
+fn keystore2_generate_key_with_blob_domain() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::BLOB,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ None,
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .unwrap();
+
+ assert!(key_metadata.certificate.is_some());
+ assert!(key_metadata.certificateChain.is_none());
+
+ // Must have the key blob.
+ assert!(key_metadata.key.blob.is_some());
+
+ let op_response = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256),
+ false,
+ ))
+ .unwrap();
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+
+ // Delete the generated key blob.
+ sec_level.deleteKey(&key_metadata.key).unwrap();
+}
diff --git a/keystore2/tests/keystore2_client_grant_key_tests.rs b/keystore2/tests/keystore2_client_grant_key_tests.rs
new file mode 100644
index 0000000..827a0de
--- /dev/null
+++ b/keystore2/tests/keystore2_client_grant_key_tests.rs
@@ -0,0 +1,206 @@
+// 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Digest::Digest, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission,
+ ResponseCode::ResponseCode,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error, run_as,
+};
+
+use crate::keystore2_client_test_utils::perform_sample_sign_operation;
+
+/// Generate an EC signing key and grant it to the user with given access vector.
+fn generate_ec_key_and_grant_to_user(
+ grantee_uid: i32,
+ access_vector: i32,
+) -> binder::Result<KeyDescriptor> {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("{}{}", "ks_grant_test_key_1", getuid());
+
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias),
+ None,
+ None,
+ )
+ .unwrap();
+
+ keystore2.grant(&key_metadata.key, grantee_uid, access_vector)
+}
+
+/// Try to grant a key with permission that does not map to any of the `KeyPermission` values.
+/// An error is expected with values that does not map to set of permissions listed in
+/// `KeyPermission`.
+#[test]
+fn keystore2_grant_key_with_invalid_perm_expecting_syserror() {
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ let grantee_uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ let invalid_access_vector = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0 << 19;
+
+ let result = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
+ grantee_uid.try_into().unwrap(),
+ invalid_access_vector,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::SYSTEM_ERROR), result.unwrap_err());
+}
+
+/// Try to grant a key with empty access vector `KeyPermission::NONE`, should be able to grant a
+/// key with empty access vector successfully. In grantee context try to use the granted key, it
+/// should fail to load the key with permission denied error.
+#[test]
+fn keystore2_grant_key_with_perm_none() {
+ static TARGET_SU_CTX: &str = "u:r:su:s0";
+
+ static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+
+ let grant_key_nspace = unsafe {
+ run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
+ let empty_access_vector = KeyPermission::NONE.0;
+
+ let grant_key = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
+ GRANTEE_UID.try_into().unwrap(),
+ empty_access_vector,
+ ))
+ .unwrap();
+
+ assert_eq!(grant_key.domain, Domain::GRANT);
+
+ grant_key.nspace
+ })
+ };
+
+ // In grantee context try to load the key, it should fail to load the granted key as it is
+ // granted with empty access vector.
+ unsafe {
+ run_as::run_as(
+ GRANTEE_CTX,
+ Uid::from_raw(GRANTEE_UID),
+ Gid::from_raw(GRANTEE_GID),
+ move || {
+ let keystore2 = get_keystore_service();
+
+ let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
+ domain: Domain::GRANT,
+ nspace: grant_key_nspace,
+ alias: None,
+ blob: None,
+ }));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ },
+ )
+ };
+}
+
+/// Grant a key to the user (grantee) with `GET_INFO|USE` key permissions. Verify whether grantee
+/// can succeed in loading the granted key and try to perform simple operation using this granted
+/// key. Grantee should be able to load the key and use the key to perform crypto operation
+/// successfully. Try to delete the granted key in grantee context where it is expected to fail to
+/// delete it as `DELETE` permission is not granted.
+#[test]
+fn keystore2_grant_get_info_use_key_perm() {
+ static TARGET_SU_CTX: &str = "u:r:su:s0";
+
+ static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10001;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+
+ // Generate a key and grant it to a user with GET_INFO|USE key permissions.
+ let grant_key_nspace = unsafe {
+ run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
+ let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
+ let grant_key = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
+ GRANTEE_UID.try_into().unwrap(),
+ access_vector,
+ ))
+ .unwrap();
+
+ assert_eq!(grant_key.domain, Domain::GRANT);
+
+ grant_key.nspace
+ })
+ };
+
+ // In grantee context load the key and try to perform crypto operation.
+ unsafe {
+ run_as::run_as(
+ GRANTEE_CTX,
+ Uid::from_raw(GRANTEE_UID),
+ Gid::from_raw(GRANTEE_GID),
+ move || {
+ let keystore2 = get_keystore_service();
+ let sec_level =
+ keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ // Load the granted key.
+ let key_entry_response = keystore2
+ .getKeyEntry(&KeyDescriptor {
+ domain: Domain::GRANT,
+ nspace: grant_key_nspace,
+ alias: None,
+ blob: None,
+ })
+ .unwrap();
+
+ // Perform sample crypto operation using granted key.
+ let op_response = sec_level
+ .createOperation(
+ &key_entry_response.metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ )
+ .unwrap();
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+
+ // Try to delete the key, it is expected to be fail with permission denied error.
+ let result = key_generations::map_ks_error(keystore2.deleteKey(&KeyDescriptor {
+ domain: Domain::GRANT,
+ nspace: grant_key_nspace,
+ alias: None,
+ blob: None,
+ }));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ },
+ )
+ };
+}
diff --git a/keystore2/tests/keystore2_client_hmac_key_tests.rs b/keystore2/tests/keystore2_client_hmac_key_tests.rs
new file mode 100644
index 0000000..6bb8001
--- /dev/null
+++ b/keystore2/tests/keystore2_client_hmac_key_tests.rs
@@ -0,0 +1,305 @@
+// 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose,
+ SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error,
+};
+
+use crate::keystore2_client_test_utils::perform_sample_sign_operation;
+
+/// Generate HMAC key with given parameters and perform a sample operation using generated key.
+fn create_hmac_key_and_operation(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ alias: &str,
+ key_size: i32,
+ mac_len: i32,
+ min_mac_len: i32,
+ digest: Digest,
+) -> Result<(), binder::Status> {
+ let key_metadata =
+ key_generations::generate_hmac_key(sec_level, alias, key_size, min_mac_len, digest)?;
+
+ let op_response = sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(digest)
+ .mac_length(mac_len),
+ false,
+ )?;
+
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+
+ Ok(())
+}
+
+/// Generate HMAC keys with various digest modes [SHA1, SHA_2_224, SHA_2_256, SHA_2_384,
+/// SHA_2_512]. Create an operation using generated keys. Test should create operations
+/// successfully.
+#[test]
+fn keystore2_hmac_key_op_success() {
+ let digests =
+ [Digest::SHA1, Digest::SHA_2_224, Digest::SHA_2_256, Digest::SHA_2_384, Digest::SHA_2_512];
+ let min_mac_len = 128;
+ let mac_len = 128;
+ let key_size = 128;
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ for digest in digests {
+ let alias = format!("ks_hmac_test_key_{}", digest.0);
+
+ assert_eq!(
+ Ok(()),
+ create_hmac_key_and_operation(
+ &sec_level,
+ &alias,
+ key_size,
+ mac_len,
+ min_mac_len,
+ digest,
+ )
+ );
+ }
+}
+
+/// Generate HMAC keys with various key lengths. For invalid key sizes, key generation
+/// should fail with an error code `UNSUPPORTED_KEY_SIZE`.
+#[test]
+fn keystore2_hmac_gen_keys_fails_expect_unsupported_key_size() {
+ let min_mac_len = 256;
+ let digest = Digest::SHA_2_256;
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ for key_size in 0..513 {
+ let alias = format!("ks_hmac_test_key_{}", key_size);
+ let result = key_generations::map_ks_error(key_generations::generate_hmac_key(
+ &sec_level,
+ &alias,
+ key_size,
+ min_mac_len,
+ digest,
+ ));
+
+ match result {
+ Ok(_) => {
+ assert!((key_size >= 64 && key_size % 8 == 0));
+ }
+ Err(e) => {
+ assert_eq!(e, Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE));
+ assert!((key_size < 64 || key_size % 8 != 0), "Unsupported KeySize: {}", key_size);
+ }
+ }
+ }
+}
+
+/// Generate HMAC keys with various min-mac-lengths. For invalid min-mac-length, key generation
+/// should fail with an error code `UNSUPPORTED_MIN_MAC_LENGTH`.
+#[test]
+fn keystore2_hmac_gen_keys_fails_expect_unsupported_min_mac_length() {
+ let digest = Digest::SHA_2_256;
+ let key_size = 128;
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ for min_mac_len in 0..257 {
+ let alias = format!("ks_hmac_test_key_mml_{}", min_mac_len);
+ match key_generations::map_ks_error(key_generations::generate_hmac_key(
+ &sec_level,
+ &alias,
+ key_size,
+ min_mac_len,
+ digest,
+ )) {
+ Ok(_) => {
+ assert!((min_mac_len >= 64 && min_mac_len % 8 == 0));
+ }
+ Err(e) => {
+ assert_eq!(e, Error::Km(ErrorCode::UNSUPPORTED_MIN_MAC_LENGTH));
+ assert!(
+ (min_mac_len < 64 || min_mac_len % 8 != 0),
+ "Unsupported MinMacLength: {}",
+ min_mac_len
+ );
+ }
+ }
+ }
+}
+
+/// Try to generate HMAC key with multiple digests in key authorizations list.
+/// Test fails to generate a key with multiple digests with an error code `UNSUPPORTED_DIGEST`.
+#[test]
+fn keystore2_hmac_gen_key_multi_digests_fails_expect_unsupported_digest() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_hmac_test_key_multi_dig";
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::HMAC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .key_size(128)
+ .min_mac_length(128)
+ .digest(Digest::SHA1)
+ .digest(Digest::SHA_2_256);
+
+ let result = key_generations::map_ks_error(sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err());
+}
+
+/// Try to generate HMAC key without providing digest mode. HMAC key generation with
+/// no digest should fail with an error code `UNSUPPORTED_DIGEST`.
+#[test]
+fn keystore2_hmac_gen_key_no_digests_fails_expect_unsupported_digest() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_hmac_test_key_no_dig";
+ let gen_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::HMAC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .key_size(128)
+ .min_mac_length(128);
+
+ let result = key_generations::map_ks_error(sec_level.generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ },
+ None,
+ &gen_params,
+ 0,
+ b"entropy",
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err());
+}
+
+/// Try to generate a HMAC key with NONE digest mode, it should fail with `UNSUPPORTED_DIGEST`
+/// error code.
+#[test]
+fn keystore2_hmac_gen_key_with_none_digest_fails_expect_unsupported_digest() {
+ let min_mac_len = 128;
+ let key_size = 128;
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_hmac_test_key_fail";
+ let result = key_generations::map_ks_error(key_generations::generate_hmac_key(
+ &sec_level,
+ alias,
+ key_size,
+ min_mac_len,
+ Digest::NONE,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err());
+}
+
+/// Generate HMAC key with min-mac-len of 128 bits for the digest modes Digest::SHA1 and
+/// Digest::SHA_2_224. Try to create an operation with generated key and mac-length greater than
+/// digest length. Test should fail to create an operation with an error code
+/// `UNSUPPORTED_MAC_LENGTH`.
+#[test]
+fn keystore2_hmac_key_op_with_mac_len_greater_than_digest_len_fail() {
+ let digests = [Digest::SHA1, Digest::SHA_2_224];
+ let min_mac_len = 128;
+ let mac_len = 256;
+ let key_size = 128;
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ for digest in digests {
+ let alias = format!("ks_hmac_test_key_{}", digest.0);
+
+ let result = key_generations::map_ks_error(create_hmac_key_and_operation(
+ &sec_level,
+ &alias,
+ key_size,
+ mac_len,
+ min_mac_len,
+ digest,
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_MAC_LENGTH), result.unwrap_err());
+ }
+}
+
+/// Generate HMAC key with min-mac-len of 128 bits for the digest modes Digest::SHA1 and
+/// Digest::SHA_2_224. Try to create an operation with generated key and mac-length less than
+/// min-mac-length. Test should fail to create an operation with an error code
+/// `INVALID_MAC_LENGTH`.
+#[test]
+fn keystore2_hmac_key_op_with_mac_len_less_than_min_mac_len_fail() {
+ let digests = [Digest::SHA1, Digest::SHA_2_224];
+ let min_mac_len = 128;
+ let mac_len = 64;
+ let key_size = 128;
+
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ for digest in digests {
+ let alias = format!("ks_hmac_test_key_{}", digest.0);
+
+ let result = key_generations::map_ks_error(create_hmac_key_and_operation(
+ &sec_level,
+ &alias,
+ key_size,
+ mac_len,
+ min_mac_len,
+ digest,
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_MAC_LENGTH), result.unwrap_err());
+ }
+}
diff --git a/keystore2/tests/keystore2_client_import_keys_tests.rs b/keystore2/tests/keystore2_client_import_keys_tests.rs
new file mode 100644
index 0000000..abf35b5
--- /dev/null
+++ b/keystore2/tests/keystore2_client_import_keys_tests.rs
@@ -0,0 +1,374 @@
+// 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;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
+ ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
+ SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error,
+};
+
+use crate::keystore2_client_test_utils::{
+ has_trusty_keymint, perform_sample_asym_sign_verify_op, perform_sample_hmac_sign_verify_op,
+ perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT,
+};
+
+pub fn import_rsa_sign_key_and_perform_sample_operation(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ import_params: authorizations::AuthSetBuilder,
+) {
+ let key_metadata =
+ key_generations::import_rsa_2048_key(sec_level, domain, nspace, alias, import_params)
+ .unwrap();
+
+ perform_sample_asym_sign_verify_op(
+ sec_level,
+ &key_metadata,
+ Some(PaddingMode::RSA_PSS),
+ Some(Digest::SHA_2_256),
+ );
+}
+
+/// Import RSA key and verify imported key parameters. Try to create an operation using the
+/// imported key. Test should be able to create an operation successfully.
+#[test]
+fn keystore2_rsa_import_key_success() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_rsa_key_test_import_1_{}{}", getuid(), 2048);
+
+ let import_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::RSA)
+ .digest(Digest::SHA_2_256)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .padding_mode(PaddingMode::RSA_PSS)
+ .key_size(2048)
+ .rsa_public_exponent(65537)
+ .cert_not_before(0)
+ .cert_not_after(253402300799000);
+
+ import_rsa_sign_key_and_perform_sample_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias),
+ import_params,
+ );
+}
+
+/// Import RSA key without providing key-size and public exponent in import key parameters list.
+/// Let Key-size and public-exponent to be determined from the imported key material. Verify
+/// imported key parameters. Try to create an operation using the imported key. Test should be
+/// able to create an operation successfully.
+#[test]
+fn keystore2_rsa_import_key_determine_key_size_and_pub_exponent() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_rsa_key_test_import_2_{}{}", getuid(), 2048);
+
+ // key-size and public-exponent shouldn't be specified in import key parameters list.
+ let import_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::RSA)
+ .digest(Digest::SHA_2_256)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .padding_mode(PaddingMode::RSA_PSS)
+ .cert_not_before(0)
+ .cert_not_after(253402300799000);
+
+ import_rsa_sign_key_and_perform_sample_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias),
+ import_params,
+ );
+}
+
+/// Try to import RSA key with wrong key size as import-key-parameter. Test should fail to import
+/// a key with `IMPORT_PARAMETER_MISMATCH` error code.
+#[test]
+fn keystore2_rsa_import_key_fails_with_keysize_param_mismatch_error() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_rsa_key_test_import_3_{}{}", getuid(), 2048);
+
+ let import_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::RSA)
+ .digest(Digest::SHA_2_256)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .padding_mode(PaddingMode::RSA_PSS)
+ .key_size(1024) // Wrong key size is specified, (actual key-size is 2048).
+ .rsa_public_exponent(65537)
+ .cert_not_before(0)
+ .cert_not_after(253402300799000);
+
+ let result = key_generations::map_ks_error(sec_level.importKey(
+ &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
+ None,
+ &import_params,
+ 0,
+ key_generations::RSA_2048_KEY,
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::IMPORT_PARAMETER_MISMATCH), result.unwrap_err());
+}
+
+/// Try to import RSA key with wrong public-exponent as import-key-parameter.
+/// Test should fail to import a key with `IMPORT_PARAMETER_MISMATCH` error code.
+#[test]
+fn keystore2_rsa_import_key_fails_with_public_exponent_param_mismatch_error() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_rsa_key_test_import_4_{}{}", getuid(), 2048);
+
+ let import_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::RSA)
+ .digest(Digest::SHA_2_256)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .padding_mode(PaddingMode::RSA_PSS)
+ .key_size(2048)
+ .rsa_public_exponent(3) // This doesn't match the key.
+ .cert_not_before(0)
+ .cert_not_after(253402300799000);
+
+ let result = key_generations::map_ks_error(sec_level.importKey(
+ &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
+ None,
+ &import_params,
+ 0,
+ key_generations::RSA_2048_KEY,
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::IMPORT_PARAMETER_MISMATCH), result.unwrap_err());
+}
+
+/// Try to import a key with multiple purposes. Test should fail to import a key with
+/// `INCOMPATIBLE_PURPOSE` error code. If the backend is `keymaster` then `importKey` shall be
+/// successful.
+#[test]
+fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_rsa_key_test_import_5_{}{}", getuid(), 2048);
+
+ let import_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::RSA)
+ .digest(Digest::SHA_2_256)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::ATTEST_KEY)
+ .padding_mode(PaddingMode::RSA_PSS)
+ .key_size(2048)
+ .rsa_public_exponent(65537)
+ .cert_not_before(0)
+ .cert_not_after(253402300799000);
+
+ let result = key_generations::map_ks_error(sec_level.importKey(
+ &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
+ None,
+ &import_params,
+ 0,
+ key_generations::RSA_2048_KEY,
+ ));
+
+ if has_trusty_keymint() {
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+ } else {
+ assert!(result.is_ok());
+ }
+}
+
+/// Import EC key and verify imported key parameters. Let ec-curve to be determined from the
+/// imported key material. Try to create an operation using the imported key. Test should be
+/// able to create an operation successfully.
+#[test]
+fn keystore2_import_ec_key_success() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_ec_key_test_import_1_{}{}", getuid(), 256);
+
+ // Don't specify ec-curve.
+ let import_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .digest(Digest::SHA_2_256)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .cert_not_before(0)
+ .cert_not_after(253402300799000);
+
+ let key_metadata = key_generations::import_ec_p_256_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias),
+ import_params,
+ )
+ .expect("Failed to import EC key.");
+
+ perform_sample_asym_sign_verify_op(&sec_level, &key_metadata, None, Some(Digest::SHA_2_256));
+}
+
+/// Try to import EC key with wrong ec-curve as import-key-parameter. Test should fail to import a
+/// key with `IMPORT_PARAMETER_MISMATCH` error code.
+#[test]
+fn keystore2_ec_import_key_fails_with_mismatch_curve_error() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_ec_key_test_import_1_{}{}", getuid(), 256);
+
+ let import_params = authorizations::AuthSetBuilder::new()
+ .no_auth_required()
+ .algorithm(Algorithm::EC)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_224) // It doesn't match with key material.
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .cert_not_before(0)
+ .cert_not_after(253402300799000);
+
+ let result = key_generations::map_ks_error(sec_level.importKey(
+ &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
+ None,
+ &import_params,
+ 0,
+ key_generations::EC_P_256_KEY,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::IMPORT_PARAMETER_MISMATCH), result.unwrap_err());
+}
+
+/// Import AES key and verify key parameters. Try to create an operation using the imported key.
+/// Test should be able to create an operation successfully.
+#[test]
+fn keystore2_import_aes_key_success() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_aes_key_test_import_1_{}{}", getuid(), 256);
+ let key_metadata = key_generations::import_aes_key(&sec_level, Domain::APP, -1, Some(alias))
+ .expect("Failed to import AES key.");
+
+ let cipher_text = perform_sample_sym_key_encrypt_op(
+ &sec_level,
+ PaddingMode::PKCS7,
+ BlockMode::ECB,
+ &mut None,
+ None,
+ &key_metadata.key,
+ )
+ .unwrap();
+
+ assert!(cipher_text.is_some());
+
+ let plain_text = perform_sample_sym_key_decrypt_op(
+ &sec_level,
+ &cipher_text.unwrap(),
+ PaddingMode::PKCS7,
+ BlockMode::ECB,
+ &mut None,
+ None,
+ &key_metadata.key,
+ )
+ .unwrap();
+
+ assert!(plain_text.is_some());
+ assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec());
+}
+
+/// Import 3DES key and verify key parameters. Try to create an operation using the imported key.
+/// Test should be able to create an operation successfully.
+#[test]
+fn keystore2_import_3des_key_success() {
+ let keystore2 = get_keystore_service();
+ let sec_level = key_generations::map_ks_error(
+ keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT),
+ )
+ .unwrap();
+
+ let alias = format!("ks_3des_key_test_import_1_{}{}", getuid(), 168);
+
+ let key_metadata = key_generations::import_3des_key(&sec_level, Domain::APP, -1, Some(alias))
+ .expect("Failed to import 3DES key.");
+
+ let cipher_text = perform_sample_sym_key_encrypt_op(
+ &sec_level,
+ PaddingMode::PKCS7,
+ BlockMode::ECB,
+ &mut None,
+ None,
+ &key_metadata.key,
+ )
+ .unwrap();
+
+ assert!(cipher_text.is_some());
+
+ let plain_text = perform_sample_sym_key_decrypt_op(
+ &sec_level,
+ &cipher_text.unwrap(),
+ PaddingMode::PKCS7,
+ BlockMode::ECB,
+ &mut None,
+ None,
+ &key_metadata.key,
+ )
+ .unwrap();
+
+ assert!(plain_text.is_some());
+ assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec());
+}
+
+/// Import HMAC key and verify key parameters. Try to create an operation using the imported key.
+/// Test should be able to create an operation successfully.
+#[test]
+fn keystore2_import_hmac_key_success() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("ks_hmac_key_test_import_1_{}", getuid());
+
+ let key_metadata = key_generations::import_hmac_key(&sec_level, Domain::APP, -1, Some(alias))
+ .expect("Failed to import HMAC key.");
+
+ perform_sample_hmac_sign_verify_op(&sec_level, &key_metadata.key);
+}
diff --git a/keystore2/tests/keystore2_client_key_id_domain_tests.rs b/keystore2/tests/keystore2_client_key_id_domain_tests.rs
new file mode 100644
index 0000000..09b1378
--- /dev/null
+++ b/keystore2/tests/keystore2_client_key_id_domain_tests.rs
@@ -0,0 +1,257 @@
+// 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;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error,
+};
+
+use crate::keystore2_client_test_utils::perform_sample_sign_operation;
+
+/// Try to generate a key with `Domain::KEY_ID`, test should fail with an error code
+/// `SYSTEM_ERROR`. `Domain::KEY_ID` is not allowed to use for generating a key. Key id is returned
+/// by Keystore2 after a key has been mapped from an alias.
+#[test]
+fn keystore2_generate_key_with_key_id_domain_expect_sys_error() {
+ let alias = "ks_gen_key_id_test_key";
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let result = key_generations::map_ks_error(key_generations::generate_ec_key(
+ &sec_level,
+ Domain::KEY_ID,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias.to_string()),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::SYSTEM_ERROR), result.unwrap_err());
+}
+
+/// Generate a key and try to load the generated key using KEY_ID as domain. Create an
+/// operation using key which is loaded with domain as KEY_ID. Test should create an operation
+/// successfully.
+#[test]
+fn keystore2_find_key_with_key_id_as_domain() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = "ks_key_id_test_key";
+
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .expect("Failed to generate a EC key.");
+
+ // Try to load the above generated key with KEY_ID as domain.
+ let key_entry_response = keystore2
+ .getKeyEntry(&KeyDescriptor {
+ domain: Domain::KEY_ID,
+ nspace: key_metadata.key.nspace,
+ alias: Some(alias.to_string()),
+ blob: None,
+ })
+ .expect("Error in getKeyEntry to load a key with domain KEY_ID.");
+
+ // Verify above found key is same the one generated.
+ assert_eq!(key_metadata.key, key_entry_response.metadata.key);
+ assert_eq!(key_metadata.certificate, key_entry_response.metadata.certificate);
+ assert_eq!(key_metadata.certificateChain, key_entry_response.metadata.certificateChain);
+ assert_eq!(key_metadata.key.nspace, key_entry_response.metadata.key.nspace);
+
+ // Try to create an operation using above loaded key, operation should be created
+ // successfully.
+ let op_response = sec_level
+ .createOperation(
+ &key_entry_response.metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ )
+ .expect("Error in creation of operation.");
+
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+}
+
+/// Generate a key with an alias. Generate another key and bind it to the same alias.
+/// Try to create an operation using previously generated key. Creation of an operation should
+/// fail because previously generated key material is no longer accessible. Test should successfully
+/// create an operation using the rebound key.
+#[test]
+fn keystore2_key_id_alias_rebind_verify_by_alias() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_key_id_test_alias_rebind_1_{}", getuid());
+
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .expect("Failed to generate a EC key.");
+
+ // Generate a key with same alias as above generated key, so that alias will be rebound
+ // to this key.
+ let new_key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .expect("Failed to generate a rebound EC key.");
+
+ assert_ne!(key_metadata.key, new_key_metadata.key);
+ assert_ne!(key_metadata.certificate, new_key_metadata.certificate);
+ assert_ne!(key_metadata.key.nspace, new_key_metadata.key.nspace);
+
+ // Try to create an operation using previously generated key_metadata.
+ // It should fail as previously generated key material is no longer remains valid.
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256),
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+
+ // Try to create an operation using rebound key, operation should be created
+ // successfully.
+ let op_response = sec_level
+ .createOperation(
+ &new_key_metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ )
+ .expect("Error in creation of operation using rebound key.");
+
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+}
+
+/// Generate a key with an alias. Load the generated key with `Domain::KEY_ID`. Generate another
+/// key and bind it to the same alias. Try to create an operation using the key loaded with domain
+/// `KEY_ID`. Creation of an operation should fail because originally loaded key no longer exists.
+/// Test should successfully create an operation using the rebound key.
+#[test]
+fn keystore2_key_id_alias_rebind_verify_by_key_id() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_key_id_test_alias_rebind_2_{}", getuid());
+
+ let key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .expect("Failed to generate a EC key.");
+
+ // Load the above generated key with KEY_ID as domain.
+ let key_entry_response = keystore2
+ .getKeyEntry(&KeyDescriptor {
+ domain: Domain::KEY_ID,
+ nspace: key_metadata.key.nspace,
+ alias: Some(alias.to_string()),
+ blob: None,
+ })
+ .expect("Error in getKeyEntry to load a key with domain KEY_ID.");
+
+ // Verify above found key is same the one generated.
+ assert_eq!(key_metadata.key, key_entry_response.metadata.key);
+ assert_eq!(key_metadata.certificate, key_entry_response.metadata.certificate);
+ assert_eq!(key_metadata.certificateChain, key_entry_response.metadata.certificateChain);
+ assert_eq!(key_metadata.key.nspace, key_entry_response.metadata.key.nspace);
+
+ // Generate another key with same alias as above generated key, so that alias will be rebound
+ // to this key.
+ let new_key_metadata = key_generations::generate_ec_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias),
+ EcCurve::P_256,
+ Digest::SHA_2_256,
+ )
+ .expect("Failed to generate a rebound EC key.");
+
+ // Verify that an alias is rebound to a new key.
+ assert_eq!(key_metadata.key.alias, new_key_metadata.key.alias);
+ assert_ne!(key_metadata.key, new_key_metadata.key);
+ assert_ne!(key_metadata.certificate, new_key_metadata.certificate);
+ assert_ne!(key_metadata.key.nspace, new_key_metadata.key.nspace);
+
+ // Try to create an operation using previously loaded key_entry_response.
+ // It should fail as previously generated key material is no longer valid.
+ let result = key_generations::map_ks_error(sec_level.createOperation(
+ &key_entry_response.metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256),
+ false,
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+
+ // Try to create an operation using rebound key, operation should be created
+ // successfully.
+ let op_response = sec_level
+ .createOperation(
+ &new_key_metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ )
+ .expect("Error in creation of operation using rebound key.");
+
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+}
diff --git a/keystore2/tests/keystore2_client_list_entries_tests.rs b/keystore2/tests/keystore2_client_list_entries_tests.rs
new file mode 100644
index 0000000..def9d94
--- /dev/null
+++ b/keystore2/tests/keystore2_client_list_entries_tests.rs
@@ -0,0 +1,187 @@
+// 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor,
+ KeyPermission::KeyPermission, ResponseCode::ResponseCode,
+};
+
+use keystore2_test_utils::{get_keystore_service, key_generations, key_generations::Error, run_as};
+
+/// Try to find a key with given key parameters using `listEntries` API.
+fn key_alias_exists(
+ keystore2: &binder::Strong<dyn IKeystoreService>,
+ domain: Domain,
+ nspace: i64,
+ alias: String,
+) -> bool {
+ let key_descriptors = keystore2.listEntries(domain, nspace).unwrap();
+ let alias_count = key_descriptors
+ .into_iter()
+ .map(|key| key.alias.unwrap())
+ .filter(|key_alias| *key_alias == alias)
+ .count();
+
+ alias_count != 0
+}
+
+/// List key entries with domain as SELINUX and APP.
+/// 1. Generate a key with domain as SELINUX and find this key entry in list of keys retrieved from
+/// `listEntries` with domain SELINUX. Test should be able find this key entry successfully.
+/// 2. Grant above generated Key to a user.
+/// 3. In a user context, generate a new key with domain as APP. Try to list the key entries with
+/// domain APP. Test should find only one key entry that should be the key generated in user
+/// context. GRANT keys shouldn't be part of this list.
+#[test]
+fn keystore2_list_entries_success() {
+ static GRANTOR_SU_CTX: &str = "u:r:su:s0";
+ static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+
+ const USER_ID: u32 = 91;
+ const APPLICATION_ID: u32 = 10006;
+ static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ static GRANTEE_GID: u32 = GRANTEE_UID;
+
+ unsafe {
+ run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = format!("list_entries_grant_key1_{}", getuid());
+
+ // Make sure there is no key exist with this `alias` in `SELINUX` domain and
+ // `SELINUX_SHELL_NAMESPACE` namespace.
+ if key_alias_exists(
+ &keystore2,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ alias.to_string(),
+ ) {
+ keystore2
+ .deleteKey(&KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: key_generations::SELINUX_SHELL_NAMESPACE,
+ alias: Some(alias.to_string()),
+ blob: None,
+ })
+ .unwrap();
+ }
+
+ // Generate a key with above defined `alias`.
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias.to_string()),
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Verify that above generated key entry is listed with domain SELINUX and
+ // namespace SELINUX_SHELL_NAMESPACE
+ assert!(key_alias_exists(
+ &keystore2,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ alias,
+ ));
+
+ // Grant a key with GET_INFO permission.
+ let access_vector = KeyPermission::GET_INFO.0;
+ keystore2
+ .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
+ .unwrap();
+ })
+ };
+
+ // In user context validate list of key entries associated with it.
+ unsafe {
+ run_as::run_as(
+ GRANTEE_CTX,
+ Uid::from_raw(GRANTEE_UID),
+ Gid::from_raw(GRANTEE_GID),
+ move || {
+ let keystore2 = get_keystore_service();
+ let sec_level =
+ keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("list_entries_success_key{}", getuid());
+
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Make sure there is only one key entry exist and that should be the same key
+ // generated in this user context. Granted key shouldn't be included in this list.
+ let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap();
+ assert_eq!(1, key_descriptors.len());
+
+ let key = key_descriptors.get(0).unwrap();
+ assert_eq!(key.alias, Some(alias));
+ assert_eq!(key.nspace, GRANTEE_UID.try_into().unwrap());
+ assert_eq!(key.domain, Domain::APP);
+
+ keystore2.deleteKey(&key_metadata.key).unwrap();
+
+ let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap();
+ assert_eq!(0, key_descriptors.len());
+ },
+ )
+ };
+}
+
+/// Try to list the key entries with domain SELINUX from user context where user doesn't possesses
+/// `GET_INFO` permission for specified namespace. Test should fail to list key entries with error
+/// response code `PERMISSION_DENIED`.
+#[test]
+fn keystore2_list_entries_fails_perm_denied() {
+ let auid = 91 * AID_USER_OFFSET + 10001;
+ let agid = 91 * AID_USER_OFFSET + 10001;
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+
+ unsafe {
+ run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
+ let keystore2 = get_keystore_service();
+
+ let result = key_generations::map_ks_error(
+ keystore2.listEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
+ );
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ })
+ };
+}
+
+/// Try to list key entries with domain BLOB. Test should fail with error repose code
+/// `INVALID_ARGUMENT`.
+#[test]
+fn keystore2_list_entries_fails_invalid_arg() {
+ let keystore2 = get_keystore_service();
+
+ let result = key_generations::map_ks_error(
+ keystore2.listEntries(Domain::BLOB, key_generations::SELINUX_SHELL_NAMESPACE),
+ );
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
+}
diff --git a/keystore2/tests/keystore2_client_operation_tests.rs b/keystore2/tests/keystore2_client_operation_tests.rs
new file mode 100644
index 0000000..e1102dd
--- /dev/null
+++ b/keystore2/tests/keystore2_client_operation_tests.rs
@@ -0,0 +1,452 @@
+// 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 std::thread;
+use std::thread::JoinHandle;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ CreateOperationResponse::CreateOperationResponse, Domain::Domain,
+ IKeystoreOperation::IKeystoreOperation, ResponseCode::ResponseCode,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error, run_as,
+};
+
+use crate::keystore2_client_test_utils::{
+ create_signing_operation, execute_op_run_as_child, perform_sample_sign_operation,
+ BarrierReached, ForcedOp, TestOutcome,
+};
+
+/// Create `max_ops` number child processes with the given context and perform an operation under each
+/// child process.
+pub fn create_operations(
+ target_ctx: &'static str,
+ forced_op: ForcedOp,
+ max_ops: i32,
+) -> Vec<run_as::ChildHandle<TestOutcome, BarrierReached>> {
+ let alias = format!("ks_op_test_key_{}", getuid());
+ let base_gid = 99 * AID_USER_OFFSET + 10001;
+ let base_uid = 99 * AID_USER_OFFSET + 10001;
+ (0..max_ops)
+ .into_iter()
+ .map(|i| {
+ execute_op_run_as_child(
+ target_ctx,
+ Domain::APP,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias.to_string()),
+ Uid::from_raw(base_uid + (i as u32)),
+ Gid::from_raw(base_gid + (i as u32)),
+ forced_op,
+ )
+ })
+ .collect()
+}
+
+/// Executes an operation in a thread. Expect an `OPERATION_BUSY` error in case of operation
+/// failure. Returns True if `OPERATION_BUSY` error is encountered otherwise returns false.
+fn perform_op_busy_in_thread(op: binder::Strong<dyn IKeystoreOperation>) -> JoinHandle<bool> {
+ thread::spawn(move || {
+ for _n in 1..1000 {
+ match key_generations::map_ks_error(op.update(b"my message")) {
+ Ok(_) => continue,
+ Err(e) => {
+ assert_eq!(Error::Rc(ResponseCode::OPERATION_BUSY), e);
+ return true;
+ }
+ }
+ }
+ let sig = op.finish(None, None).unwrap();
+ assert!(sig.is_some());
+ false
+ })
+}
+
+/// This test verifies that backend service throws BACKEND_BUSY error when all
+/// operations slots are full. This test creates operations in child processes and
+/// collects the status of operations performed in each child proc and determines
+/// whether any child proc exited with error status.
+#[test]
+fn keystore2_backend_busy_test() {
+ const MAX_OPS: i32 = 100;
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+
+ let mut child_handles = create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS);
+
+ // Wait until all child procs notifies us to continue,
+ // so that there are definitely enough operations outstanding to trigger a BACKEND_BUSY.
+ for ch in child_handles.iter_mut() {
+ ch.recv();
+ }
+ // Notify each child to resume and finish.
+ for ch in child_handles.iter_mut() {
+ ch.send(&BarrierReached {});
+ }
+
+ // Collect the result and validate whether backend busy has occurred.
+ let mut busy_count = 0;
+ for ch in child_handles.into_iter() {
+ if ch.get_result() == TestOutcome::BackendBusy {
+ busy_count += 1;
+ }
+ }
+ assert!(busy_count > 0)
+}
+
+/// This test confirms that forced operation is having high pruning power.
+/// 1. Initially create regular operations such that there are enough operations outstanding
+/// to trigger BACKEND_BUSY.
+/// 2. Then, create a forced operation. System should be able to prune one of the regular
+/// operations and create a slot for forced operation successfully.
+#[test]
+fn keystore2_forced_op_after_backendbusy_test() {
+ const MAX_OPS: i32 = 100;
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+
+ // Create regular operations.
+ let mut child_handles = create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS);
+
+ // Wait until all child procs notifies us to continue, so that there are enough
+ // operations outstanding to trigger a BACKEND_BUSY.
+ for ch in child_handles.iter_mut() {
+ ch.recv();
+ }
+
+ // Create a forced operation.
+ let auid = 99 * AID_USER_OFFSET + 10604;
+ let agid = 99 * AID_USER_OFFSET + 10604;
+ unsafe {
+ run_as::run_as(
+ key_generations::TARGET_VOLD_CTX,
+ Uid::from_raw(auid),
+ Gid::from_raw(agid),
+ move || {
+ let alias = format!("ks_prune_forced_op_key_{}", getuid());
+
+ // To make room for this forced op, system should be able to prune one of the
+ // above created regular operations and create a slot for this forced operation
+ // successfully.
+ create_signing_operation(
+ ForcedOp(true),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::SELINUX,
+ 100,
+ Some(alias),
+ )
+ .expect("Client failed to create forced operation after BACKEND_BUSY state.");
+ },
+ );
+ };
+
+ // Notify each child to resume and finish.
+ for ch in child_handles.iter_mut() {
+ ch.send(&BarrierReached {});
+ }
+
+ // Collect the results of above created regular operations.
+ let mut pruned_count = 0;
+ let mut busy_count = 0;
+ let mut _other_err = 0;
+ for ch in child_handles.into_iter() {
+ match ch.get_result() {
+ TestOutcome::BackendBusy => {
+ busy_count += 1;
+ }
+ TestOutcome::InvalidHandle => {
+ pruned_count += 1;
+ }
+ _ => {
+ _other_err += 1;
+ }
+ }
+ }
+ // Verify that there should be at least one backend busy has occurred while creating
+ // above regular operations.
+ assert!(busy_count > 0);
+
+ // Verify that there should be at least one pruned operation which should have failed while
+ // performing operation.
+ assert!(pruned_count > 0);
+}
+
+/// This test confirms that forced operations can't be pruned.
+/// 1. Creates an initial forced operation and tries to complete the operation after BACKEND_BUSY
+/// error is triggered.
+/// 2. Create MAX_OPS number of forced operations so that definitely enough number of operations
+/// outstanding to trigger a BACKEND_BUSY.
+/// 3. Try to use initially created forced operation (in step #1) and able to perform the
+/// operation successfully. This confirms that none of the later forced operations evicted the
+/// initial forced operation.
+#[test]
+fn keystore2_max_forced_ops_test() {
+ const MAX_OPS: i32 = 100;
+ let auid = 99 * AID_USER_OFFSET + 10205;
+ let agid = 99 * AID_USER_OFFSET + 10205;
+
+ // Create initial forced operation in a child process
+ // and wait for the parent to notify to perform operation.
+ let alias = format!("ks_forced_op_key_{}", getuid());
+ let mut first_op_handle = execute_op_run_as_child(
+ key_generations::TARGET_SU_CTX,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias),
+ Uid::from_raw(auid),
+ Gid::from_raw(agid),
+ ForcedOp(true),
+ );
+
+ // Wait until above child proc notifies us to continue, so that there is definitely a forced
+ // operation outstanding to perform a operation.
+ first_op_handle.recv();
+
+ // Create MAX_OPS number of forced operations.
+ let mut child_handles =
+ create_operations(key_generations::TARGET_SU_CTX, ForcedOp(true), MAX_OPS);
+
+ // Wait until all child procs notifies us to continue, so that there are enough operations
+ // outstanding to trigger a BACKEND_BUSY.
+ for ch in child_handles.iter_mut() {
+ ch.recv();
+ }
+
+ // Notify initial created forced operation to continue performing the operations.
+ first_op_handle.send(&BarrierReached {});
+
+ // Collect initially created forced operation result and is expected to complete operation
+ // successfully.
+ let first_op_result = first_op_handle.get_result();
+ assert_eq!(first_op_result, TestOutcome::Ok);
+
+ // Notify each child to resume and finish.
+ for ch in child_handles.iter_mut() {
+ ch.send(&BarrierReached {});
+ }
+
+ // Collect the result and validate whether backend busy has occurred with MAX_OPS number
+ // of forced operations.
+ let busy_count = child_handles
+ .into_iter()
+ .map(|ch| ch.get_result())
+ .filter(|r| *r == TestOutcome::BackendBusy)
+ .count();
+ assert!(busy_count > 0);
+}
+
+/// This test will verify the use case with the same owner(UID) requesting `n` number of operations.
+/// This test confirms that when all operation slots are full and a new operation is requested,
+/// an operation which is least recently used and lived longest will be pruned to make a room
+/// for a new operation. Pruning strategy should prevent the operations of the other owners(UID)
+/// from being pruned.
+///
+/// 1. Create an operation in a child process with `untrusted_app` context and wait for parent
+/// notification to complete the operation.
+/// 2. Let parent process create `n` number of operations such that there are enough operations
+/// outstanding to trigger cannibalizing their own sibling operations.
+/// 3. Sequentially try to use above created `n` number of operations and also add a new operation,
+/// so that it should trigger cannibalizing one of their own sibling operations.
+/// 3.1 While trying to use these pruned operations an `INVALID_OPERATION_HANDLE` error is
+/// expected as they are already pruned.
+/// 4. Notify the child process to resume and complete the operation. It is expected to complete the
+/// operation successfully.
+/// 5. Try to use the latest operation of parent. It is expected to complete the operation
+/// successfully.
+#[test]
+fn keystore2_ops_prune_test() {
+ const MAX_OPS: usize = 40; // This should be at least 32 with sec_level TEE.
+
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10601;
+
+ let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+
+ // Create an operation in an untrusted_app context. Wait until the parent notifies to continue.
+ // Once the parent notifies, this operation is expected to be completed successfully.
+ let alias = format!("ks_reg_op_key_{}", getuid());
+ let mut child_handle = execute_op_run_as_child(
+ TARGET_CTX,
+ Domain::APP,
+ -1,
+ Some(alias),
+ Uid::from_raw(uid),
+ Gid::from_raw(gid),
+ ForcedOp(false),
+ );
+
+ // Wait until child process notifies us to continue, so that an operation from child process is
+ // outstanding to complete the operation.
+ child_handle.recv();
+
+ // Generate a key to use in below operations.
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_prune_op_test_key_{}", getuid());
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias),
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Create multiple operations in this process to trigger cannibalizing sibling operations.
+ let mut ops: Vec<binder::Result<CreateOperationResponse>> = (0..MAX_OPS)
+ .into_iter()
+ .map(|_| {
+ sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ )
+ })
+ .collect();
+
+ // Sequentially try to use operation handles created above and also add a new operation.
+ for vec_index in 0..MAX_OPS {
+ match &ops[vec_index] {
+ Ok(CreateOperationResponse { iOperation: Some(op), .. }) => {
+ // Older operation handle is pruned, if we try to use that an error is expected.
+ assert_eq!(
+ Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)),
+ key_generations::map_ks_error(op.update(b"my message"))
+ );
+ }
+ _ => panic!("Operation should have created successfully."),
+ }
+
+ // Create a new operation, it should trigger to cannibalize one of their own sibling
+ // operations.
+ ops.push(
+ sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ ),
+ );
+ }
+
+ // Notify child process to continue the operation.
+ child_handle.send(&BarrierReached {});
+ assert!((child_handle.get_result() == TestOutcome::Ok), "Failed to perform an operation");
+
+ // Try to use the latest operation created by parent, should be able to use it successfully.
+ match ops.last() {
+ Some(Ok(CreateOperationResponse { iOperation: Some(op), .. })) => {
+ assert_eq!(Ok(()), key_generations::map_ks_error(perform_sample_sign_operation(op)));
+ }
+ _ => panic!("Operation should have created successfully."),
+ }
+}
+
+/// Try to create forced operations with various contexts -
+/// - untrusted_app
+/// - system_server
+/// - priv_app
+/// `PERMISSION_DENIED` error response is expected.
+#[test]
+fn keystore2_forced_op_perm_denied_test() {
+ static TARGET_CTXS: &[&str] =
+ &["u:r:untrusted_app:s0", "u:r:system_server:s0", "u:r:priv_app:s0"];
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10601;
+
+ let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+
+ for context in TARGET_CTXS.iter() {
+ unsafe {
+ run_as::run_as(context, Uid::from_raw(uid), Gid::from_raw(gid), move || {
+ let alias = format!("ks_app_forced_op_test_key_{}", getuid());
+ let result = key_generations::map_ks_error(create_signing_operation(
+ ForcedOp(true),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some(alias),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+ });
+ }
+ }
+}
+
+/// Try to create a forced operation with `vold` context.
+/// Should be able to create forced operation with `vold` context successfully.
+#[test]
+fn keystore2_forced_op_success_test() {
+ static TARGET_CTX: &str = "u:r:vold:s0";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10601;
+
+ let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+
+ unsafe {
+ run_as::run_as(TARGET_CTX, Uid::from_raw(uid), Gid::from_raw(gid), move || {
+ let alias = format!("ks_vold_forced_op_key_{}", getuid());
+ create_signing_operation(
+ ForcedOp(true),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::SELINUX,
+ key_generations::SELINUX_VOLD_NAMESPACE,
+ Some(alias),
+ )
+ .expect("Client with vold context failed to create forced operation.");
+ });
+ }
+}
+
+/// Create an operation and try to use this operation handle in multiple threads to perform
+/// operations. Test should fail to perform an operation with an error response `OPERATION_BUSY`
+/// when multiple threads try to access the operation handle at same time.
+#[test]
+fn keystore2_op_fails_operation_busy() {
+ let op_response = create_signing_operation(
+ ForcedOp(false),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some("op_busy_alias_test_key".to_string()),
+ )
+ .unwrap();
+
+ let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+
+ let th_handle_1 = perform_op_busy_in_thread(op.clone());
+ let th_handle_2 = perform_op_busy_in_thread(op);
+
+ let result1 = th_handle_1.join().unwrap();
+ let result2 = th_handle_2.join().unwrap();
+
+ assert!(result1 || result2);
+}
diff --git a/keystore2/tests/keystore2_client_rsa_key_tests.rs b/keystore2/tests/keystore2_client_rsa_key_tests.rs
new file mode 100644
index 0000000..3139c2b
--- /dev/null
+++ b/keystore2/tests/keystore2_client_rsa_key_tests.rs
@@ -0,0 +1,1916 @@
+// 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ BlockMode::BlockMode, Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose,
+ PaddingMode::PaddingMode, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ CreateOperationResponse::CreateOperationResponse, Domain::Domain,
+ IKeystoreSecurityLevel::IKeystoreSecurityLevel,
+};
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error,
+};
+
+use crate::keystore2_client_test_utils::{
+ delete_app_key, has_trusty_keymint, perform_sample_sign_operation, ForcedOp,
+};
+
+/// This macro is used for creating signing key operation tests using digests and paddings
+/// for various key sizes.
+macro_rules! test_rsa_sign_key_op {
+ ( $test_name:ident, $digest:expr, $key_size:expr, $padding:expr ) => {
+ #[test]
+ fn $test_name() {
+ perform_rsa_sign_key_op_success($digest, $key_size, stringify!($test_name), $padding);
+ }
+ };
+
+ ( $test_name:ident, $digest:expr, $padding:expr ) => {
+ #[test]
+ fn $test_name() {
+ perform_rsa_sign_key_op_failure($digest, stringify!($test_name), $padding);
+ }
+ };
+}
+
+/// This macro is used for creating encrypt/decrypt key operation tests using digests, mgf-digests
+/// and paddings for various key sizes.
+macro_rules! test_rsa_encrypt_key_op {
+ ( $test_name:ident, $digest:expr, $key_size:expr, $padding:expr ) => {
+ #[test]
+ fn $test_name() {
+ create_rsa_encrypt_decrypt_key_op_success(
+ $digest,
+ $key_size,
+ stringify!($test_name),
+ $padding,
+ None,
+ None,
+ );
+ }
+ };
+
+ ( $test_name:ident, $digest:expr, $key_size:expr, $padding:expr, $mgf_digest:expr ) => {
+ #[test]
+ fn $test_name() {
+ create_rsa_encrypt_decrypt_key_op_success(
+ $digest,
+ $key_size,
+ stringify!($test_name),
+ $padding,
+ $mgf_digest,
+ Some(BlockMode::ECB),
+ );
+ }
+ };
+}
+
+/// Generate a RSA key and create an operation using the generated key.
+fn create_rsa_key_and_operation(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ key_params: &key_generations::KeyParams,
+ op_purpose: KeyPurpose,
+ forced_op: ForcedOp,
+) -> binder::Result<CreateOperationResponse> {
+ let key_metadata =
+ key_generations::generate_rsa_key(sec_level, domain, nspace, alias, key_params, None)?;
+
+ let mut op_params = authorizations::AuthSetBuilder::new().purpose(op_purpose);
+
+ if let Some(value) = key_params.digest {
+ op_params = op_params.digest(value)
+ }
+ if let Some(value) = key_params.padding {
+ op_params = op_params.padding_mode(value);
+ }
+ if let Some(value) = key_params.mgf_digest {
+ op_params = op_params.mgf_digest(value);
+ }
+ if let Some(value) = key_params.block_mode {
+ op_params = op_params.block_mode(value)
+ }
+
+ sec_level.createOperation(&key_metadata.key, &op_params, forced_op.0)
+}
+
+/// Generate RSA signing key with given parameters and perform signing operation.
+fn perform_rsa_sign_key_op_success(
+ digest: Digest,
+ key_size: i32,
+ alias: &str,
+ padding: PaddingMode,
+) {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let op_response = create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(padding),
+ digest: Some(digest),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::SIGN,
+ ForcedOp(false),
+ )
+ .expect("Failed to create an operation.");
+
+ assert!(op_response.iOperation.is_some());
+ assert_eq!(
+ Ok(()),
+ key_generations::map_ks_error(perform_sample_sign_operation(
+ &op_response.iOperation.unwrap()
+ ))
+ );
+
+ delete_app_key(&keystore2, alias).unwrap();
+}
+
+/// Generate RSA signing key with given parameters and try to perform signing operation.
+/// Error `INCOMPATIBLE_DIGEST | UNKNOWN_ERROR` is expected while creating an opearation.
+fn perform_rsa_sign_key_op_failure(digest: Digest, alias: &str, padding: PaddingMode) {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(padding),
+ digest: Some(digest),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::SIGN,
+ ForcedOp(false),
+ ));
+ assert!(result.is_err());
+
+ if has_trusty_keymint() {
+ assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::UNKNOWN_ERROR));
+ } else {
+ assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::INCOMPATIBLE_DIGEST));
+ }
+
+ delete_app_key(&keystore2, alias).unwrap();
+}
+
+/// Generate RSA encrypt/decrypt key with given parameters and perform decrypt operation.
+fn create_rsa_encrypt_decrypt_key_op_success(
+ digest: Option<Digest>,
+ key_size: i32,
+ alias: &str,
+ padding: PaddingMode,
+ mgf_digest: Option<Digest>,
+ block_mode: Option<BlockMode>,
+) {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let result = create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT],
+ padding: Some(padding),
+ digest,
+ mgf_digest,
+ block_mode,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::DECRYPT,
+ ForcedOp(false),
+ );
+
+ assert!(result.is_ok());
+
+ delete_app_key(&keystore2, alias).unwrap();
+}
+
+// Below macros generate tests for generating RSA signing keys with -
+// Padding mode: RSA_PKCS1_1_5_SIGN
+// Digest modes: `NONE, MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512`
+// and create operations with generated keys. Tests should create operations successfully.
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_none_2048,
+ Digest::NONE,
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_md5_2048,
+ Digest::MD5,
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha1_2048,
+ Digest::SHA1,
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha224_2048,
+ Digest::SHA_2_224,
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha256_2048,
+ Digest::SHA_2_256,
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha384_2048,
+ Digest::SHA_2_384,
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha512_2048,
+ Digest::SHA_2_512,
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_none_3072,
+ Digest::NONE,
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_md5_3072,
+ Digest::MD5,
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha1_3072,
+ Digest::SHA1,
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha224_3072,
+ Digest::SHA_2_224,
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha256_3072,
+ Digest::SHA_2_256,
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha384_3072,
+ Digest::SHA_2_384,
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha512_3072,
+ Digest::SHA_2_512,
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_none_4096,
+ Digest::NONE,
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_md5_4096,
+ Digest::MD5,
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha1_4096,
+ Digest::SHA1,
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha224_4096,
+ Digest::SHA_2_224,
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha256_4096,
+ Digest::SHA_2_256,
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha384_4096,
+ Digest::SHA_2_384,
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+test_rsa_sign_key_op!(
+ sign_key_pkcs1_1_5_sha512_4096,
+ Digest::SHA_2_512,
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_SIGN
+);
+
+// Below macros generate tests for generating RSA signing keys with -
+// Padding mode: RSA_PSS
+// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512`
+// and create operations with generated keys. Tests should create operations
+// successfully.
+test_rsa_sign_key_op!(sign_key_pss_md5_2048, Digest::MD5, 2048, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha1_2048, Digest::SHA1, 2048, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha224_2048, Digest::SHA_2_224, 2048, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha256_2048, Digest::SHA_2_256, 2048, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha384_2048, Digest::SHA_2_384, 2048, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha512_2048, Digest::SHA_2_512, 2048, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_md5_3072, Digest::MD5, 3072, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha1_3072, Digest::SHA1, 3072, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha224_3072, Digest::SHA_2_224, 3072, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha256_3072, Digest::SHA_2_256, 3072, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha384_3072, Digest::SHA_2_384, 3072, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha512_3072, Digest::SHA_2_512, 3072, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_md5_4096, Digest::MD5, 4096, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha1_4096, Digest::SHA1, 4096, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha224_4096, Digest::SHA_2_224, 4096, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha256_4096, Digest::SHA_2_256, 4096, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha384_4096, Digest::SHA_2_384, 4096, PaddingMode::RSA_PSS);
+test_rsa_sign_key_op!(sign_key_pss_sha512_4096, Digest::SHA_2_512, 4096, PaddingMode::RSA_PSS);
+
+// Below macros generate tests for generating RSA signing keys with -
+// Padding mode: `NONE`
+// Digest mode `NONE`
+// and try to create operations with generated keys. Tests should create operations
+// successfully.
+test_rsa_sign_key_op!(sign_key_none_none_2048, Digest::NONE, 2048, PaddingMode::NONE);
+test_rsa_sign_key_op!(sign_key_none_none_3072, Digest::NONE, 3072, PaddingMode::NONE);
+test_rsa_sign_key_op!(sign_key_none_none_4096, Digest::NONE, 4096, PaddingMode::NONE);
+
+// Below macros generate tests for generating RSA signing keys with -
+// Padding mode: `NONE`
+// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512`
+// and create operations with generated keys. Tests should fail to create operations with
+// an error code `UNKNOWN_ERROR | INCOMPATIBLE_DIGEST`.
+test_rsa_sign_key_op!(sign_key_none_md5_2048, Digest::MD5, PaddingMode::NONE);
+test_rsa_sign_key_op!(sign_key_none_sha1_2048, Digest::SHA1, PaddingMode::NONE);
+test_rsa_sign_key_op!(sign_key_none_sha224_2048, Digest::SHA_2_224, PaddingMode::NONE);
+test_rsa_sign_key_op!(sign_key_none_sha256_2048, Digest::SHA_2_256, PaddingMode::NONE);
+test_rsa_sign_key_op!(sign_key_none_sha384_2048, Digest::SHA_2_384, PaddingMode::NONE);
+test_rsa_sign_key_op!(sign_key_none_sha512_2048, Digest::SHA_2_512, PaddingMode::NONE);
+
+// Below macros generate tests for generating RSA encryption keys with various digest mode
+// and padding mode combinations.
+// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512`
+// Padding modes: `NONE, RSA_PKCS1_1_5_ENCRYPT`
+// and try to create operations using generated keys, tests should create operations successfully.
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_none_2048,
+ Some(Digest::NONE),
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_md5_2048,
+ Some(Digest::MD5),
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha1_2048,
+ Some(Digest::SHA1),
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha224_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha256_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha384_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha512_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_none_3072,
+ Some(Digest::NONE),
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_md5_3072,
+ Some(Digest::MD5),
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha1_3072,
+ Some(Digest::SHA1),
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha224_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha256_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha384_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha512_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_none_4096,
+ Some(Digest::NONE),
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_md5_4096,
+ Some(Digest::MD5),
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha1_4096,
+ Some(Digest::SHA1),
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha224_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha256_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha384_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_sha512_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT
+);
+test_rsa_encrypt_key_op!(encrypt_key_none_none_2048, Some(Digest::NONE), 2048, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(encrypt_key_none_md5_2048, Some(Digest::MD5), 2048, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(encrypt_key_none_sha1_2048, Some(Digest::SHA1), 2048, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha224_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha256_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha384_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha512_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(encrypt_key_none_none_3072, Some(Digest::NONE), 3072, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(encrypt_key_none_md5_3072, Some(Digest::MD5), 3072, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(encrypt_key_none_sha1_3072, Some(Digest::SHA1), 3072, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha224_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha256_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha384_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha512_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(encrypt_key_none_none_4096, Some(Digest::NONE), 4096, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(encrypt_key_none_md5_4096, Some(Digest::MD5), 4096, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(encrypt_key_none_sha1_4096, Some(Digest::SHA1), 4096, PaddingMode::NONE);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha224_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha256_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha384_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::NONE
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_none_sha512_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::NONE
+);
+
+// Below macros generate tests for generating RSA keys with -
+// Padding Mode: `RSA_OAEP`
+// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512`
+// mgf-digests: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512`
+// and create a decrypt operations using generated keys. Tests should create operations
+// successfully.
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_md5_2048,
+ Some(Digest::MD5),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha1_2048,
+ Some(Digest::MD5),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha224_2048,
+ Some(Digest::MD5),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha256_2048,
+ Some(Digest::MD5),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha384_2048,
+ Some(Digest::MD5),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha512_2048,
+ Some(Digest::MD5),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_md5_2048,
+ Some(Digest::SHA1),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha1_2048,
+ Some(Digest::SHA1),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha224_2048,
+ Some(Digest::SHA1),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha256_2048,
+ Some(Digest::SHA1),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha384_2048,
+ Some(Digest::SHA1),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha512_2048,
+ Some(Digest::SHA1),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_md5_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha1_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha224_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha256_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha384_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha512_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_md5_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha1_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha224_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha256_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha384_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha512_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_md5_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha1_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha224_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha256_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha384_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha512_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_md5_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha1_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha224_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha256_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha384_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha512_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_md5_3072,
+ Some(Digest::MD5),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha1_3072,
+ Some(Digest::MD5),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha224_3072,
+ Some(Digest::MD5),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha256_3072,
+ Some(Digest::MD5),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha384_3072,
+ Some(Digest::MD5),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha512_3072,
+ Some(Digest::MD5),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_md5_3072,
+ Some(Digest::SHA1),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha1_3072,
+ Some(Digest::SHA1),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha224_3072,
+ Some(Digest::SHA1),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha256_3072,
+ Some(Digest::SHA1),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha384_3072,
+ Some(Digest::SHA1),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha512_3072,
+ Some(Digest::SHA1),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_md5_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha1_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha224_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha256_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha384_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha512_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_md5_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha1_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha224_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha256_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha384_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha512_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_md5_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha1_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha224_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha256_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha384_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha512_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_md5_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha1_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha224_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha256_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha384_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha512_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_md5_4096,
+ Some(Digest::MD5),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha1_4096,
+ Some(Digest::MD5),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha224_4096,
+ Some(Digest::MD5),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha256_4096,
+ Some(Digest::MD5),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha384_4096,
+ Some(Digest::MD5),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_sha512_4096,
+ Some(Digest::MD5),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_md5_4096,
+ Some(Digest::SHA1),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha1_4096,
+ Some(Digest::SHA1),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha224_4096,
+ Some(Digest::SHA1),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha256_4096,
+ Some(Digest::SHA1),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha384_4096,
+ Some(Digest::SHA1),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_sha512_4096,
+ Some(Digest::SHA1),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_md5_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha1_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha224_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha256_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha384_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_sha512_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_md5_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha1_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha224_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha256_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha384_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_sha512_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_md5_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha1_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha224_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha256_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha384_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_sha512_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_md5_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::MD5)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha1_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA1)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha224_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_224)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha256_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_256)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha384_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_384)
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_sha512_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ Some(Digest::SHA_2_512)
+);
+
+// Below macros generate tests for generating RSA keys with -
+// Padding mode: `RSA_OAEP`
+// Digest modes: `MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 384 and SHA-2 512`
+// and create a decrypt operations using generated keys. Tests should create operations
+// successfully.
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_no_mgf_2048,
+ Some(Digest::MD5),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_no_mgf_2048,
+ Some(Digest::SHA1),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_no_mgf_2048,
+ Some(Digest::SHA_2_224),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_no_mgf_2048,
+ Some(Digest::SHA_2_256),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_no_mgf_2048,
+ Some(Digest::SHA_2_384),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_no_mgf_2048,
+ Some(Digest::SHA_2_512),
+ 2048,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_no_mgf_3072,
+ Some(Digest::MD5),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_no_mgf_3072,
+ Some(Digest::SHA1),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_no_mgf_3072,
+ Some(Digest::SHA_2_224),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_no_mgf_3072,
+ Some(Digest::SHA_2_256),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_no_mgf_3072,
+ Some(Digest::SHA_2_384),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_no_mgf_3072,
+ Some(Digest::SHA_2_512),
+ 3072,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_md5_no_mgf_4096,
+ Some(Digest::MD5),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha1_no_mgf_4096,
+ Some(Digest::SHA1),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha224_no_mgf_4096,
+ Some(Digest::SHA_2_224),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha256_no_mgf_4096,
+ Some(Digest::SHA_2_256),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha384_no_mgf_4096,
+ Some(Digest::SHA_2_384),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_oaep_sha512_no_mgf_4096,
+ Some(Digest::SHA_2_512),
+ 4096,
+ PaddingMode::RSA_OAEP,
+ None
+);
+
+// Below macros generate tests for generating RSA encryption keys with only padding modes.
+// Padding modes: `NONE, RSA_PKCS1_1_5_ENCRYPT`
+// and try to create operations using generated keys, tests should create operations
+// successfully.
+test_rsa_encrypt_key_op!(encrypt_key_none_pad_2048, None, 2048, PaddingMode::NONE, None);
+test_rsa_encrypt_key_op!(encrypt_key_none_pad_3072, None, 3072, PaddingMode::NONE, None);
+test_rsa_encrypt_key_op!(encrypt_key_none_pad_4096, None, 4096, PaddingMode::NONE, None);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_pad_2048,
+ None,
+ 2048,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_pad_3072,
+ None,
+ 3072,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT,
+ None
+);
+test_rsa_encrypt_key_op!(
+ encrypt_key_pkcs1_1_5_pad_4096,
+ None,
+ 4096,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT,
+ None
+);
+
+/// Generate RSA signing key with -
+/// Padding mode: RSA_PSS
+/// Digest mode: `NONE`.
+/// Try to create an operation with this generated key. Test should fail to create an operation with
+/// `INCOMPATIBLE_DIGEST` error code.
+#[test]
+fn keystore2_rsa_generate_signing_key_padding_pss_fail() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_pss_none_key_op_test";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PSS),
+ digest: Some(Digest::NONE),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::SIGN,
+ ForcedOp(false),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_DIGEST), result.unwrap_err());
+}
+
+/// Generate RSA encryption key with -
+/// Digest mode: `NONE`
+/// Padding mode: `RSA_OAEP`
+/// Try to create an operation using generated key. Test should fail to create an operation
+/// with an error code `INCOMPATIBLE_DIGEST`.
+#[test]
+fn keystore2_rsa_generate_key_with_oaep_padding_fail() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_key_oaep_padding_fail_test";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT],
+ padding: Some(PaddingMode::RSA_OAEP),
+ digest: Some(Digest::NONE),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::DECRYPT,
+ ForcedOp(false),
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_DIGEST), result.unwrap_err());
+}
+
+/// Generate RSA keys without padding and digest modes. Try to create decrypt operation without
+/// digest and padding. Creation of an operation should fail with an error code
+/// `UNSUPPORTED_PADDING_MODE`.
+#[test]
+fn keystore2_rsa_generate_keys() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_key_unsupport_padding_test";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT],
+ padding: None,
+ digest: None,
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::DECRYPT,
+ ForcedOp(false),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err());
+}
+
+/// Generate a RSA encryption key. Try to create a signing operation with it, an error
+/// `INCOMPATIBLE_PURPOSE` is expected as the generated key doesn't support sign operation.
+#[test]
+fn keystore2_rsa_encrypt_key_op_invalid_purpose() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_test_key_1";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::SIGN,
+ ForcedOp(false),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+}
+
+/// Generate a RSA signing key. Try to create a decrypt operation with it, an error
+/// `INCOMPATIBLE_PURPOSE` is expected as the generated key doesn't support decrypt operation.
+#[test]
+fn keystore2_rsa_sign_key_op_invalid_purpose() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_test_key_2";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::DECRYPT,
+ ForcedOp(false),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+}
+
+/// Generate a RSA key with SIGN and AGREE_KEY purposes. Try to perform an operation using the
+/// generated key, an error `UNSUPPORTED_PURPOSE` is expected as RSA doesn't support AGREE_KEY.
+#[test]
+fn keystore2_rsa_key_unsupported_purpose() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_key_test_3";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::AGREE_KEY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::AGREE_KEY,
+ ForcedOp(false),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PURPOSE), result.unwrap_err());
+}
+
+/// Generate a RSA encrypt key with padding mode supported for signing. Try to create an operation
+/// using generated key, an error `UNSUPPORTED_PADDING_MODE` is expected with unsupported padding
+/// mode.
+#[test]
+fn keystore2_rsa_encrypt_key_unsupported_padding() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let paddings = [PaddingMode::RSA_PKCS1_1_5_SIGN, PaddingMode::RSA_PSS];
+
+ for padding in paddings {
+ let alias = format!("ks_rsa_encrypt_key_unsupported_pad_test{}", padding.0);
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT],
+ padding: Some(padding),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::DECRYPT,
+ ForcedOp(false),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err());
+ }
+}
+
+/// Generate a RSA signing key with padding mode supported for encryption. Try to create an
+/// operation using generated key, an error `UNSUPPORTED_PADDING_MODE` is expected with
+/// unsupported padding mode.
+#[test]
+fn keystore2_rsa_signing_key_unsupported_padding() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let paddings = [PaddingMode::RSA_PKCS1_1_5_ENCRYPT, PaddingMode::RSA_OAEP];
+
+ for padding in paddings {
+ let alias = format!("ks_rsa_sign_key_unsupported_pad_test_4_{}", padding.0);
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(padding),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::SIGN,
+ ForcedOp(false),
+ ));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err());
+ }
+}
+
+/// Generate a RSA encryption key. Try to perform encrypt operation using the generated
+/// key, an error `UNSUPPORTED_PURPOSE` is expected as encrypt operation is not supported
+/// with RSA key.
+#[test]
+fn keystore2_rsa_key_unsupported_op() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_key_test_5";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::ENCRYPT,
+ ForcedOp(false),
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PURPOSE), result.unwrap_err());
+}
+
+/// Generate a RSA key with encrypt, sign and verify purpose. Try to perform decrypt operation
+/// using the generated key, an error `INCOMPATIBLE_PURPOSE` is expected as the key is not
+/// generated with decrypt purpose.
+#[test]
+fn keystore2_rsa_key_missing_purpose() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_key_test_6";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::DECRYPT,
+ ForcedOp(false),
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+}
+
+/// Generate RSA encryption keys with OAEP padding mode and without digest mode. Try to create an
+/// operation with generated key, unsupported digest error is expected.
+#[test]
+fn keystore2_rsa_gen_keys_with_oaep_paddings_without_digest() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_key_padding_fail";
+ let result = key_generations::map_ks_error(create_rsa_key_and_operation(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 2048,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT],
+ padding: Some(PaddingMode::RSA_OAEP),
+ digest: None,
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ KeyPurpose::DECRYPT,
+ ForcedOp(false),
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err());
+}
+
+/// Generate RSA keys with unsupported key size, an error `UNSUPPORTED_KEY_SIZE` is expected.
+#[test]
+fn keystore2_rsa_gen_keys_unsupported_size() {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let alias = "ks_rsa_key_padding_fail";
+ let result = key_generations::map_ks_error(key_generations::generate_rsa_key(
+ &sec_level,
+ Domain::APP,
+ -1,
+ Some(alias.to_string()),
+ &key_generations::KeyParams {
+ key_size: 5120,
+ purpose: vec![KeyPurpose::ENCRYPT, KeyPurpose::SIGN, KeyPurpose::VERIFY],
+ padding: Some(PaddingMode::RSA_PKCS1_1_5_ENCRYPT),
+ digest: Some(Digest::SHA_2_256),
+ mgf_digest: None,
+ block_mode: None,
+ att_challenge: None,
+ att_app_id: None,
+ },
+ None,
+ ));
+
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_KEY_SIZE), result.unwrap_err());
+}
diff --git a/keystore2/tests/keystore2_client_test_utils.rs b/keystore2/tests/keystore2_client_test_utils.rs
new file mode 100644
index 0000000..758e88b
--- /dev/null
+++ b/keystore2/tests/keystore2_client_test_utils.rs
@@ -0,0 +1,350 @@
+// 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::{Gid, Uid};
+use serde::{Deserialize, Serialize};
+
+use openssl::hash::MessageDigest;
+use openssl::rsa::Padding;
+use openssl::sign::Verifier;
+use openssl::x509::X509;
+
+use binder::wait_for_interface;
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ BlockMode::BlockMode, Digest::Digest, ErrorCode::ErrorCode,
+ KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
+ SecurityLevel::SecurityLevel, Tag::Tag,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ CreateOperationResponse::CreateOperationResponse, Domain::Domain,
+ IKeystoreOperation::IKeystoreOperation, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
+ IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata,
+ KeyParameters::KeyParameters, ResponseCode::ResponseCode,
+};
+
+use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
+
+use keystore2_test_utils::{
+ authorizations, get_keystore_service, key_generations, key_generations::Error, run_as,
+};
+
+/// This enum is used to communicate between parent and child processes.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub enum TestOutcome {
+ Ok,
+ BackendBusy,
+ InvalidHandle,
+ OtherErr,
+}
+
+/// This is used to notify the child or parent process that the expected state is reched.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub struct BarrierReached;
+
+/// Forced operation.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ForcedOp(pub bool);
+
+/// Sample plain text input for encrypt operation.
+pub const SAMPLE_PLAIN_TEXT: &[u8] = b"my message 11111";
+
+pub const PACKAGE_MANAGER_NATIVE_SERVICE: &str = "package_native";
+pub const APP_ATTEST_KEY_FEATURE: &str = "android.hardware.keystore.app_attest_key";
+
+/// Determines whether app_attest_key_feature is supported or not.
+pub fn app_attest_key_feature_exists() -> bool {
+ let pm = wait_for_interface::<dyn IPackageManagerNative>(PACKAGE_MANAGER_NATIVE_SERVICE)
+ .expect("Failed to get package manager native service.");
+
+ pm.hasSystemFeature(APP_ATTEST_KEY_FEATURE, 0).expect("hasSystemFeature failed.")
+}
+
+#[macro_export]
+macro_rules! skip_test_if_no_app_attest_key_feature {
+ () => {
+ if !app_attest_key_feature_exists() {
+ return;
+ }
+ };
+}
+
+pub fn has_trusty_keymint() -> bool {
+ binder::is_declared("android.hardware.security.keymint.IKeyMintDevice/default")
+ .expect("Could not check for declared keymint interface")
+}
+
+/// Generate a EC_P256 key using given domain, namespace and alias.
+/// Create an operation using the generated key and perform sample signing operation.
+pub fn create_signing_operation(
+ forced_op: ForcedOp,
+ op_purpose: KeyPurpose,
+ op_digest: Digest,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+) -> binder::Result<CreateOperationResponse> {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level, domain, nspace, alias, None, None,
+ )
+ .unwrap();
+
+ sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new().purpose(op_purpose).digest(op_digest),
+ forced_op.0,
+ )
+}
+
+/// Performs sample signing operation.
+pub fn perform_sample_sign_operation(
+ op: &binder::Strong<dyn IKeystoreOperation>,
+) -> Result<(), binder::Status> {
+ op.update(b"my message")?;
+ let sig = op.finish(None, None)?;
+ assert!(sig.is_some());
+ Ok(())
+}
+
+/// Perform sample HMAC sign and verify operations.
+pub fn perform_sample_hmac_sign_verify_op(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ key: &KeyDescriptor,
+) {
+ let sign_op = sec_level
+ .createOperation(
+ key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256)
+ .mac_length(256),
+ false,
+ )
+ .unwrap();
+ assert!(sign_op.iOperation.is_some());
+
+ let op = sign_op.iOperation.unwrap();
+ op.update(b"my message").unwrap();
+ let sig = op.finish(None, None).unwrap();
+ assert!(sig.is_some());
+
+ let sig = sig.unwrap();
+ let verify_op = sec_level
+ .createOperation(
+ key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256),
+ false,
+ )
+ .unwrap();
+ assert!(verify_op.iOperation.is_some());
+
+ let op = verify_op.iOperation.unwrap();
+ let result = op.finish(Some(b"my message"), Some(&sig)).unwrap();
+ assert!(result.is_none());
+}
+
+/// Map KeyMint Digest values to OpenSSL MessageDigest.
+pub fn get_openssl_digest_mode(digest: Option<Digest>) -> MessageDigest {
+ match digest {
+ Some(Digest::MD5) => MessageDigest::md5(),
+ Some(Digest::SHA1) => MessageDigest::sha1(),
+ Some(Digest::SHA_2_224) => MessageDigest::sha224(),
+ Some(Digest::SHA_2_256) => MessageDigest::sha256(),
+ Some(Digest::SHA_2_384) => MessageDigest::sha384(),
+ Some(Digest::SHA_2_512) => MessageDigest::sha512(),
+ _ => MessageDigest::sha256(),
+ }
+}
+
+/// Map KeyMint PaddingMode values to OpenSSL Padding.
+pub fn get_openssl_padding_mode(padding: PaddingMode) -> Padding {
+ match padding {
+ PaddingMode::RSA_OAEP => Padding::PKCS1_OAEP,
+ PaddingMode::RSA_PSS => Padding::PKCS1_PSS,
+ PaddingMode::RSA_PKCS1_1_5_SIGN => Padding::PKCS1,
+ PaddingMode::RSA_PKCS1_1_5_ENCRYPT => Padding::PKCS1,
+ _ => Padding::NONE,
+ }
+}
+
+/// Perform sample sign and verify operations using RSA or EC key.
+pub fn perform_sample_asym_sign_verify_op(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ key_metadata: &KeyMetadata,
+ padding: Option<PaddingMode>,
+ digest: Option<Digest>,
+) {
+ let mut authorizations = authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN);
+ if let Some(value) = padding {
+ authorizations = authorizations.padding_mode(value);
+ }
+ if let Some(value) = digest {
+ authorizations = authorizations.digest(value);
+ }
+
+ let sign_op = sec_level.createOperation(&key_metadata.key, &authorizations, false).unwrap();
+ assert!(sign_op.iOperation.is_some());
+
+ let op = sign_op.iOperation.unwrap();
+ op.update(b"my message").unwrap();
+ let sig = op.finish(None, None).unwrap();
+ assert!(sig.is_some());
+
+ let sig = sig.unwrap();
+ let cert_bytes = key_metadata.certificate.as_ref().unwrap();
+ let cert = X509::from_der(cert_bytes.as_ref()).unwrap();
+ let pub_key = cert.public_key().unwrap();
+ let mut verifier = Verifier::new(get_openssl_digest_mode(digest), pub_key.as_ref()).unwrap();
+ if let Some(value) = padding {
+ verifier.set_rsa_padding(get_openssl_padding_mode(value)).unwrap();
+ }
+ verifier.update(b"my message").unwrap();
+ assert!(verifier.verify(&sig).unwrap());
+}
+
+/// Create new operation on child proc and perform simple operation after parent notification.
+pub fn execute_op_run_as_child(
+ target_ctx: &'static str,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ auid: Uid,
+ agid: Gid,
+ forced_op: ForcedOp,
+) -> run_as::ChildHandle<TestOutcome, BarrierReached> {
+ unsafe {
+ run_as::run_as_child(target_ctx, auid, agid, move |reader, writer| {
+ let result = key_generations::map_ks_error(create_signing_operation(
+ forced_op,
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ domain,
+ nspace,
+ alias,
+ ));
+
+ // Let the parent know that an operation has been started, then
+ // wait until the parent notifies us to continue, so the operation
+ // remains open.
+ writer.send(&BarrierReached {});
+ reader.recv();
+
+ // Continue performing the operation after parent notifies.
+ match &result {
+ Ok(CreateOperationResponse { iOperation: Some(op), .. }) => {
+ match key_generations::map_ks_error(perform_sample_sign_operation(op)) {
+ Ok(()) => TestOutcome::Ok,
+ Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {
+ TestOutcome::InvalidHandle
+ }
+ Err(e) => panic!("Error in performing op: {:#?}", e),
+ }
+ }
+ Ok(_) => TestOutcome::OtherErr,
+ Err(Error::Rc(ResponseCode::BACKEND_BUSY)) => TestOutcome::BackendBusy,
+ _ => TestOutcome::OtherErr,
+ }
+ })
+ .expect("Failed to create an operation.")
+ }
+}
+
+/// Get NONCE value from given key parameters list.
+pub fn get_op_nonce(parameters: &KeyParameters) -> Option<Vec<u8>> {
+ for key_param in ¶meters.keyParameter {
+ if key_param.tag == Tag::NONCE {
+ if let KeyParameterValue::Blob(val) = &key_param.value {
+ return Some(val.clone());
+ }
+ }
+ }
+ None
+}
+
+/// This performs sample encryption operation with given symmetric key (AES/3DES).
+/// It encrypts `SAMPLE_PLAIN_TEXT` of length 128-bits.
+pub fn perform_sample_sym_key_encrypt_op(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ padding_mode: PaddingMode,
+ block_mode: BlockMode,
+ nonce: &mut Option<Vec<u8>>,
+ mac_len: Option<i32>,
+ key: &KeyDescriptor,
+) -> binder::Result<Option<Vec<u8>>> {
+ let mut op_params = authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::ENCRYPT)
+ .padding_mode(padding_mode)
+ .block_mode(block_mode);
+ if let Some(value) = nonce {
+ op_params = op_params.nonce(value.to_vec());
+ }
+
+ if let Some(val) = mac_len {
+ op_params = op_params.mac_length(val);
+ }
+
+ let op_response = sec_level.createOperation(key, &op_params, false)?;
+ assert!(op_response.iOperation.is_some());
+ let op = op_response.iOperation.unwrap();
+ if op_response.parameters.is_some() && nonce.is_none() {
+ *nonce = get_op_nonce(&op_response.parameters.unwrap());
+ }
+ op.finish(Some(SAMPLE_PLAIN_TEXT), None)
+}
+
+/// This performs sample decryption operation with given symmetric key (AES/3DES).
+pub fn perform_sample_sym_key_decrypt_op(
+ sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ input: &[u8],
+ padding_mode: PaddingMode,
+ block_mode: BlockMode,
+ nonce: &mut Option<Vec<u8>>,
+ mac_len: Option<i32>,
+ key: &KeyDescriptor,
+) -> binder::Result<Option<Vec<u8>>> {
+ let mut op_params = authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::DECRYPT)
+ .padding_mode(padding_mode)
+ .block_mode(block_mode);
+ if let Some(value) = nonce {
+ op_params = op_params.nonce(value.to_vec());
+ }
+
+ if let Some(val) = mac_len {
+ op_params = op_params.mac_length(val);
+ }
+
+ let op_response = sec_level.createOperation(key, &op_params, false)?;
+ assert!(op_response.iOperation.is_some());
+ let op = op_response.iOperation.unwrap();
+ op.finish(Some(input), None)
+}
+
+/// Delete a key with domain APP.
+pub fn delete_app_key(
+ keystore2: &binder::Strong<dyn IKeystoreService>,
+ alias: &str,
+) -> binder::Result<()> {
+ keystore2.deleteKey(&KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias.to_string()),
+ blob: None,
+ })
+}
diff --git a/keystore2/tests/keystore2_client_tests.rs b/keystore2/tests/keystore2_client_tests.rs
new file mode 100644
index 0000000..41e3e36
--- /dev/null
+++ b/keystore2/tests/keystore2_client_tests.rs
@@ -0,0 +1,26 @@
+// 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.
+
+pub mod keystore2_client_3des_key_tests;
+pub mod keystore2_client_aes_key_tests;
+pub mod keystore2_client_attest_key_tests;
+pub mod keystore2_client_ec_key_tests;
+pub mod keystore2_client_grant_key_tests;
+pub mod keystore2_client_hmac_key_tests;
+pub mod keystore2_client_import_keys_tests;
+pub mod keystore2_client_key_id_domain_tests;
+pub mod keystore2_client_list_entries_tests;
+pub mod keystore2_client_operation_tests;
+pub mod keystore2_client_rsa_key_tests;
+pub mod keystore2_client_test_utils;
diff --git a/keystore2/tests/legacy_blobs/Android.bp b/keystore2/tests/legacy_blobs/Android.bp
new file mode 100644
index 0000000..92f2cc3
--- /dev/null
+++ b/keystore2/tests/legacy_blobs/Android.bp
@@ -0,0 +1,53 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_test {
+ name: "keystore2_legacy_blobs_test",
+ srcs: ["keystore2_legacy_blob_tests.rs"],
+ test_suites: [
+ "general-tests",
+ ],
+ // auto_gen_config: true,
+ test_config: "AndroidTest.xml",
+
+ rustlibs: [
+ "libkeystore2_with_test_utils",
+ "libkeystore2_crypto_rust",
+ "android.security.maintenance-rust",
+ "android.security.authorization-rust",
+ "librustutils",
+ "libkeystore2_test_utils",
+ "libnix",
+ "libanyhow",
+ "libbinder_rs",
+ "liblazy_static",
+ "liblibc",
+ "libserde",
+ "libthiserror",
+ ],
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ "keystore2_use_latest_aidl_rust",
+ ],
+ require_root: true,
+}
diff --git a/keystore2/test_utils/AndroidTest.xml b/keystore2/tests/legacy_blobs/AndroidTest.xml
similarity index 71%
rename from keystore2/test_utils/AndroidTest.xml
rename to keystore2/tests/legacy_blobs/AndroidTest.xml
index 24e277a..ea83fbf 100644
--- a/keystore2/test_utils/AndroidTest.xml
+++ b/keystore2/tests/legacy_blobs/AndroidTest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<!-- 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.
@@ -13,20 +13,22 @@
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.">
-<configuration description="Config to run keystore2_test_utils_test device tests.">
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+ <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_test_utils_test->/data/local/tmp/keystore2_test_utils_test"
+ 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_test_utils_test" />
+ <option name="module-name" value="keystore2_legacy_blobs_test" />
+ <option name="native-test-flag" value="--test-threads=1" />
</test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
new file mode 100644
index 0000000..32ecd03
--- /dev/null
+++ b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
@@ -0,0 +1,595 @@
+// 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_ascii_digit()).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 att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::BLOB,
+ SELINUX_SHELL_NAMESPACE,
+ None,
+ Some(att_challenge),
+ Some(att_app_id),
+ )
+ .expect("Failed to generate key blob");
+
+ // Create keystore file layout for user_99.
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap());
+ let 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 att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::BLOB,
+ SELINUX_SHELL_NAMESPACE,
+ None,
+ Some(att_challenge),
+ Some(att_app_id),
+ )
+ .expect("Failed to generate key blob");
+
+ // Create keystore file layout for user_98.
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key(SUPERKEY_SALT, 32).unwrap());
+ let 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 efa0389..f56cfab 100644
--- a/ondevice-signing/Android.bp
+++ b/ondevice-signing/Android.bp
@@ -74,6 +74,42 @@
],
}
+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,
+}
+
+genrule {
+ name: "statslog_odsign.h",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_odsign.h --module art --namespace art,metrics,statsd",
+ out: [
+ "statslog_odsign.h",
+ ],
+}
+
cc_binary {
name: "odsign",
defaults: [
@@ -82,21 +118,20 @@
cpp_std: "experimental",
init_rc: ["odsign.rc"],
srcs: [
- "odsign_main.cpp",
- "CertUtils.cpp",
"KeystoreKey.cpp",
"KeystoreHmacKey.cpp",
- "VerityUtils.cpp",
+ "odsign_main.cpp",
+ "StatsReporter.cpp",
],
+ generated_headers: ["statslog_odsign.h"],
header_libs: ["odrefresh_headers"],
static_libs: [
"libc++fs",
+ "libsigningutils",
"lib_odsign_proto",
- "lib_compos_proto",
],
-
shared_libs: [
"android.system.keystore2-V1-cpp",
"android.hardware.security.keymint-V1-cpp",
diff --git a/ondevice-signing/CertUtils.cpp b/ondevice-signing/CertUtils.cpp
index d67bea6..8fe0816 100644
--- a/ondevice-signing/CertUtils.cpp
+++ b/ondevice-signing/CertUtils.cpp
@@ -352,7 +352,7 @@
return extractPublicKey(X509_get_pubkey(cert.value().get()));
}
-Result<std::vector<uint8_t>> extractRsaPublicKey(EVP_PKEY* pkey) {
+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";
diff --git a/ondevice-signing/KeystoreHmacKey.cpp b/ondevice-signing/KeystoreHmacKey.cpp
index 09677d7..916cbbc 100644
--- a/ondevice-signing/KeystoreHmacKey.cpp
+++ b/ondevice-signing/KeystoreHmacKey.cpp
@@ -49,17 +49,14 @@
using android::base::unique_fd;
-// Keystore boot level that the odsign key uses
-static const int kOdsignBootLevel = 30;
-
-static KeyDescriptor getHmacKeyDescriptor() {
+static KeyDescriptor getHmacKeyDescriptor(const android::String16& keyAlias, int64_t keyNspace) {
// 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 = String16("ondevice-signing-hmac");
- descriptor.nspace = 101; // odsign_key
+ descriptor.alias = keyAlias + android::String16("-hmac");
+ descriptor.nspace = keyNspace;
});
return descriptor;
@@ -106,7 +103,7 @@
KeyParameter boot_level;
boot_level.tag = Tag::MAX_BOOT_LEVEL;
- boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(kOdsignBootLevel);
+ boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(mKeyBootLevel);
params.push_back(boot_level);
KeyMetadata metadata;
@@ -133,7 +130,7 @@
// 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>() == kOdsignBootLevel) {
+ if (auth.keyParameter.value.get<KeyParameterValue::integer>() == mKeyBootLevel) {
keyValid = true;
break;
}
@@ -152,9 +149,9 @@
}
}
-KeystoreHmacKey::KeystoreHmacKey() {
- mDescriptor = getHmacKeyDescriptor();
-}
+KeystoreHmacKey::KeystoreHmacKey(const android::String16& keyAlias, int64_t keyNspace,
+ int keyBootLevel)
+ : mDescriptor(getHmacKeyDescriptor(keyAlias, keyNspace)), mKeyBootLevel(keyBootLevel) {}
static std::vector<KeyParameter> getVerifyOpParameters() {
std::vector<KeyParameter> opParameters;
diff --git a/ondevice-signing/KeystoreHmacKey.h b/ondevice-signing/KeystoreHmacKey.h
index 782969a..1a815a3 100644
--- a/ondevice-signing/KeystoreHmacKey.h
+++ b/ondevice-signing/KeystoreHmacKey.h
@@ -31,7 +31,7 @@
using KeyDescriptor = ::android::system::keystore2::KeyDescriptor;
public:
- KeystoreHmacKey();
+ KeystoreHmacKey(const android::String16& keyAlias, int64_t keyNspace, int keyBootLevel);
android::base::Result<void> initialize(android::sp<IKeystoreService> service,
android::sp<IKeystoreSecurityLevel> securityLevel);
android::base::Result<std::string> sign(const std::string& message) const;
@@ -44,4 +44,6 @@
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 03bb6d5..6ce65d6 100644
--- a/ondevice-signing/KeystoreKey.cpp
+++ b/ondevice-signing/KeystoreKey.cpp
@@ -50,27 +50,24 @@
using android::base::Error;
using android::base::Result;
-// 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() {
+static KeyDescriptor getKeyDescriptor(const android::String16& keyAlias, int64_t keyNspace) {
// 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 = String16("ondevice-signing");
- descriptor.nspace = 101; // odsign_key
+ descriptor.alias = keyAlias;
+ descriptor.nspace = keyNspace;
});
return descriptor;
}
-KeystoreKey::KeystoreKey() {
- mDescriptor = getKeyDescriptor();
-}
+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) {}
Result<std::vector<uint8_t>> KeystoreKey::createKey() {
std::vector<KeyParameter> params;
@@ -113,7 +110,7 @@
KeyParameter boot_level;
boot_level.tag = Tag::MAX_BOOT_LEVEL;
- boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(kOdsignBootLevel);
+ boot_level.value = KeyParameterValue::make<KeyParameterValue::integer>(mKeyBootLevel);
params.push_back(boot_level);
KeyMetadata metadata;
@@ -137,7 +134,7 @@
return Error() << "Failed to sign public key.";
}
- if (!android::base::WriteStringToFile(*signature, kPublicKeySignature)) {
+ if (!android::base::WriteStringToFile(*signature, mSignedPubKeyPath)) {
return Error() << "Can't write public key signature.";
}
@@ -206,7 +203,7 @@
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>() == kOdsignBootLevel) {
+ if (auth.keyParameter.value.get<KeyParameterValue::integer>() == mKeyBootLevel) {
foundBootLevel = true;
break;
}
@@ -232,7 +229,7 @@
std::string publicKeyString = {publicKey->begin(), publicKey->end()};
std::string signature;
- if (!android::base::ReadFileToString(kPublicKeySignature, &signature)) {
+ if (!android::base::ReadFileToString(mSignedPubKeyPath, &signature)) {
return Error() << "Can't find signature for public key.";
}
@@ -256,13 +253,15 @@
return *existingKey;
}
-Result<SigningKey*> KeystoreKey::getInstance() {
- static KeystoreKey keystoreKey;
+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);
- if (!keystoreKey.initialize()) {
+ if (!keystoreKey->initialize()) {
return Error() << "Failed to initialize keystore key.";
} else {
- return &keystoreKey;
+ return keystoreKey;
}
}
diff --git a/ondevice-signing/KeystoreKey.h b/ondevice-signing/KeystoreKey.h
index f2fbb70..3c9a0ab 100644
--- a/ondevice-signing/KeystoreKey.h
+++ b/ondevice-signing/KeystoreKey.h
@@ -36,13 +36,16 @@
public:
virtual ~KeystoreKey(){};
- static android::base::Result<SigningKey*> getInstance();
+ static android::base::Result<SigningKey*> getInstance(const std::string& signedPubKeyPath,
+ const android::String16& keyAlias,
+ int64_t KeyNspace, int keyBootLevel);
virtual android::base::Result<std::string> sign(const std::string& message) const;
virtual android::base::Result<std::vector<uint8_t>> getPublicKey() const;
private:
- KeystoreKey();
+ KeystoreKey(std::string signedPubKeyPath, const android::String16& keyAlias, int64_t keyNspace,
+ int keyBootLevel);
bool initialize();
android::base::Result<std::vector<uint8_t>> verifyExistingKey();
android::base::Result<std::vector<uint8_t>> createKey();
@@ -53,4 +56,7 @@
android::sp<IKeystoreService> mService;
android::sp<IKeystoreSecurityLevel> mSecurityLevel;
std::vector<uint8_t> mPublicKey;
+
+ std::string mSignedPubKeyPath;
+ int mKeyBootLevel;
};
diff --git a/ondevice-signing/StatsReporter.cpp b/ondevice-signing/StatsReporter.cpp
new file mode 100644
index 0000000..e4e4a03
--- /dev/null
+++ b/ondevice-signing/StatsReporter.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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 constants in sync with those in OdsignStatsLogger.java.
+constexpr const char* kOdsignMetricsFile = "/data/misc/odsign/metrics/odsign-metrics.txt";
+constexpr const char* kComposMetricName = "comp_os_artifacts_check_record";
+constexpr const char* kOdsignMetricName = "odsign_record";
+
+StatsReporter::~StatsReporter() {
+ if (comp_os_artifacts_check_record_ == nullptr && !odsign_record_enabled_) {
+ 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;
+ }
+ if (chmod(kOdsignMetricsFile, 0644) != 0) {
+ PLOG(ERROR) << "Could not set correct file permissions for " << kOdsignMetricsFile;
+ return;
+ }
+
+ if (comp_os_artifacts_check_record_ != nullptr) {
+ odsign_metrics_file_ << kComposMetricName << ' '
+ << comp_os_artifacts_check_record_->current_artifacts_ok << ' '
+ << comp_os_artifacts_check_record_->comp_os_pending_artifacts_exists
+ << ' '
+ << comp_os_artifacts_check_record_->use_comp_os_generated_artifacts
+ << '\n';
+ }
+
+ if (odsign_record_enabled_) {
+ odsign_metrics_file_ << kOdsignMetricName << ' ' << odsign_record_.status << '\n';
+ }
+
+ odsign_metrics_file_.close();
+ if (!odsign_metrics_file_) {
+ PLOG(ERROR) << "Failed to close the file";
+ }
+}
+
+StatsReporter::CompOsArtifactsCheckRecord* StatsReporter::GetOrCreateComposArtifactsCheckRecord() {
+ if (comp_os_artifacts_check_record_ == nullptr) {
+ comp_os_artifacts_check_record_ = std::make_unique<CompOsArtifactsCheckRecord>();
+ }
+ return comp_os_artifacts_check_record_.get();
+}
diff --git a/ondevice-signing/StatsReporter.h b/ondevice-signing/StatsReporter.h
new file mode 100644
index 0000000..add7a11
--- /dev/null
+++ b/ondevice-signing/StatsReporter.h
@@ -0,0 +1,61 @@
+/*
+ * 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>
+
+#include "statslog_odsign.h"
+
+// 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 in sync with the 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;
+ };
+
+ // Keep in sync with the OdsignReported definition in proto_logging/stats/atoms.proto.
+ struct OdsignRecord {
+ int32_t status = art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_UNSPECIFIED;
+ };
+
+ // The report is flushed (from buffer) into a file by the destructor.
+ ~StatsReporter();
+
+ // Returns a mutable CompOS record. The pointer remains valid for the lifetime of this
+ // StatsReporter. If this function is not called, no CompOS record will be logged.
+ CompOsArtifactsCheckRecord* GetOrCreateComposArtifactsCheckRecord();
+
+ // Returns a mutable odsign record. The pointer remains valid for the lifetime of this
+ // StatsReporter.
+ OdsignRecord* GetOdsignRecord() { return &odsign_record_; }
+
+ // Enables/disables odsign metrics.
+ void SetOdsignRecordEnabled(bool value) { odsign_record_enabled_ = value; }
+
+ private:
+ // Temporary buffer which stores the metrics.
+ std::unique_ptr<CompOsArtifactsCheckRecord> comp_os_artifacts_check_record_;
+
+ OdsignRecord odsign_record_;
+ bool odsign_record_enabled_ = true;
+};
diff --git a/ondevice-signing/TEST_MAPPING b/ondevice-signing/TEST_MAPPING
new file mode 100644
index 0000000..4b2c8c6
--- /dev/null
+++ b/ondevice-signing/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "libsigningutils_test"
+ }
+ ]
+}
diff --git a/ondevice-signing/VerityUtils.cpp b/ondevice-signing/VerityUtils.cpp
index 2beb7eb..d5c7299 100644
--- a/ondevice-signing/VerityUtils.cpp
+++ b/ondevice-signing/VerityUtils.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <charconv>
#include <filesystem>
#include <map>
#include <span>
@@ -25,6 +26,8 @@
#include <sys/types.h>
#include <sys/wait.h>
+#include "android-base/errors.h"
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <asm/byteorder.h>
@@ -33,7 +36,6 @@
#include "CertUtils.h"
#include "SigningKey.h"
-#include "compos_signature.pb.h"
#define FS_VERITY_MAX_DIGEST_SIZE 64
@@ -42,13 +44,11 @@
using android::base::Result;
using android::base::unique_fd;
-using compos::proto::Signature;
-
static const char* kFsVerityInitPath = "/system/bin/fsverity_init";
-static const char* kSignatureExtension = ".signature";
+static const char* kFsVerityProcPath = "/proc/sys/fs/verity";
-static bool isSignatureFile(const std::filesystem::path& path) {
- return path.extension().native() == kSignatureExtension;
+bool SupportsFsVerity() {
+ return access(kFsVerityProcPath, F_OK) == 0;
}
static std::string toHex(std::span<const uint8_t> data) {
@@ -59,13 +59,30 @@
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;
}
-Result<std::vector<uint8_t>> createDigest(int fd) {
+static Result<std::vector<uint8_t>> createDigest(int fd) {
struct stat filestat;
int ret = fstat(fd, &filestat);
if (ret < 0) {
@@ -110,7 +127,6 @@
}
}
};
-} // namespace
template <typename T> using trailing_unique_ptr = std::unique_ptr<T, DeleteAsPODArray<T>>;
@@ -121,6 +137,32 @@
return trailing_unique_ptr<T>{ptr};
}
+static Result<std::string> measureFsVerity(int fd) {
+ auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
+ d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
+
+ if (ioctl(fd, FS_IOC_MEASURE_VERITY, d.get()) != 0) {
+ if (errno == ENODATA) {
+ return Error() << "File is not in fs-verity";
+ } else {
+ return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY";
+ }
+ }
+
+ return toHex({&d->digest[0], &d->digest[d->digest_size]});
+}
+
+static Result<std::string> measureFsVerity(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;
+ }
+
+ return measureFsVerity(fd.get());
+}
+
+} // namespace
+
static Result<std::vector<uint8_t>> signDigest(const SigningKey& key,
const std::vector<uint8_t>& digest) {
auto d = makeUniqueWithTrailingData<fsverity_formatted_digest>(digest.size());
@@ -138,7 +180,7 @@
return std::vector<uint8_t>(signed_digest->begin(), signed_digest->end());
}
-Result<void> enableFsVerity(int fd, std::span<uint8_t> pkcs7) {
+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());
@@ -155,15 +197,10 @@
return {};
}
-Result<std::string> enableFsVerity(const std::string& path, const SigningKey& key) {
- unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
- if (!fd.ok()) {
- return ErrnoError() << "Failed to open " << path;
- }
-
- auto digest = createDigest(fd.get());
+Result<std::string> enableFsVerity(int fd, const SigningKey& key) {
+ auto digest = createDigest(fd);
if (!digest.ok()) {
- return Error() << digest.error() << ": " << path;
+ return Error() << digest.error();
}
auto signed_digest = signDigest(key, digest.value());
@@ -176,15 +213,23 @@
return pkcs7_data.error();
}
- auto enabled = enableFsVerity(fd.get(), pkcs7_data.value());
+ auto enabled = enableFsVerity(fd, pkcs7_data.value());
if (!enabled.ok()) {
- return Error() << enabled.error() << ": " << path;
+ return Error() << enabled.error();
}
// Return the root hash as a hex string
return toHex(digest.value());
}
+static Result<bool> isFileInVerity(int fd) {
+ unsigned int flags;
+ if (ioctl(fd, FS_IOC_GETFLAGS, &flags) < 0) {
+ return ErrnoError() << "ioctl(FS_IOC_GETFLAGS) failed";
+ }
+ return (flags & FS_VERITY_FL) != 0;
+}
+
Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path,
const SigningKey& key) {
std::map<std::string, std::string> digests;
@@ -193,12 +238,19 @@
auto it = std::filesystem::recursive_directory_iterator(path, ec);
for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
if (it->is_regular_file()) {
- LOG(INFO) << "Adding " << it->path() << " to fs-verity...";
- auto result = enableFsVerity(it->path(), key);
- if (!result.ok()) {
- return result.error();
+ unique_fd fd(TEMP_FAILURE_RETRY(open(it->path().c_str(), O_RDONLY | O_CLOEXEC)));
+ if (!fd.ok()) {
+ return ErrnoError() << "Failed to open " << path;
}
- digests[it->path()] = *result;
+ auto enabled = OR_RETURN(isFileInVerity(fd));
+ if (!enabled) {
+ LOG(INFO) << "Adding " << it->path() << " to fs-verity...";
+ OR_RETURN(enableFsVerity(fd, key));
+ } else {
+ LOG(INFO) << it->path() << " was already in fs-verity.";
+ }
+ auto digest = OR_RETURN(measureFsVerity(fd));
+ digests[it->path()] = digest;
}
}
if (ec) {
@@ -208,32 +260,21 @@
return digests;
}
-Result<std::string> isFileInVerity(int fd) {
- auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
- d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
- auto ret = ioctl(fd, FS_IOC_MEASURE_VERITY, d.get());
- if (ret < 0) {
- if (errno == ENODATA) {
- return Error() << "File is not in fs-verity";
- } else {
- return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY";
- }
- }
- return toHex({&d->digest[0], &d->digest[d->digest_size]});
-}
-
-Result<std::string> isFileInVerity(const std::string& path) {
+Result<void> enableFsVerity(const std::string& path, const std::string& signature_path) {
unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
if (!fd.ok()) {
- return ErrnoError() << "Failed to open " << path;
+ return Error() << "Can't open " << path;
}
- auto digest = isFileInVerity(fd);
- if (!digest.ok()) {
- return Error() << digest.error() << ": " << path;
- }
+ std::string signature;
+ android::base::ReadFileToString(signature_path, &signature);
+ std::vector<uint8_t> span = std::vector<uint8_t>(signature.begin(), signature.end());
- return digest;
+ const auto& enable = enableFsVerity(fd.get(), span);
+ if (!enable.ok()) {
+ return enable.error();
+ }
+ return {};
}
Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path) {
@@ -246,11 +287,8 @@
while (!ec && it != end) {
if (it->is_regular_file()) {
// Verify the file is in fs-verity
- auto result = isFileInVerity(it->path());
- if (!result.ok()) {
- return result.error();
- }
- digests[it->path()] = *result;
+ auto result = OR_RETURN(measureFsVerity(it->path()));
+ digests[it->path()] = result;
} else if (it->is_directory()) {
// These are fine to ignore
} else if (it->is_symlink()) {
@@ -267,99 +305,62 @@
return digests;
}
-Result<Signature> readSignature(const std::filesystem::path& signature_path) {
- unique_fd fd(TEMP_FAILURE_RETRY(open(signature_path.c_str(), O_RDONLY | O_CLOEXEC)));
- if (fd == -1) {
- return ErrnoError();
- }
- Signature signature;
- if (!signature.ParseFromFileDescriptor(fd.get())) {
- return Error() << "Failed to parse";
- }
- return signature;
-}
-
-Result<std::map<std::string, std::string>>
-verifyAllFilesUsingCompOs(const std::string& directory_path,
- const std::vector<uint8_t>& compos_key) {
- std::map<std::string, std::string> new_digests;
- std::vector<std::filesystem::path> signature_files;
-
+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()) {
- if (isSignatureFile(path)) {
- continue;
+ 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 signature_path = path;
- signature_path += kSignatureExtension;
- auto signature = readSignature(signature_path);
- if (!signature.ok()) {
- return Error() << "Invalid signature " << signature_path << ": "
- << signature.error();
- }
- signature_files.push_back(signature_path);
-
- // Note that these values are not yet trusted.
- auto& raw_digest = signature->digest();
- auto& raw_signature = signature->signature();
-
- // Re-construct the fsverity_formatted_digest that was signed, so we
- // can verify the signature.
- std::vector<uint8_t> buffer(sizeof(fsverity_formatted_digest) + raw_digest.size());
- auto signed_data = new (buffer.data()) fsverity_formatted_digest;
- memcpy(signed_data->magic, "FSVerity", sizeof signed_data->magic);
- signed_data->digest_algorithm = __cpu_to_le16(FS_VERITY_HASH_ALG_SHA256);
- signed_data->digest_size = __cpu_to_le16(raw_digest.size());
- memcpy(signed_data->digest, raw_digest.data(), raw_digest.size());
-
- // Make sure the signature matches the CompOs public key, and not some other
- // fs-verity trusted key.
- std::string to_verify(reinterpret_cast<char*>(buffer.data()), buffer.size());
-
- auto verified = verifyRsaPublicKeySignature(to_verify, raw_signature, compos_key);
- if (!verified.ok()) {
- return Error() << verified.error() << ": " << path;
- }
-
- std::span<const uint8_t> digest_bytes(
- reinterpret_cast<const uint8_t*>(raw_digest.data()), raw_digest.size());
- std::string compos_digest = toHex(digest_bytes);
-
- auto verity_digest = isFileInVerity(fd);
+ auto verity_digest = measureFsVerity(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) {
- return Error() << "fs-verity digest does not match signature file: " << path;
+ // 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. But we have a valid signature of some
- // digest. 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.
- std::vector<uint8_t> signature_bytes(raw_signature.begin(), raw_signature.end());
- auto pkcs7 = createPkcs7(signature_bytes, kCompOsSubject);
- if (!pkcs7.ok()) {
- return Error() << pkcs7.error() << ": " << path;
- }
-
+ // 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 enabled = enableFsVerity(fd, pkcs7.value());
- if (!enabled.ok()) {
- return Error() << enabled.error() << ": " << path;
- }
- }
- new_digests[path] = compos_digest;
+ 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()) {
@@ -372,17 +373,13 @@
return Error() << "Failed to iterate " << directory_path << ": " << ec.message();
}
- // Delete the signature files now that they have served their purpose. (ART
- // has no use for them, and their presence could cause verification to fail
- // on subsequent boots.)
- for (auto& signature_path : signature_files) {
- std::filesystem::remove(signature_path, ec);
- if (ec) {
- return Error() << "Failed to delete " << signature_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 new_digests;
+ return {};
}
Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName) {
diff --git a/ondevice-signing/CertUtils.h b/ondevice-signing/include/CertUtils.h
similarity index 100%
rename from ondevice-signing/CertUtils.h
rename to ondevice-signing/include/CertUtils.h
diff --git a/ondevice-signing/SigningKey.h b/ondevice-signing/include/SigningKey.h
similarity index 100%
rename from ondevice-signing/SigningKey.h
rename to ondevice-signing/include/SigningKey.h
diff --git a/ondevice-signing/VerityUtils.h b/ondevice-signing/include/VerityUtils.h
similarity index 60%
rename from ondevice-signing/VerityUtils.h
rename to ondevice-signing/include/VerityUtils.h
index 8d8e62c..e6e49c7 100644
--- a/ondevice-signing/VerityUtils.h
+++ b/ondevice-signing/include/VerityUtils.h
@@ -18,15 +18,29 @@
#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<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);
-android::base::Result<std::map<std::string, std::string>>
-verifyAllFilesUsingCompOs(const std::string& path, const std::vector<uint8_t>& compos_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 4a7baad..93ec3e4 100644
--- a/ondevice-signing/odsign_main.cpp
+++ b/ondevice-signing/odsign_main.cpp
@@ -33,18 +33,25 @@
#include "CertUtils.h"
#include "KeystoreKey.h"
+#include "StatsReporter.h"
#include "VerityUtils.h"
+#include "statslog_odsign.h"
#include "odsign_info.pb.h"
using android::base::ErrnoError;
using android::base::Error;
-using android::base::GetProperty;
using android::base::Result;
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 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";
@@ -52,21 +59,19 @@
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_key";
-constexpr const char* kFsVerityProcPath = "/proc/sys/fs/verity";
-constexpr const char* kKvmDevicePath = "/dev/kvm";
+constexpr const char* kCompOsVerifyPath = "/apex/com.android.compos/bin/compos_verify";
constexpr bool kForceCompilation = false;
constexpr bool kUseCompOs = true;
-const std::string kCompOsCert = "/data/misc/odsign/compos_key.cert";
-
-const std::string kCompOsCurrentPublicKey =
- "/data/misc/apexdata/com.android.compos/current/key.pubkey";
-const std::string kCompOsPendingPublicKey =
- "/data/misc/apexdata/com.android.compos/pending/key.pubkey";
-
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";
constexpr const char* kOdsignVerificationDoneProp = "odsign.verification.done";
constexpr const char* kOdsignKeyDoneProp = "odsign.key.done";
@@ -79,13 +84,9 @@
enum class CompOsInstance { kCurrent, kPending };
-static std::vector<uint8_t> readBytesFromFile(const std::string& path) {
- std::string str;
- android::base::ReadFileToString(path, &str);
- return std::vector<uint8_t>(str.begin(), str.end());
-}
+namespace {
-static bool rename(const std::string& from, const std::string& to) {
+bool rename(const std::string& from, const std::string& to) {
std::error_code ec;
std::filesystem::rename(from, to, ec);
if (ec) {
@@ -95,7 +96,7 @@
return true;
}
-static int removeDirectory(const std::string& directory) {
+int removeDirectory(const std::string& directory) {
std::error_code ec;
auto num_removed = std::filesystem::remove_all(directory, ec);
if (ec) {
@@ -109,7 +110,7 @@
}
}
-static bool directoryHasContent(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);
@@ -129,7 +130,7 @@
return static_cast<art::odrefresh::ExitCode>(exit_code);
}
-static std::string toHex(const std::vector<uint8_t>& digest) {
+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);
@@ -138,12 +139,8 @@
}
bool compOsPresent() {
- return access(kCompOsVerifyPath, X_OK) == 0 && access(kKvmDevicePath, F_OK) == 0;
-}
-
-bool isDebugBuild() {
- std::string build_type = GetProperty("ro.build.type", "");
- return build_type == "userdebug" || build_type == "eng";
+ // We must have the CompOS APEX
+ return access(kCompOsVerifyPath, X_OK) == 0;
}
Result<void> verifyExistingRootCert(const SigningKey& key) {
@@ -180,108 +177,6 @@
return createSelfSignedCertificate(*publicKey, keySignFunction, outPath);
}
-Result<std::vector<uint8_t>> extractRsaPublicKeyFromLeafCert(const SigningKey& key,
- const std::string& certPath,
- const std::string& expectedCn) {
- if (access(certPath.c_str(), F_OK) < 0) {
- return ErrnoError() << "Certificate not found: " << certPath;
- }
- auto trustedPublicKey = key.getPublicKey();
- if (!trustedPublicKey.ok()) {
- return Error() << "Failed to retrieve signing public key: " << trustedPublicKey.error();
- }
-
- auto existingCertInfo = verifyAndExtractCertInfoFromX509(certPath, trustedPublicKey.value());
- if (!existingCertInfo.ok()) {
- return Error() << "Failed to verify certificate at " << certPath << ": "
- << existingCertInfo.error();
- }
-
- auto& actualCn = existingCertInfo.value().subjectCn;
- if (actualCn != expectedCn) {
- return Error() << "CN of existing certificate at " << certPath << " is " << actualCn
- << ", should be " << expectedCn;
- }
-
- return existingCertInfo.value().subjectRsaPublicKey;
-}
-
-// Attempt to start a CompOS VM for the specified instance to get it to
-// verify ita public key & key blob.
-bool startCompOsAndVerifyKey(CompOsInstance instance) {
- bool isCurrent = instance == CompOsInstance::kCurrent;
- const std::string& keyPath = isCurrent ? kCompOsCurrentPublicKey : kCompOsPendingPublicKey;
- if (access(keyPath.c_str(), R_OK) != 0) {
- return false;
- }
-
- const char* const argv[] = {kCompOsVerifyPath, "--instance", isCurrent ? "current" : "pending"};
- int result =
- logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
- if (result == 0) {
- return true;
- }
-
- LOG(ERROR) << kCompOsVerifyPath << " returned " << result;
- return false;
-}
-
-Result<std::vector<uint8_t>> verifyCompOsKey(const SigningKey& signingKey) {
- bool verified = false;
-
- // If a pending key has been generated we don't know if it is the correct
- // one for the pending CompOS VM, so we need to start it and ask it.
- if (startCompOsAndVerifyKey(CompOsInstance::kPending)) {
- verified = true;
- }
-
- if (!verified) {
- // Alternatively if we signed a cert for the key on a previous boot, then we
- // can use that straight away.
- auto existing_key =
- extractRsaPublicKeyFromLeafCert(signingKey, kCompOsCert, kCompOsSubject.commonName);
- if (existing_key.ok()) {
- LOG(INFO) << "Found and verified existing CompOs public key certificate: "
- << kCompOsCert;
- return existing_key.value();
- }
- }
-
- // Otherwise, if there is an existing key that we haven't signed yet, then we can sign
- // it now if CompOS confirms it's OK.
- if (!verified && startCompOsAndVerifyKey(CompOsInstance::kCurrent)) {
- verified = true;
- }
-
- if (!verified) {
- return Error() << "No valid CompOs key present.";
- }
-
- // If the pending key was verified it will have been promoted to current, so
- // at this stage if there is a key it will be the current one.
- auto publicKey = readBytesFromFile(kCompOsCurrentPublicKey);
- if (publicKey.empty()) {
- // This shouldn`t really happen.
- return Error() << "Failed to read CompOs key.";
- }
-
- // One way or another we now have a valid public key. Persist a certificate so
- // we can simplify the checks on subsequent boots.
-
- auto signFunction = [&](const std::string& to_be_signed) {
- return signingKey.sign(to_be_signed);
- };
- auto certStatus = createLeafCertificate(kCompOsSubject, publicKey, signFunction,
- kSigningKeyCert, kCompOsCert);
- if (!certStatus.ok()) {
- return Error() << "Failed to create CompOs cert: " << certStatus.error();
- }
-
- LOG(INFO) << "Verified key, wrote new CompOs cert";
-
- return publicKey;
-}
-
Result<std::map<std::string, std::string>> computeDigests(const std::string& path) {
std::error_code ec;
std::map<std::string, std::string> digests;
@@ -312,7 +207,7 @@
for (const auto& path_digest : digests) {
auto path = path_digest.first;
auto digest = path_digest.second;
- if ((trusted_digests.count(path) == 0)) {
+ if (trusted_digests.count(path) == 0) {
return Error() << "Couldn't find digest for " << path;
}
if (trusted_digests.at(path) != digest) {
@@ -347,7 +242,7 @@
return verifyDigests(*result, trusted_digests);
}
-Result<OdsignInfo> getOdsignInfo(const SigningKey& key) {
+Result<OdsignInfo> getAndVerifyOdsignInfo(const SigningKey& key) {
std::string persistedSignature;
OdsignInfo odsignInfo;
@@ -381,6 +276,28 @@
return odsignInfo;
}
+std::map<std::string, std::string> getTrustedDigests(const SigningKey& key) {
+ std::map<std::string, std::string> trusted_digests;
+
+ if (access(kOdsignInfo.c_str(), F_OK) != 0) {
+ // no odsign info file, which is not necessarily an error - just return
+ // an empty list of digests.
+ LOG(INFO) << kOdsignInfo << " not found.";
+ return trusted_digests;
+ }
+ auto signInfo = getAndVerifyOdsignInfo(key);
+
+ if (signInfo.ok()) {
+ trusted_digests.insert(signInfo->file_hashes().begin(), signInfo->file_hashes().end());
+ } else {
+ // This is not expected, since the file did exist. Log an error and
+ // return an empty list of digests.
+ LOG(ERROR) << "Couldn't load trusted digests: " << signInfo.error();
+ }
+
+ return trusted_digests;
+}
+
Result<void> persistDigests(const std::map<std::string, std::string>& digests,
const SigningKey& key) {
OdsignInfo signInfo;
@@ -406,23 +323,8 @@
return {};
}
-static Result<void> verifyArtifacts(const SigningKey& key, bool supportsFsVerity) {
- auto signInfo = getOdsignInfo(key);
- // Tell init we're done with the key; this is a boot time optimization
- // in particular for the no fs-verity case, where we need to do a
- // costly verification. If the files haven't been tampered with, which
- // should be the common path, the verification will succeed, and we won't
- // need the key anymore. If it turns out the artifacts are invalid (eg not
- // in fs-verity) or the hash doesn't match, we won't be able to generate
- // new artifacts without the key, so in those cases, remove the artifacts,
- // and use JIT zygote for the current boot. We should recover automatically
- // by the next boot.
- SetProperty(kOdsignKeyDoneProp, "1");
- if (!signInfo.ok()) {
- return signInfo.error();
- }
- std::map<std::string, std::string> trusted_digests(signInfo->file_hashes().begin(),
- signInfo->file_hashes().end());
+Result<void> verifyArtifactsIntegrity(const std::map<std::string, std::string>& trusted_digests,
+ bool supportsFsVerity) {
Result<void> integrityStatus;
if (supportsFsVerity) {
@@ -437,43 +339,72 @@
return {};
}
-Result<std::vector<uint8_t>> addCompOsCertToFsVerityKeyring(const SigningKey& signingKey) {
- auto publicKey = verifyCompOsKey(signingKey);
- if (!publicKey.ok()) {
- return publicKey.error();
+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;
}
- auto cert_add_result = addCertToFsVerityKeyring(kCompOsCert, "fsv_compos");
- if (!cert_add_result.ok()) {
- // Best efforts only - nothing we can do if deletion fails.
- unlink(kCompOsCert.c_str());
- return Error() << "Failed to add CompOs certificate to fs-verity keyring: "
- << cert_add_result.error();
+ std::string compos_info_str;
+ if (!android::base::ReadFileToString(kCompOsInfo, &compos_info_str)) {
+ return ErrnoError() << "Failed to read " << kCompOsInfo;
}
- return publicKey;
+ // 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 std::vector<uint8_t>& compos_key,
- const SigningKey& signingKey,
- bool* digests_verified) {
+art::odrefresh::ExitCode CheckCompOsPendingArtifacts(const SigningKey& signing_key,
+ bool* digests_verified,
+ StatsReporter* stats_reporter) {
+ StatsReporter::CompOsArtifactsCheckRecord* compos_check_record =
+ stats_reporter->GetOrCreateComposArtifactsCheckRecord();
+
if (!directoryHasContent(kCompOsPendingArtifactsDir)) {
- return art::odrefresh::ExitCode::kCompilationRequired;
+ // 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 has generated some artifacts that may, or may not, match the
+ 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.
+ // 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
+ // 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)) {
@@ -481,45 +412,66 @@
return art::odrefresh::ExitCode::kCompilationRequired;
}
- // TODO: Make sure that we check here that the contents of the artifacts
- // correspond to their filenames (and extensions) - the CompOs signatures
- // can't guarantee that.
- odrefresh_status = checkArtifacts();
- if (odrefresh_status != art::odrefresh::ExitCode::kOkay) {
- LOG(WARNING) << "Pending artifacts are not OK";
- return odrefresh_status;
- }
-
- // The artifacts appear to be up to date - but we haven't
- // verified that they are genuine yet.
- Result<std::map<std::string, std::string>> digests =
- verifyAllFilesUsingCompOs(kArtArtifactsDir, compos_key);
-
- if (digests.ok()) {
- auto persisted = persistDigests(digests.value(), signingKey);
-
- // Having signed the digests (or failed to), we're done with the signing key.
- SetProperty(kOdsignKeyDoneProp, "1");
-
- if (persisted.ok()) {
- *digests_verified = true;
- LOG(INFO) << "Pending artifacts successfully verified.";
- return art::odrefresh::ExitCode::kOkay;
- } else {
- LOG(WARNING) << persisted.error();
- }
+ // 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 {
- LOG(WARNING) << "Pending artifact verification failed: " << digests.error();
+ 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.
- removeDirectory(kArtArtifactsDir);
+ 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 */) {
- auto errorScopeGuard = []() {
+int main(int /* argc */, char** argv) {
+ android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
+
+ auto scope_guard = android::base::make_scope_guard([]() {
// In case we hit any error, remove the artifacts and tell Zygote not to use
// anything
removeDirectory(kArtArtifactsDir);
@@ -531,27 +483,34 @@
SetProperty(kOdsignVerificationDoneProp, "1");
// Tell init it shouldn't try to restart us - see odsign.rc
SetProperty(kStopServiceProp, "odsign");
- };
- auto scope_guard = android::base::make_scope_guard(errorScopeGuard);
+ });
+
+ // `stats_reporter` must come after `scope_guard` so that its destructor is called before
+ // `scope_guard`.
+ auto stats_reporter = std::make_unique<StatsReporter>();
+ StatsReporter::OdsignRecord* odsign_record = stats_reporter->GetOdsignRecord();
if (!android::base::GetBoolProperty("ro.apex.updatable", false)) {
LOG(INFO) << "Device doesn't support updatable APEX, exiting.";
+ stats_reporter->SetOdsignRecordEnabled(false);
return 0;
}
-
- auto keystoreResult = KeystoreKey::getInstance();
+ auto keystoreResult =
+ KeystoreKey::getInstance(kPublicKeySignature, kKeyAlias, kKeyNspace, kKeyBootLevel);
if (!keystoreResult.ok()) {
LOG(ERROR) << "Could not create keystore key: " << keystoreResult.error();
+ odsign_record->status =
+ art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_KEYSTORE_FAILED;
return -1;
}
SigningKey* key = keystoreResult.value();
- bool supportsFsVerity = access(kFsVerityProcPath, F_OK) == 0;
+ bool supportsFsVerity = SupportsFsVerity();
if (!supportsFsVerity) {
LOG(INFO) << "Device doesn't support fsverity. Falling back to full verification.";
}
- bool useCompOs = kUseCompOs && supportsFsVerity && compOsPresent() && isDebugBuild();
+ bool useCompOs = kUseCompOs && supportsFsVerity && compOsPresent();
if (supportsFsVerity) {
auto existing_cert = verifyExistingRootCert(*key);
@@ -563,6 +522,8 @@
if (!new_cert.ok()) {
LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error();
// TODO apparently the key become invalid - delete the blob / cert
+ odsign_record->status =
+ art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_CERT_FAILED;
return -1;
}
} else {
@@ -572,48 +533,79 @@
if (!cert_add_result.ok()) {
LOG(ERROR) << "Failed to add certificate to fs-verity keyring: "
<< cert_add_result.error();
+ odsign_record->status =
+ art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_CERT_FAILED;
return -1;
}
}
- art::odrefresh::ExitCode odrefresh_status = art::odrefresh::ExitCode::kCompilationRequired;
bool digests_verified = false;
+ art::odrefresh::ExitCode odrefresh_status =
+ useCompOs ? CheckCompOsPendingArtifacts(*key, &digests_verified, stats_reporter.get())
+ : checkArtifacts();
- if (useCompOs) {
- auto compos_key = addCompOsCertToFsVerityKeyring(*key);
- if (!compos_key.ok()) {
- LOG(WARNING) << compos_key.error();
- } else {
- odrefresh_status =
- checkCompOsPendingArtifacts(compos_key.value(), *key, &digests_verified);
+ // The artifacts dir doesn't necessarily need to exist; if the existing
+ // artifacts on the system partition are valid, those can be used.
+ int err = access(kArtArtifactsDir.c_str(), F_OK);
+ // 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 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
+ // case, odrefresh may use partial compilation, and leave some
+ // artifacts unchanged.
+ auto trusted_digests = getTrustedDigests(*key);
+
+ if (odrefresh_status == art::odrefresh::ExitCode::kOkay) {
+ // Tell init we're done with the key; this is a boot time optimization
+ // in particular for the no fs-verity case, where we need to do a
+ // costly verification. If the files haven't been tampered with, which
+ // should be the common path, the verification will succeed, and we won't
+ // need the key anymore. If it turns out the artifacts are invalid (eg not
+ // in fs-verity) or the hash doesn't match, we won't be able to generate
+ // new artifacts without the key, so in those cases, remove the artifacts,
+ // and use JIT zygote for the current boot. We should recover automatically
+ // by the next boot.
+ SetProperty(kOdsignKeyDoneProp, "1");
+ }
+
+ auto verificationResult = verifyArtifactsIntegrity(trusted_digests, supportsFsVerity);
+ if (!verificationResult.ok()) {
+ int num_removed = removeDirectory(kArtArtifactsDir);
+ if (num_removed == 0) {
+ // If we can't remove the bad artifacts, we shouldn't continue, and
+ // instead prevent Zygote from using them (which is taken care of
+ // in the exit handler).
+ LOG(ERROR) << "Failed to remove unknown artifacts.";
+ odsign_record->status =
+ art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_CLEANUP_FAILED;
+ return -1;
+ }
}
}
+ // Now that we verified existing artifacts, compile if we need to.
if (odrefresh_status == art::odrefresh::ExitCode::kCompilationRequired) {
odrefresh_status = compileArtifacts(kForceCompilation);
}
+
if (odrefresh_status == art::odrefresh::ExitCode::kOkay) {
+ // No new artifacts generated, and we verified existing ones above, nothing left to do.
LOG(INFO) << "odrefresh said artifacts are VALID";
- if (!digests_verified) {
- // A post-condition of validating artifacts is that if the ones on /system
- // are used, kArtArtifactsDir is removed. Conversely, if kArtArtifactsDir
- // exists, those are artifacts that will be used, and we should verify them.
- int err = access(kArtArtifactsDir.c_str(), F_OK);
- // If we receive any error other than ENOENT, be suspicious
- bool artifactsPresent = (err == 0) || (err < 0 && errno != ENOENT);
- if (artifactsPresent) {
- auto verificationResult = verifyArtifacts(*key, supportsFsVerity);
- if (!verificationResult.ok()) {
- LOG(ERROR) << verificationResult.error();
- return -1;
- }
- }
- }
+ stats_reporter->SetOdsignRecordEnabled(false);
} else if (odrefresh_status == art::odrefresh::ExitCode::kCompilationSuccess ||
odrefresh_status == art::odrefresh::ExitCode::kCompilationFailed) {
const bool compiled_all = odrefresh_status == art::odrefresh::ExitCode::kCompilationSuccess;
LOG(INFO) << "odrefresh compiled " << (compiled_all ? "all" : "partial")
<< " artifacts, returned " << odrefresh_status;
+ // This value may be overwritten later.
+ odsign_record->status =
+ compiled_all ? art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_ALL_OK
+ : art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_PARTIAL_OK;
Result<std::map<std::string, std::string>> digests;
if (supportsFsVerity) {
digests = addFilesToVerityRecursive(kArtArtifactsDir, *key);
@@ -624,24 +616,39 @@
}
if (!digests.ok()) {
LOG(ERROR) << digests.error();
+ odsign_record->status =
+ art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_SIGNING_FAILED;
return -1;
}
auto persistStatus = persistDigests(*digests, *key);
if (!persistStatus.ok()) {
LOG(ERROR) << persistStatus.error();
+ odsign_record->status =
+ art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_SIGNING_FAILED;
return -1;
}
} else if (odrefresh_status == art::odrefresh::ExitCode::kCleanupFailed) {
LOG(ERROR) << "odrefresh failed cleaning up existing artifacts";
+ odsign_record->status =
+ art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_ODREFRESH_FAILED;
return -1;
} else {
LOG(ERROR) << "odrefresh exited unexpectedly, returned " << odrefresh_status;
+ odsign_record->status =
+ art::metrics::statsd::ODSIGN_REPORTED__STATUS__STATUS_ODREFRESH_FAILED;
return -1;
}
LOG(INFO) << "On-device signing done.";
scope_guard.Disable();
+
+ // 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();
+
// At this point, we're done with the key for sure
SetProperty(kOdsignKeyDoneProp, "1");
// And we did a successful verification
diff --git a/ondevice-signing/proto/Android.bp b/ondevice-signing/proto/Android.bp
index c042b8e..356e661 100644
--- a/ondevice-signing/proto/Android.bp
+++ b/ondevice-signing/proto/Android.bp
@@ -26,18 +26,19 @@
type: "lite",
},
srcs: ["odsign_info.proto"],
-}
-
-cc_library_static {
- name: "lib_compos_proto",
- host_supported: true,
- proto: {
- export_proto_headers: true,
- type: "lite",
- },
- srcs: ["compos_signature.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
new file mode 100644
index 0000000..4027220
--- /dev/null
+++ b/ondevice-signing/tests/Android.bp
@@ -0,0 +1,44 @@
+// 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
new file mode 100644
index 0000000..0703d59
--- /dev/null
+++ b/ondevice-signing/tests/SigningUtils.cert.der
Binary files differ
diff --git a/ondevice-signing/tests/SigningUtils.pem b/ondevice-signing/tests/SigningUtils.pem
new file mode 100644
index 0000000..01dfa5e
--- /dev/null
+++ b/ondevice-signing/tests/SigningUtils.pem
@@ -0,0 +1,51 @@
+-----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
new file mode 100644
index 0000000..10f7629
--- /dev/null
+++ b/ondevice-signing/tests/SigningUtilsTest.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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
new file mode 100644
index 0000000..8a121be
--- /dev/null
+++ b/ondevice-signing/tests/test_file
Binary files differ
diff --git a/ondevice-signing/tests/test_file.sig b/ondevice-signing/tests/test_file.sig
new file mode 100644
index 0000000..ffd95dc
--- /dev/null
+++ b/ondevice-signing/tests/test_file.sig
Binary files differ
diff --git a/prng_seeder/Android.bp b/prng_seeder/Android.bp
new file mode 100644
index 0000000..5759731
--- /dev/null
+++ b/prng_seeder/Android.bp
@@ -0,0 +1,50 @@
+// 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.
+
+package {
+ // See: http://go/android-license-faq
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_bindgen {
+ name: "libcutils_socket_bindgen",
+ crate_name: "cutils_socket_bindgen",
+ wrapper_src: "cutils_wrapper.h",
+ source_stem: "bindings",
+ bindgen_flags: [
+ "--allowlist-function=android_get_control_socket",
+ ],
+ shared_libs: [
+ "libcutils",
+ ],
+}
+
+rust_binary {
+ name: "prng_seeder",
+ edition: "2021",
+ srcs: ["src/main.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libbssl_ffi",
+ "libclap",
+ "libcutils_socket_bindgen",
+ "liblogger",
+ "liblog_rust",
+ "libnix",
+ "libtokio",
+ ],
+
+ init_rc: ["prng_seeder.rc"],
+}
diff --git a/prng_seeder/OWNERS b/prng_seeder/OWNERS
new file mode 100644
index 0000000..9202b90
--- /dev/null
+++ b/prng_seeder/OWNERS
@@ -0,0 +1,2 @@
+paulcrowley@google.com
+prb@google.com
\ No newline at end of file
diff --git a/prng_seeder/cutils_wrapper.h b/prng_seeder/cutils_wrapper.h
new file mode 100644
index 0000000..9c1fe56
--- /dev/null
+++ b/prng_seeder/cutils_wrapper.h
@@ -0,0 +1,15 @@
+// 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 <cutils/sockets.h>
diff --git a/prng_seeder/prng_seeder.rc b/prng_seeder/prng_seeder.rc
new file mode 100644
index 0000000..9825583
--- /dev/null
+++ b/prng_seeder/prng_seeder.rc
@@ -0,0 +1,12 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Start PRNG seeder daemon from early-init
+
+on early-init
+ start prng_seeder
+
+service prng_seeder /system/bin/prng_seeder
+ user prng_seeder
+ group prng_seeder
+ stdio_to_kmsg
+ socket prng_seeder stream+listen 0666 prng_seeder prng_seeder
diff --git a/prng_seeder/src/conditioner.rs b/prng_seeder/src/conditioner.rs
new file mode 100644
index 0000000..eca8af8
--- /dev/null
+++ b/prng_seeder/src/conditioner.rs
@@ -0,0 +1,76 @@
+// 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.
+
+use std::{fs::File, io::Read, os::unix::io::AsRawFd};
+
+use anyhow::{ensure, Context, Result};
+use log::debug;
+use nix::fcntl::{fcntl, FcntlArg::F_SETFL, OFlag};
+use tokio::io::AsyncReadExt;
+
+use crate::drbg;
+
+const SEED_FOR_CLIENT_LEN: usize = 496;
+const NUM_REQUESTS_PER_RESEED: u32 = 256;
+
+pub struct ConditionerBuilder {
+ hwrng: File,
+ rg: drbg::Drbg,
+}
+
+impl ConditionerBuilder {
+ pub fn new(mut hwrng: File) -> Result<ConditionerBuilder> {
+ let mut et: drbg::Entropy = [0; drbg::ENTROPY_LEN];
+ hwrng.read_exact(&mut et).context("hwrng.read_exact in new")?;
+ let rg = drbg::Drbg::new(&et)?;
+ fcntl(hwrng.as_raw_fd(), F_SETFL(OFlag::O_NONBLOCK))
+ .context("setting O_NONBLOCK on hwrng")?;
+ Ok(ConditionerBuilder { hwrng, rg })
+ }
+
+ pub fn build(self) -> Conditioner {
+ Conditioner {
+ hwrng: tokio::fs::File::from_std(self.hwrng),
+ rg: self.rg,
+ requests_since_reseed: 0,
+ }
+ }
+}
+
+pub struct Conditioner {
+ hwrng: tokio::fs::File,
+ rg: drbg::Drbg,
+ requests_since_reseed: u32,
+}
+
+impl Conditioner {
+ pub async fn reseed_if_necessary(&mut self) -> Result<()> {
+ if self.requests_since_reseed >= NUM_REQUESTS_PER_RESEED {
+ debug!("Reseeding DRBG");
+ let mut et: drbg::Entropy = [0; drbg::ENTROPY_LEN];
+ self.hwrng.read_exact(&mut et).await.context("hwrng.read_exact in reseed")?;
+ self.rg.reseed(&et)?;
+ self.requests_since_reseed = 0;
+ }
+ Ok(())
+ }
+
+ pub fn request(&mut self) -> Result<[u8; SEED_FOR_CLIENT_LEN]> {
+ ensure!(self.requests_since_reseed < NUM_REQUESTS_PER_RESEED, "Not enough reseeds");
+ let mut seed_for_client = [0u8; SEED_FOR_CLIENT_LEN];
+ self.rg.generate(&mut seed_for_client)?;
+ self.requests_since_reseed += 1;
+ Ok(seed_for_client)
+ }
+}
diff --git a/prng_seeder/src/cutils_socket.rs b/prng_seeder/src/cutils_socket.rs
new file mode 100644
index 0000000..ab2c869
--- /dev/null
+++ b/prng_seeder/src/cutils_socket.rs
@@ -0,0 +1,25 @@
+// 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.
+
+use std::ffi::CString;
+use std::os::unix::{net::UnixListener, prelude::FromRawFd};
+
+use anyhow::{ensure, Result};
+
+pub fn android_get_control_socket(name: &str) -> Result<UnixListener> {
+ let name = CString::new(name)?;
+ let fd = unsafe { cutils_socket_bindgen::android_get_control_socket(name.as_ptr()) };
+ ensure!(fd >= 0, "android_get_control_socket failed");
+ Ok(unsafe { UnixListener::from_raw_fd(fd) })
+}
diff --git a/prng_seeder/src/drbg.rs b/prng_seeder/src/drbg.rs
new file mode 100644
index 0000000..89c5a88
--- /dev/null
+++ b/prng_seeder/src/drbg.rs
@@ -0,0 +1,65 @@
+// 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.
+
+use anyhow::{ensure, Result};
+use bssl_ffi as bssl_sys;
+
+pub const ENTROPY_LEN: usize = bssl_sys::CTR_DRBG_ENTROPY_LEN as usize;
+
+pub type Entropy = [u8; ENTROPY_LEN];
+
+pub struct Drbg(*mut bssl_sys::CTR_DRBG_STATE);
+
+impl Drbg {
+ pub fn new(entropy: &Entropy) -> Result<Drbg> {
+ let p = unsafe { bssl_sys::CTR_DRBG_new(entropy.as_ptr(), std::ptr::null(), 0) };
+ ensure!(!p.is_null(), "CTR_DRBG_new failed");
+ Ok(Drbg(p))
+ }
+
+ pub fn reseed(&mut self, entropy: &Entropy) -> Result<()> {
+ ensure!(
+ unsafe { bssl_sys::CTR_DRBG_reseed(self.0, entropy.as_ptr(), std::ptr::null(), 0) }
+ == 1,
+ "CTR_DRBG_reseed failed"
+ );
+ Ok(())
+ }
+
+ pub fn generate(&mut self, buf: &mut [u8]) -> Result<()> {
+ ensure!(
+ unsafe {
+ bssl_sys::CTR_DRBG_generate(
+ self.0,
+ buf.as_mut_ptr(),
+ buf.len(),
+ std::ptr::null(),
+ 0,
+ )
+ } == 1,
+ "CTR_DRBG_generate failed"
+ );
+ Ok(())
+ }
+}
+
+impl Drop for Drbg {
+ fn drop(&mut self) {
+ unsafe {
+ bssl_sys::CTR_DRBG_free(self.0);
+ }
+ }
+}
+
+unsafe impl Send for Drbg {}
diff --git a/prng_seeder/src/main.rs b/prng_seeder/src/main.rs
new file mode 100644
index 0000000..3f698f6
--- /dev/null
+++ b/prng_seeder/src/main.rs
@@ -0,0 +1,137 @@
+// 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.
+
+//! FIPS compliant random number conditioner. Reads from /dev/hw_random
+//! and applies the NIST SP 800-90A CTR DRBG strategy to provide
+//! pseudorandom bytes to clients which connect to a socket provided
+//! by init.
+
+mod conditioner;
+mod cutils_socket;
+mod drbg;
+
+use std::{
+ convert::Infallible,
+ fs::remove_file,
+ io::ErrorKind,
+ os::unix::net::UnixListener,
+ path::{Path, PathBuf},
+};
+
+use anyhow::{ensure, Context, Result};
+use clap::Parser;
+use log::{error, info, Level};
+use nix::sys::signal;
+use tokio::{io::AsyncWriteExt, net::UnixListener as TokioUnixListener};
+
+use crate::conditioner::ConditionerBuilder;
+
+#[derive(Debug, clap::Parser)]
+struct Cli {
+ #[clap(long, default_value = "/dev/hw_random")]
+ source: PathBuf,
+ #[clap(long)]
+ socket: Option<PathBuf>,
+}
+
+fn configure_logging() -> Result<()> {
+ ensure!(
+ logger::init(
+ logger::Config::default().with_tag_on_device("prng_seeder").with_min_level(Level::Info)
+ ),
+ "log configuration failed"
+ );
+ Ok(())
+}
+
+fn get_socket(path: &Path) -> Result<UnixListener> {
+ if let Err(e) = remove_file(path) {
+ if e.kind() != ErrorKind::NotFound {
+ return Err(e).context(format!("Removing old socket: {}", path.display()));
+ }
+ } else {
+ info!("Deleted old {}", path.display());
+ }
+ UnixListener::bind(path)
+ .with_context(|| format!("In get_socket: binding socket to {}", path.display()))
+}
+
+fn setup() -> Result<(ConditionerBuilder, UnixListener)> {
+ configure_logging()?;
+ let cli = Cli::try_parse()?;
+ unsafe { signal::signal(signal::Signal::SIGPIPE, signal::SigHandler::SigIgn) }
+ .context("In setup, setting SIGPIPE to SIG_IGN")?;
+
+ let listener = match cli.socket {
+ Some(path) => get_socket(path.as_path())?,
+ None => cutils_socket::android_get_control_socket("prng_seeder")
+ .context("In setup, calling android_get_control_socket")?,
+ };
+ let hwrng = std::fs::File::open(&cli.source)
+ .with_context(|| format!("Unable to open hwrng {}", cli.source.display()))?;
+ let cb = ConditionerBuilder::new(hwrng)?;
+ Ok((cb, listener))
+}
+
+async fn listen_loop(cb: ConditionerBuilder, listener: UnixListener) -> Result<Infallible> {
+ let mut conditioner = cb.build();
+ listener.set_nonblocking(true).context("In listen_loop, on set_nonblocking")?;
+ let listener = TokioUnixListener::from_std(listener).context("In listen_loop, on from_std")?;
+ info!("Starting listen loop");
+ loop {
+ match listener.accept().await {
+ Ok((mut stream, _)) => {
+ let new_bytes = conditioner.request()?;
+ tokio::spawn(async move {
+ if let Err(e) = stream.write_all(&new_bytes).await {
+ error!("Request failed: {}", e);
+ }
+ });
+ conditioner.reseed_if_necessary().await?;
+ }
+ Err(e) if e.kind() == ErrorKind::Interrupted => {}
+ Err(e) => return Err(e).context("accept on socket failed"),
+ }
+ }
+}
+
+fn run() -> Result<Infallible> {
+ let (cb, listener) = match setup() {
+ Ok(t) => t,
+ Err(e) => {
+ // If setup fails, just hang forever. That way init doesn't respawn us.
+ error!("Hanging forever because setup failed: {:?}", e);
+ // Logs are sometimes mysteriously not being logged, so print too
+ println!("prng_seeder: Hanging forever because setup failed: {:?}", e);
+ loop {
+ std::thread::park();
+ error!("std::thread::park() finished unexpectedly, re-parking thread");
+ }
+ }
+ };
+
+ tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .context("In run, building reactor")?
+ .block_on(async { listen_loop(cb, listener).await })
+}
+
+fn main() {
+ let e = run();
+ error!("Launch terminated: {:?}", e);
+ // Logs are sometimes mysteriously not being logged, so print too
+ println!("prng_seeder: launch terminated: {:?}", e);
+ std::process::exit(-1);
+}
diff --git a/provisioner/Android.bp b/provisioner/Android.bp
index aac4878..b548973 100644
--- a/provisioner/Android.bp
+++ b/provisioner/Android.bp
@@ -43,23 +43,58 @@
},
}
-cc_binary {
- name: "rkp_factory_extraction_tool",
- vendor: true,
- srcs: ["rkp_factory_extraction_tool.cpp"],
+cc_defaults {
+ name: "rkp_factory_extraction_defaults",
+ defaults: [
+ "keymint_use_latest_hal_aidl_ndk_static",
+ ],
shared_libs: [
- "android.hardware.security.keymint-V1-ndk",
"libbinder",
"libbinder_ndk",
"libcrypto",
"liblog",
],
static_libs: [
+ "android.hardware.security.rkp-V3-ndk",
"libbase",
"libcppbor_external",
"libcppcose_rkp",
- "libgflags",
"libjsoncpp",
"libkeymint_remote_prov_support",
],
}
+
+cc_library_static {
+ name: "librkp_factory_extraction",
+ defaults: [
+ "rkp_factory_extraction_defaults",
+ ],
+ srcs: ["rkp_factory_extraction_lib.cpp"],
+ vendor_available: true,
+}
+
+cc_test {
+ name: "librkp_factory_extraction_test",
+ defaults: [
+ "rkp_factory_extraction_defaults",
+ ],
+ srcs: ["rkp_factory_extraction_lib_test.cpp"],
+ test_suites: ["device-tests"],
+ static_libs: [
+ "libgmock",
+ "librkp_factory_extraction",
+ ],
+}
+
+cc_binary {
+ name: "rkp_factory_extraction_tool",
+ vendor: true,
+ srcs: ["rkp_factory_extraction_tool.cpp"],
+ defaults: [
+ "rkp_factory_extraction_defaults",
+ ],
+ static_libs: [
+ "libgflags",
+ "librkp_factory_extraction",
+ ],
+}
diff --git a/provisioner/TEST_MAPPING b/provisioner/TEST_MAPPING
new file mode 100644
index 0000000..de3f165
--- /dev/null
+++ b/provisioner/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "librkp_factory_extraction_test"
+ }
+ ]
+}
diff --git a/provisioner/rkp_factory_extraction_lib.cpp b/provisioner/rkp_factory_extraction_lib.cpp
new file mode 100644
index 0000000..d85e85f
--- /dev/null
+++ b/provisioner/rkp_factory_extraction_lib.cpp
@@ -0,0 +1,280 @@
+/*
+ * 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.
+ */
+
+#include "rkp_factory_extraction_lib.h"
+
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+#include <android/binder_manager.h>
+#include <cppbor.h>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <keymaster/cppcose/cppcose.h>
+#include <openssl/base64.h>
+#include <remote_prov/remote_prov_utils.h>
+#include <sys/random.h>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "cppbor_parse.h"
+
+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::EekChain;
+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;
+using aidl::android::hardware::security::keymint::remote_prov::parseAndValidateFactoryDeviceInfo;
+using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryCsr;
+using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryProtectedData;
+
+using namespace cppbor;
+using namespace cppcose;
+
+constexpr size_t kVersionWithoutSuperencryption = 3;
+
+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);
+ }
+
+ // BoringSSL automatically adds a NUL -- remove it from the string data
+ base64.pop_back();
+
+ return base64;
+}
+
+std::vector<uint8_t> generateChallenge() {
+ std::vector<uint8_t> challenge(kChallengeSize);
+
+ ssize_t bytesRemaining = static_cast<ssize_t>(challenge.size());
+ 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);
+ }
+ }
+ bytesRemaining -= bytesRead;
+ writePtr += bytesRead;
+ }
+
+ return challenge;
+}
+
+CborResult<Array> composeCertificateRequestV1(const ProtectedData& protectedData,
+ const DeviceInfo& verifiedDeviceInfo,
+ const std::vector<uint8_t>& challenge,
+ const std::vector<uint8_t>& keysToSignMac,
+ IRemotelyProvisionedComponent* provisionable) {
+ Array macedKeysToSign = Array()
+ .add(Map().add(1, 5).encode()) // alg: hmac-sha256
+ .add(Map()) // empty unprotected headers
+ .add(Null()) // nil for the payload
+ .add(keysToSignMac); // MAC as returned from the HAL
+
+ ErrMsgOr<std::unique_ptr<Map>> parsedVerifiedDeviceInfo =
+ parseAndValidateFactoryDeviceInfo(verifiedDeviceInfo.deviceInfo, provisionable);
+ if (!parsedVerifiedDeviceInfo) {
+ return {nullptr, parsedVerifiedDeviceInfo.moveMessage()};
+ }
+
+ auto [parsedProtectedData, ignore2, errMsg] = parse(protectedData.protectedData);
+ if (!parsedProtectedData) {
+ std::cerr << "Error parsing protected data: '" << errMsg << "'" << std::endl;
+ return {nullptr, errMsg};
+ }
+
+ Array deviceInfo = Array().add(parsedVerifiedDeviceInfo.moveValue()).add(Map());
+
+ auto certificateRequest = std::make_unique<Array>();
+ (*certificateRequest)
+ .add(std::move(deviceInfo))
+ .add(challenge)
+ .add(std::move(parsedProtectedData))
+ .add(std::move(macedKeysToSign));
+ return {std::move(certificateRequest), ""};
+}
+
+CborResult<Array> getCsrV1(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+ std::vector<uint8_t> keysToSignMac;
+ std::vector<MacedPublicKey> emptyKeys;
+ DeviceInfo verifiedDeviceInfo;
+ ProtectedData protectedData;
+ RpcHardwareInfo hwInfo;
+ ::ndk::ScopedAStatus status = irpc->getHardwareInfo(&hwInfo);
+ if (!status.isOk()) {
+ std::cerr << "Failed to get hardware info for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+
+ const std::vector<uint8_t> eek = getProdEekChain(hwInfo.supportedEekCurve);
+ const std::vector<uint8_t> challenge = generateChallenge();
+ status = irpc->generateCertificateRequest(
+ /*test_mode=*/false, emptyKeys, eek, challenge, &verifiedDeviceInfo, &protectedData,
+ &keysToSignMac);
+ if (!status.isOk()) {
+ std::cerr << "Bundle extraction failed for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+ return composeCertificateRequestV1(protectedData, verifiedDeviceInfo, challenge, keysToSignMac,
+ irpc);
+}
+
+void selfTestGetCsrV1(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+ std::vector<uint8_t> keysToSignMac;
+ std::vector<MacedPublicKey> emptyKeys;
+ DeviceInfo verifiedDeviceInfo;
+ ProtectedData protectedData;
+ RpcHardwareInfo hwInfo;
+ ::ndk::ScopedAStatus status = irpc->getHardwareInfo(&hwInfo);
+ if (!status.isOk()) {
+ std::cerr << "Failed to get hardware info for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+
+ const std::vector<uint8_t> eekId = {0, 1, 2, 3, 4, 5, 6, 7};
+ ErrMsgOr<EekChain> eekChain = generateEekChain(hwInfo.supportedEekCurve, /*length=*/3, eekId);
+ if (!eekChain) {
+ std::cerr << "Error generating test EEK certificate chain: " << eekChain.message();
+ exit(-1);
+ }
+ const std::vector<uint8_t> challenge = generateChallenge();
+ status = irpc->generateCertificateRequest(
+ /*test_mode=*/true, emptyKeys, eekChain->chain, challenge, &verifiedDeviceInfo,
+ &protectedData, &keysToSignMac);
+ if (!status.isOk()) {
+ std::cerr << "Error generating test cert chain for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+
+ auto result = verifyFactoryProtectedData(verifiedDeviceInfo, /*keysToSign=*/{}, keysToSignMac,
+ protectedData, *eekChain, eekId,
+ hwInfo.supportedEekCurve, irpc, challenge);
+
+ std::cout << "Self test successful." << std::endl;
+}
+
+CborResult<Array> composeCertificateRequestV3(const std::vector<uint8_t>& csr) {
+ auto [parsedCsr, _, csrErrMsg] = cppbor::parse(csr);
+ if (!parsedCsr) {
+ return {nullptr, csrErrMsg};
+ }
+ if (!parsedCsr->asArray()) {
+ return {nullptr, "CSR is not a CBOR array."};
+ }
+
+ return {std::unique_ptr<Array>(parsedCsr.release()->asArray()), ""};
+}
+
+CborResult<cppbor::Array> getCsrV3(std::string_view componentName,
+ IRemotelyProvisionedComponent* irpc) {
+ std::vector<uint8_t> csr;
+ std::vector<MacedPublicKey> emptyKeys;
+ const std::vector<uint8_t> challenge = generateChallenge();
+
+ auto status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr);
+ if (!status.isOk()) {
+ std::cerr << "Bundle extraction failed for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+
+ return composeCertificateRequestV3(csr);
+}
+
+void selfTestGetCsrV3(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+ std::vector<uint8_t> csr;
+ std::vector<MacedPublicKey> emptyKeys;
+ const std::vector<uint8_t> challenge = generateChallenge();
+
+ auto status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr);
+ if (!status.isOk()) {
+ std::cerr << "Bundle extraction failed for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+
+ auto result = verifyFactoryCsr(/*keysToSign=*/cppbor::Array(), csr, irpc, challenge);
+ if (!result) {
+ std::cerr << "Self test failed for '" << componentName
+ << "'. Error message: " << result.message() << "." << std::endl;
+ exit(-1);
+ }
+
+ std::cout << "Self test successful." << std::endl;
+}
+
+CborResult<Array> getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+ RpcHardwareInfo hwInfo;
+ auto status = irpc->getHardwareInfo(&hwInfo);
+ if (!status.isOk()) {
+ std::cerr << "Failed to get hardware info for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+
+ if (hwInfo.versionNumber < kVersionWithoutSuperencryption) {
+ return getCsrV1(componentName, irpc);
+ } else {
+ return getCsrV3(componentName, irpc);
+ }
+}
+
+void selfTestGetCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+ RpcHardwareInfo hwInfo;
+ auto status = irpc->getHardwareInfo(&hwInfo);
+ if (!status.isOk()) {
+ std::cerr << "Failed to get hardware info for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+
+ if (hwInfo.versionNumber < kVersionWithoutSuperencryption) {
+ selfTestGetCsrV1(componentName, irpc);
+ } else {
+ selfTestGetCsrV3(componentName, irpc);
+ }
+}
diff --git a/provisioner/rkp_factory_extraction_lib.h b/provisioner/rkp_factory_extraction_lib.h
new file mode 100644
index 0000000..a218338
--- /dev/null
+++ b/provisioner/rkp_factory_extraction_lib.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+#include <android/binder_manager.h>
+#include <cppbor.h>
+#include <keymaster/cppcose/cppcose.h>
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+// Challenge size must be between 32 and 64 bytes inclusive.
+constexpr size_t kChallengeSize = 64;
+
+// Contains a the result of an operation that should return cborData on success.
+// Returns an an error message and null cborData on error.
+template <typename T> struct CborResult {
+ std::unique_ptr<T> cborData;
+ std::string errMsg;
+};
+
+// Return `buffer` encoded as a base64 string.
+std::string toBase64(const std::vector<uint8_t>& buffer);
+
+// Generate a random challenge containing `kChallengeSize` bytes.
+std::vector<uint8_t> generateChallenge();
+
+// Get a certificate signing request for the given IRemotelyProvisionedComponent.
+// On error, the csr Array is null, and the string field contains a description of
+// what went wrong.
+CborResult<cppbor::Array>
+getCsr(std::string_view componentName,
+ aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
+
+// Generates a test certificate chain and validates it, exiting the process on error.
+void selfTestGetCsr(
+ std::string_view componentName,
+ aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
diff --git a/provisioner/rkp_factory_extraction_lib_test.cpp b/provisioner/rkp_factory_extraction_lib_test.cpp
new file mode 100644
index 0000000..05509b3
--- /dev/null
+++ b/provisioner/rkp_factory_extraction_lib_test.cpp
@@ -0,0 +1,259 @@
+/*
+ * 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 "rkp_factory_extraction_lib.h"
+
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock-more-matchers.h"
+#include <aidl/android/hardware/security/keymint/DeviceInfo.h>
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/MacedPublicKey.h>
+#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <vector>
+
+#include "aidl/android/hardware/security/keymint/ProtectedData.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
+#include "cppbor.h"
+
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+
+using namespace ::aidl::android::hardware::security::keymint;
+using namespace ::cppbor;
+using namespace ::testing;
+
+namespace cppbor {
+
+std::ostream& operator<<(std::ostream& os, const Item& item) {
+ return os << prettyPrint(&item);
+}
+
+std::ostream& operator<<(std::ostream& os, const std::unique_ptr<Item>& item) {
+ return os << *item;
+}
+
+std::ostream& operator<<(std::ostream& os, const Item* item) {
+ return os << *item;
+}
+
+} // namespace cppbor
+
+class MockIRemotelyProvisionedComponent : public IRemotelyProvisionedComponentDefault {
+ public:
+ MOCK_METHOD(ScopedAStatus, getHardwareInfo, (RpcHardwareInfo * _aidl_return), (override));
+ MOCK_METHOD(ScopedAStatus, generateEcdsaP256KeyPair,
+ (bool in_testMode, MacedPublicKey* out_macedPublicKey,
+ std::vector<uint8_t>* _aidl_return),
+ (override));
+ MOCK_METHOD(ScopedAStatus, generateCertificateRequest,
+ (bool in_testMode, const std::vector<MacedPublicKey>& in_keysToSign,
+ const std::vector<uint8_t>& in_endpointEncryptionCertChain,
+ const std::vector<uint8_t>& in_challenge, DeviceInfo* out_deviceInfo,
+ ProtectedData* out_protectedData, std::vector<uint8_t>* _aidl_return),
+ (override));
+ MOCK_METHOD(ScopedAStatus, generateCertificateRequestV2,
+ (const std::vector<MacedPublicKey>& in_keysToSign,
+ const std::vector<uint8_t>& in_challenge, std::vector<uint8_t>* _aidl_return),
+ (override));
+ MOCK_METHOD(ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override));
+ MOCK_METHOD(ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override));
+};
+
+TEST(LibRkpFactoryExtractionTests, ToBase64) {
+ std::vector<uint8_t> input(UINT8_MAX + 1);
+ for (int i = 0; i < input.size(); ++i) {
+ input[i] = i;
+ }
+
+ // Test three lengths so we get all the different paddding options
+ EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4"
+ "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV"
+ "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj"
+ "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8"
+ "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv"
+ "s7e7v8PHy8/T19vf4+fr7/P3+/w==",
+ toBase64(input));
+
+ input.push_back(42);
+ EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4"
+ "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV"
+ "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj"
+ "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8"
+ "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv"
+ "s7e7v8PHy8/T19vf4+fr7/P3+/yo=",
+ toBase64(input));
+
+ input.push_back(42);
+ EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4"
+ "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV"
+ "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj"
+ "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8"
+ "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv"
+ "s7e7v8PHy8/T19vf4+fr7/P3+/yoq",
+ toBase64(input));
+}
+
+TEST(LibRkpFactoryExtractionTests, UniqueChallengeSmokeTest) {
+ // This will at least catch VERY broken implementations.
+ constexpr size_t NUM_CHALLENGES = 32;
+ std::set<std::vector<uint8_t>> challenges;
+ for (size_t i = 0; i < NUM_CHALLENGES; ++i) {
+ const std::vector<uint8_t> challenge = generateChallenge();
+ const auto [_, wasInserted] = challenges.insert(generateChallenge());
+ EXPECT_TRUE(wasInserted) << "Duplicate challenge: " << toBase64(challenge);
+ }
+}
+
+TEST(LibRkpFactoryExtractionTests, GetCsrWithV2Hal) {
+ ASSERT_TRUE(true);
+
+ const std::vector<uint8_t> kFakeMac = {1, 2, 3, 4};
+
+ Map cborDeviceInfo;
+ cborDeviceInfo.add("product", "gShoe");
+ cborDeviceInfo.add("version", 2);
+ cborDeviceInfo.add("brand", "Fake Brand");
+ cborDeviceInfo.add("manufacturer", "Fake Mfr");
+ cborDeviceInfo.add("model", "Fake Model");
+ cborDeviceInfo.add("device", "Fake Device");
+ cborDeviceInfo.add("vb_state", "orange");
+ cborDeviceInfo.add("bootloader_state", "unlocked");
+ cborDeviceInfo.add("vbmeta_digest", std::vector<uint8_t>{1, 2, 3, 4});
+ cborDeviceInfo.add("system_patch_level", 42);
+ cborDeviceInfo.add("boot_patch_level", 31415);
+ cborDeviceInfo.add("vendor_patch_level", 0);
+ cborDeviceInfo.add("fused", 0);
+ cborDeviceInfo.add("security_level", "tee");
+ cborDeviceInfo.add("os_version", "the best version");
+ const DeviceInfo kVerifiedDeviceInfo = {cborDeviceInfo.canonicalize().encode()};
+
+ Array cborProtectedData;
+ cborProtectedData.add(Bstr()); // protected
+ cborProtectedData.add(Map()); // unprotected
+ cborProtectedData.add(Bstr()); // ciphertext
+ cborProtectedData.add(Array()); // recipients
+ const ProtectedData kProtectedData = {cborProtectedData.encode()};
+
+ std::vector<uint8_t> eekChain;
+ std::vector<uint8_t> challenge;
+
+ // Set up mock, then call getSCsr
+ auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
+ EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
+ hwInfo->versionNumber = 2;
+ return ScopedAStatus::ok();
+ });
+ EXPECT_CALL(*mockRpc,
+ generateCertificateRequest(false, // testMode
+ IsEmpty(), // keysToSign
+ _, // endpointEncryptionCertChain
+ _, // challenge
+ NotNull(), // deviceInfo
+ NotNull(), // protectedData
+ NotNull())) // _aidl_return
+ .WillOnce(DoAll(SaveArg<2>(&eekChain), //
+ SaveArg<3>(&challenge), //
+ SetArgPointee<4>(kVerifiedDeviceInfo), //
+ SetArgPointee<5>(kProtectedData), //
+ SetArgPointee<6>(kFakeMac), //
+ Return(ByMove(ScopedAStatus::ok())))); //
+
+ auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get());
+ ASSERT_THAT(csr, NotNull()) << csrErrMsg;
+ ASSERT_THAT(csr->asArray(), Pointee(Property(&Array::size, Eq(4))));
+
+ // Verify the input parameters that we received
+ auto [parsedEek, ignore1, eekParseError] = parse(eekChain);
+ ASSERT_THAT(parsedEek, NotNull()) << eekParseError;
+ EXPECT_THAT(parsedEek->asArray(), Pointee(Property(&Array::size, Gt(1))));
+ EXPECT_THAT(challenge, Property(&std::vector<uint8_t>::size, Eq(kChallengeSize)));
+
+ // Device info consists of (verified info, unverified info)
+ const Array* deviceInfoArray = csr->get(0)->asArray();
+ EXPECT_THAT(deviceInfoArray, Pointee(Property(&Array::size, 2)));
+
+ // Verified device info must match our mock value
+ const Map* actualVerifiedDeviceInfo = deviceInfoArray->get(0)->asMap();
+ EXPECT_THAT(actualVerifiedDeviceInfo, Pointee(Property(&Map::size, Eq(cborDeviceInfo.size()))));
+ EXPECT_THAT(actualVerifiedDeviceInfo->get("product"), Pointee(Eq(Tstr("gShoe"))));
+ EXPECT_THAT(actualVerifiedDeviceInfo->get("version"), Pointee(Eq(Uint(2))));
+
+ // Empty unverified device info
+ const Map* actualUnverifiedDeviceInfo = deviceInfoArray->get(1)->asMap();
+ EXPECT_THAT(actualUnverifiedDeviceInfo, Pointee(Property(&Map::size, Eq(0))));
+
+ // Challenge must match the call to generateCertificateRequest
+ const Bstr* actualChallenge = csr->get(1)->asBstr();
+ EXPECT_THAT(actualChallenge, Pointee(Property(&Bstr::value, Eq(challenge))));
+
+ // Protected data must match the mock value
+ const Array* actualProtectedData = csr->get(2)->asArray();
+ EXPECT_THAT(actualProtectedData, Pointee(Eq(ByRef(cborProtectedData))));
+
+ // Ensure the maced public key matches the expected COSE_mac0
+ const Array* actualMacedKeys = csr->get(3)->asArray();
+ ASSERT_THAT(actualMacedKeys, Pointee(Property(&Array::size, Eq(4))));
+ ASSERT_THAT(actualMacedKeys->get(0)->asBstr(), NotNull());
+ auto [macProtectedParams, ignore2, macParamParseError] =
+ parse(actualMacedKeys->get(0)->asBstr());
+ ASSERT_THAT(macProtectedParams, NotNull()) << macParamParseError;
+ Map expectedMacProtectedParams;
+ expectedMacProtectedParams.add(1, 5);
+ EXPECT_THAT(macProtectedParams, Pointee(Eq(ByRef(expectedMacProtectedParams))));
+ EXPECT_THAT(actualMacedKeys->get(1)->asMap(), Pointee(Property(&Map::size, Eq(0))));
+ EXPECT_THAT(actualMacedKeys->get(2)->asNull(), NotNull());
+ EXPECT_THAT(actualMacedKeys->get(3)->asBstr(), Pointee(Eq(Bstr(kFakeMac))));
+}
+
+TEST(LibRkpFactoryExtractionTests, GetCsrWithV3Hal) {
+ const std::vector<uint8_t> kCsr = Array()
+ .add(3 /* version */)
+ .add(Map() /* UdsCerts */)
+ .add(Array() /* DiceCertChain */)
+ .add(Array() /* SignedData */)
+ .encode();
+ std::vector<uint8_t> challenge;
+
+ // Set up mock, then call getCsr
+ auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
+ EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
+ hwInfo->versionNumber = 3;
+ return ScopedAStatus::ok();
+ });
+ EXPECT_CALL(*mockRpc,
+ generateCertificateRequestV2(IsEmpty(), // keysToSign
+ _, // challenge
+ NotNull())) // _aidl_return
+ .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(kCsr),
+ Return(ByMove(ScopedAStatus::ok()))));
+
+ auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get());
+ ASSERT_THAT(csr, NotNull()) << csrErrMsg;
+ ASSERT_THAT(csr, Pointee(Property(&Array::size, Eq(4))));
+
+ EXPECT_THAT(csr->get(0 /* version */), Pointee(Eq(Uint(3))));
+ EXPECT_THAT(csr->get(1)->asMap(), NotNull());
+ EXPECT_THAT(csr->get(2)->asArray(), NotNull());
+ EXPECT_THAT(csr->get(3)->asArray(), NotNull());
+}
diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp
index 2e59dbd..2aeabe0 100644
--- a/provisioner/rkp_factory_extraction_tool.cpp
+++ b/provisioner/rkp_factory_extraction_tool.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#include <string>
-#include <vector>
-
#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
#include <android/binder_manager.h>
#include <cppbor.h>
@@ -26,20 +23,22 @@
#include <remote_prov/remote_prov_utils.h>
#include <sys/random.h>
-using aidl::android::hardware::security::keymint::DeviceInfo;
+#include <string>
+#include <vector>
+
+#include "rkp_factory_extraction_lib.h"
+
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::remote_prov::generateEekChain;
-using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain;
using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild;
using namespace cppbor;
using namespace cppcose;
-DEFINE_bool(test_mode, false, "If enabled, a fake EEK key/cert are used.");
-
-DEFINE_string(output_format, "csr", "How to format the output. Defaults to 'csr'.");
+DEFINE_string(output_format, "build+csr", "How to format the output. Defaults to 'build+csr'.");
+DEFINE_bool(self_test, false,
+ "If true, the tool does not output CSR data, but instead performs a self-test, "
+ "validating a test payload for correctness. This may be used to verify a device on the "
+ "factory line before attempting to upload the output to the device info service.");
namespace {
@@ -48,91 +47,12 @@
constexpr std::string_view kBuildPlusCsr = "build+csr"; // Text-encoded (JSON) build
// fingerprint plus CSR.
-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);
-
- ssize_t bytesRemaining = static_cast<ssize_t>(challenge.size());
- uint8_t* writePtr = challenge.data();
- while (bytesRemaining > 0) {
- int bytesRead = getrandom(writePtr, bytesRemaining, /*flags=*/0);
- if (bytesRead < 0 && errno != EINTR) {
- std::cerr << errno << ": " << strerror(errno) << std::endl;
- exit(-1);
- }
- bytesRemaining -= bytesRead;
- writePtr += bytesRead;
- }
-
- return challenge;
-}
-
-Array composeCertificateRequest(const ProtectedData& protectedData,
- const DeviceInfo& verifiedDeviceInfo,
- const std::vector<uint8_t>& challenge,
- const std::vector<uint8_t>& keysToSignMac) {
- Array macedKeysToSign = Array()
- .add(std::vector<uint8_t>(0)) // empty protected headers as bstr
- .add(Map()) // empty unprotected headers
- .add(Null()) // nil for the payload
- .add(keysToSignMac); // MAC as returned from the HAL
-
- Array deviceInfo =
- Array().add(EncodedItem(verifiedDeviceInfo.deviceInfo)).add(Map()); // Empty device info
-
- Array certificateRequest = Array()
- .add(std::move(deviceInfo))
- .add(challenge)
- .add(EncodedItem(protectedData.protectedData))
- .add(std::move(macedKeysToSign));
- return certificateRequest;
-}
-
-std::vector<uint8_t> getEekChain() {
- if (FLAGS_test_mode) {
- const std::vector<uint8_t> kFakeEekId = {'f', 'a', 'k', 'e', 0};
- 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;
- return eek;
- }
-
- return getProdEekChain();
-}
-
-void writeOutput(const Array& csr) {
+void writeOutput(const std::string instance_name, const Array& csr) {
if (FLAGS_output_format == kBinaryCsrOutput) {
auto bytes = csr.encode();
std::copy(bytes.begin(), bytes.end(), std::ostream_iterator<char>(std::cout));
} else if (FLAGS_output_format == kBuildPlusCsr) {
- auto [json, error] = jsonEncodeCsrWithBuild(csr);
+ auto [json, error] = jsonEncodeCsrWithBuild(instance_name, csr);
if (!error.empty()) {
std::cerr << "Error JSON encoding the output: " << error;
exit(1);
@@ -158,24 +78,20 @@
auto rkp_service = IRemotelyProvisionedComponent::fromBinder(rkp_binder);
if (!rkp_service) {
std::cerr << "Unable to get binder object for '" << fullName << "', skipping.";
- return;
- }
-
- std::vector<uint8_t> keysToSignMac;
- std::vector<MacedPublicKey> emptyKeys;
- DeviceInfo verifiedDeviceInfo;
- ProtectedData protectedData;
- ::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;
exit(-1);
}
- auto request =
- composeCertificateRequest(protectedData, verifiedDeviceInfo, challenge, keysToSignMac);
- writeOutput(request);
+
+ if (FLAGS_self_test) {
+ selfTestGetCsr(name, rkp_service.get());
+ } else {
+ auto [request, errMsg] = getCsr(name, rkp_service.get());
+ if (!request) {
+ std::cerr << "Unable to build CSR for '" << fullName << ": " << errMsg << std::endl;
+ exit(-1);
+ }
+
+ writeOutput(std::string(name), *request);
+ }
}
} // namespace