Diced: Add service diced an implementation of android.security.dice
Bug: 198197213
Test: diced_test
Change-Id: I7075c2e7ac8e48a13f4eb177f2e989ff1e6695a2
diff --git a/diced/Android.bp b/diced/Android.bp
new file mode 100644
index 0000000..8de046f
--- /dev/null
+++ b/diced/Android.bp
@@ -0,0 +1,136 @@
+// 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"],
+
+ 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"],
+
+ 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",
+ "libkeystore2_vintf_rust",
+ "liblibc",
+ "liblog_event_list",
+ "liblog_rust",
+ "libthiserror",
+ ],
+}
+
+rust_binary {
+ name: "diced",
+ srcs: ["src/diced_main.rs"],
+ rustlibs: [
+ "libandroid_logger",
+ "libbinder_rs",
+ "libdiced",
+ "libdiced_open_dice_cbor",
+ "libdiced_utils",
+ "liblog_rust",
+ ],
+ init_rc: ["diced.rc"],
+}
+
+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",
+ "libthiserror",
+ ],
+}
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/src/diced_main.rs b/diced/src/diced_main.rs
new file mode 100644
index 0000000..bc6e762
--- /dev/null
+++ b/diced/src/diced_main.rs
@@ -0,0 +1,50 @@
+// 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 diced::dice::CDI_SIZE;
+use diced::DiceNode;
+use diced::ResidentNode;
+use std::panic;
+use std::sync::Arc;
+
+static DICE_SERVICE_NAME: &str = "android.security.dice";
+
+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::new(
+ ResidentNode::new(&[0u8; CDI_SIZE], &[1u8; CDI_SIZE], vec![])
+ .expect("Failed to construct a resident node."),
+ );
+
+ let node =
+ DiceNode::new_as_binder(node_impl).expect("Failed to create IDiceNode service instance.");
+
+ binder::add_service(DICE_SERVICE_NAME, node.as_binder())
+ .expect("Failed to register IDiceNode 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..92aa97c
--- /dev/null
+++ b/diced/src/error.rs
@@ -0,0 +1,125 @@
+// 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::{
+ public_api::Result as BinderResult, ExceptionCode, 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/lib.rs b/diced/src/lib.rs
new file mode 100644
index 0000000..ac9484a
--- /dev/null
+++ b/diced/src/lib.rs
@@ -0,0 +1,140 @@
+// 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 resident_node;
+
+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::{
+ IDiceNode::BnDiceNode, IDiceNode::IDiceNode, ResponseCode::ResponseCode,
+};
+use anyhow::{Context, Result};
+use binder::{public_api::Result as BinderResult, BinderFeatures, Strong, ThreadState};
+pub use diced_open_dice_cbor as dice;
+use error::{map_or_log_err, Error};
+use libc::uid_t;
+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>,
+}
+
+fn client_input_values(uid: uid_t) -> Result<BinderInputValues> {
+ Ok(BinderInputValues {
+ codeHash: vec![0; dice::HASH_SIZE],
+ config: BinderConfig {
+ desc: dice::bcc::format_config_descriptor(Some(&format!("{}", uid)), None, true)
+ .context("In client_input_values: failed to format config descriptor")?,
+ },
+ authorityHash: vec![0; dice::HASH_SIZE],
+ authorityDescriptor: None,
+ hidden: vec![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> {
+ 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> {
+ 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> {
+ 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<()> {
+ 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)
+ }
+}
diff --git a/diced/src/resident_node.rs b/diced/src/resident_node.rs
new file mode 100644
index 0000000..5fe4dc9
--- /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.try_into())
+ .collect::<Result<_>>()?;
+
+ 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..f76ebc9
--- /dev/null
+++ b/diced/src/sample_inputs.rs
@@ -0,0 +1,255 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module provides a set of sample input values for a DICE chain, a sample UDS,
+//! as well as tuple of CDIs and BCC derived thereof.
+
+use android_hardware_security_dice::aidl::android::hardware::security::dice::{
+ Config::Config as BinderConfig, InputValues::InputValues as BinderInputValues, Mode::Mode,
+};
+use anyhow::{Context, Result};
+use dice::ContextImpl;
+use diced_open_dice_cbor as dice;
+use diced_utils::cbor;
+use diced_utils::InputValues;
+use keystore2_crypto::ZVec;
+use std::convert::{TryFrom, TryInto};
+use std::io::Write;
+
+/// Sample UDS used to perform the root dice flow by `make_sample_bcc_and_cdis`.
+pub static UDS: &[u8; dice::CDI_SIZE] = &[
+ 0x65, 0x4f, 0xab, 0xa9, 0xa5, 0xad, 0x0f, 0x5e, 0x15, 0xc3, 0x12, 0xf7, 0x77, 0x45, 0xfa, 0x55,
+ 0x18, 0x6a, 0xa6, 0x34, 0xb6, 0x7c, 0x82, 0x7b, 0x89, 0x4c, 0xc5, 0x52, 0xd3, 0x27, 0x35, 0x8e,
+];
+
+fn encode_pub_key_ed25519(pub_key: &[u8], stream: &mut dyn Write) -> Result<()> {
+ cbor::encode_header(5 /* CBOR MAP */, 5, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode map header.")?;
+ cbor::encode_number(1, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode Key type tag.")?;
+ cbor::encode_number(1, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode Key type.")?;
+ cbor::encode_number(3, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode algorithm tag.")?;
+ // Encoding a -8 for AlgorithmEdDSA. The encoded number is -1 - <header argument>,
+ // the an argument of 7 below.
+ cbor::encode_header(1 /* CBOR NEGATIVE INT */, 7 /* -1 -7 = -8*/, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode algorithm.")?;
+ cbor::encode_number(4, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode ops tag.")?;
+ // Ops 2 for verify.
+ cbor::encode_number(2, stream).context("In encode_pub_key_ed25519: Trying to encode ops.")?;
+ cbor::encode_header(1 /* CBOR NEGATIVE INT */, 0 /* -1 -0 = -1*/, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode curve tag.")?;
+ // Curve 6 for Ed25519
+ cbor::encode_number(6, stream).context("In encode_pub_key_ed25519: Trying to encode curve.")?;
+ cbor::encode_header(1 /* CBOR NEGATIVE INT */, 1 /* -1 -1 = -2*/, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode X coordinate tag.")?;
+ cbor::encode_bstr(pub_key, stream)
+ .context("In encode_pub_key_ed25519: Trying to encode X coordinate.")?;
+ Ok(())
+}
+
+/// Derives a tuple of (CDI_ATTEST, CDI_SEAL, BCC) derived of the vector of input values returned
+/// by `get_input_values_vector`.
+pub fn make_sample_bcc_and_cdis() -> Result<(ZVec, ZVec, Vec<u8>)> {
+ let mut dice_ctx = dice::OpenDiceCborContext::new();
+ let private_key_seed = dice_ctx
+ .derive_cdi_private_key_seed(UDS)
+ .context("In make_sample_bcc_and_cdis: Trying to derive private key seed.")?;
+
+ let (public_key, _) =
+ dice_ctx
+ .keypair_from_seed(&private_key_seed[..].try_into().context(
+ "In make_sample_bcc_and_cids: Failed to convert seed to array reference.",
+ )?)
+ .context("In make_sample_bcc_and_cids: Failed to generate key pair.")?;
+
+ let input_values_vector = get_input_values_vector();
+
+ let (cdi_attest, cdi_seal, mut cert) = dice_ctx
+ .main_flow(
+ UDS,
+ UDS,
+ &InputValues::try_from(&input_values_vector[0])
+ .context("In make_sample_bcc_and_cdis: Trying to convert input values. (0)")?,
+ )
+ .context("In make_sample_bcc_and_cdis: Trying to run first main flow.")?;
+
+ let mut bcc: Vec<u8> = vec![];
+
+ cbor::encode_header(4 /* CBOR ARRAY */, 2, &mut bcc)
+ .context("In make_sample_bcc_and_cdis: Trying to encode array header.")?;
+ encode_pub_key_ed25519(&public_key, &mut bcc)
+ .context("In make_sample_bcc_and_cdis: Trying encode pub_key.")?;
+
+ bcc.append(&mut cert);
+
+ let (cdi_attest, cdi_seal, bcc) = dice_ctx
+ .bcc_main_flow(
+ &cdi_attest[..].try_into().context(
+ "In make_sample_bcc_and_cdis: Failed to convert cdi_attest to array reference. (1)",
+ )?,
+ &cdi_seal[..].try_into().context(
+ "In make_sample_bcc_and_cdis: Failed to convert cdi_seal to array reference. (1)",
+ )?,
+ &bcc,
+ &InputValues::try_from(&input_values_vector[1])
+ .context("In make_sample_bcc_and_cdis: Trying to convert input values. (1)")?,
+ )
+ .context("In make_sample_bcc_and_cdis: Trying to run first bcc main flow.")?;
+ dice_ctx
+ .bcc_main_flow(
+ &cdi_attest[..].try_into().context(
+ "In make_sample_bcc_and_cdis: Failed to convert cdi_attest to array reference. (2)",
+ )?,
+ &cdi_seal[..].try_into().context(
+ "In make_sample_bcc_and_cdis: Failed to convert cdi_seal to array reference. (2)",
+ )?,
+ &bcc,
+ &InputValues::try_from(&input_values_vector[2])
+ .context("In make_sample_bcc_and_cdis: Trying to convert input values. (2)")?,
+ )
+ .context("In make_sample_bcc_and_cdis: Trying to run second bcc main flow.")
+}
+
+fn make_input_values(
+ code_hash: &[u8; dice::HASH_SIZE],
+ authority_hash: &[u8; dice::HASH_SIZE],
+ config_name: &str,
+ config_version: u64,
+ config_resettable: bool,
+ mode: Mode,
+ hidden: &[u8; dice::HIDDEN_SIZE],
+) -> Result<BinderInputValues> {
+ Ok(BinderInputValues {
+ codeHash: code_hash.to_vec(),
+ 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.to_vec(),
+ authorityDescriptor: None,
+ hidden: hidden.to_vec(),
+ 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..abb8a7b
--- /dev/null
+++ b/diced/src/utils.rs
@@ -0,0 +1,445 @@
+// 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::{anyhow, Context, Result};
+use dice::ContextImpl;
+use diced_open_dice_cbor as dice;
+use keystore2_crypto::ZVec;
+use std::convert::{TryFrom, 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> TryFrom<&'a BinderInputValues> for InputValues<'a> {
+ type Error = anyhow::Error;
+
+ fn try_from(input_values: &'a BinderInputValues) -> Result<InputValues<'a>> {
+ if input_values.codeHash.len() != dice::HASH_SIZE {
+ return Err(anyhow!(format!(
+ "In try_from: Code hash has invalid size: {}",
+ input_values.codeHash.len()
+ )));
+ }
+ if input_values.authorityHash.len() != dice::HASH_SIZE {
+ return Err(anyhow!(format!(
+ "In try_from: Authority hash has invalid size: {}",
+ input_values.authorityHash.len()
+ )));
+ }
+ if input_values.hidden.len() != dice::HIDDEN_SIZE {
+ return Err(anyhow!(format!(
+ "In try_from: Hidden has invalid size: {}",
+ input_values.hidden.len()
+ )));
+ }
+
+ Ok(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] {
+ // If `self` was created using try_from the length was checked and this cannot panic.
+ self.0.codeHash.as_slice().try_into().unwrap()
+ }
+
+ fn config(&self) -> dice::Config {
+ dice::Config::Descriptor(self.0.config.desc.as_slice())
+ }
+
+ fn authority_hash(&self) -> &[u8; dice::HASH_SIZE] {
+ // If `self` was created using try_from the length was checked and this cannot panic.
+ self.0.authorityHash.as_slice().try_into().unwrap()
+ }
+
+ 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.as_slice().try_into().unwrap()
+ }
+}
+
+/// 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.to_vec(),
+ cdiSeal: cdi_seal.to_vec(),
+ 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(),
+ })
+ }
+
+ /// 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:")
+ }
+}
+
+/// 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]
+ );
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use android_hardware_security_dice::aidl::android::hardware::security::dice::{
+ Config::Config as BinderConfig, InputValues::InputValues as BinderInputValues,
+ };
+ use dice::InputValues as DiceInputValues;
+ use diced_open_dice_cbor as dice;
+
+ static CODE_HASH_TEST_VECTOR: [u8; dice::HASH_SIZE] = [1u8; dice::HASH_SIZE];
+ static CONFIG_DESCRIPTOR_TEST_VECTOR: &[u8] = &[3, 2, 1];
+ static AUTHORITY_HASH_TEST_VECTOR: [u8; dice::HASH_SIZE] = [3u8; dice::HASH_SIZE];
+ static AUTHORITY_DESCRIPTOR_TEST_VECTOR: &[u8] = &[1, 2, 3];
+ static HIDDEN_TEST_VECTOR: [u8; dice::HIDDEN_SIZE] = [4u8; dice::HIDDEN_SIZE];
+
+ #[test]
+ fn try_from_input_values_binder() {
+ let input_values_good = BinderInputValues {
+ codeHash: CODE_HASH_TEST_VECTOR.to_vec(),
+ config: BinderConfig { desc: CONFIG_DESCRIPTOR_TEST_VECTOR.to_vec() },
+ authorityHash: AUTHORITY_HASH_TEST_VECTOR.to_vec(),
+ authorityDescriptor: Some(AUTHORITY_DESCRIPTOR_TEST_VECTOR.to_vec()),
+ mode: BinderMode::NORMAL,
+ hidden: HIDDEN_TEST_VECTOR.to_vec(),
+ };
+
+ let converted_input_values: InputValues =
+ (&input_values_good).try_into().expect("This should succeed.");
+ assert_eq!(*converted_input_values.code_hash(), CODE_HASH_TEST_VECTOR);
+ assert_eq!(
+ converted_input_values.config(),
+ dice::Config::Descriptor(CONFIG_DESCRIPTOR_TEST_VECTOR)
+ );
+ assert_eq!(*converted_input_values.authority_hash(), AUTHORITY_HASH_TEST_VECTOR);
+ assert_eq!(
+ converted_input_values.authority_descriptor(),
+ Some(AUTHORITY_DESCRIPTOR_TEST_VECTOR)
+ );
+ assert_eq!(converted_input_values.mode(), dice::Mode::Normal);
+ assert_eq!(*converted_input_values.hidden(), HIDDEN_TEST_VECTOR);
+
+ // One more time without authority descriptor.
+ let input_values_still_good_without_authority_descriptor =
+ BinderInputValues { authorityDescriptor: None, ..input_values_good.clone() };
+
+ let converted_input_values: InputValues =
+ (&input_values_still_good_without_authority_descriptor)
+ .try_into()
+ .expect("This should succeed.");
+ assert_eq!(*converted_input_values.code_hash(), CODE_HASH_TEST_VECTOR);
+ assert_eq!(
+ converted_input_values.config(),
+ dice::Config::Descriptor(CONFIG_DESCRIPTOR_TEST_VECTOR)
+ );
+ assert_eq!(*converted_input_values.authority_hash(), AUTHORITY_HASH_TEST_VECTOR);
+ assert_eq!(converted_input_values.authority_descriptor(), None);
+ assert_eq!(converted_input_values.mode(), dice::Mode::Normal);
+ assert_eq!(*converted_input_values.hidden(), HIDDEN_TEST_VECTOR);
+
+ // Now check the failure cases.
+ // Wrong sized codeHash.
+ let input_values_bad_code_hash = BinderInputValues {
+ codeHash: vec![1u8; dice::HASH_SIZE + 1],
+ ..input_values_good.clone()
+ };
+
+ InputValues::try_from(&input_values_bad_code_hash)
+ .expect_err("Conversion of input values with wrong sized code hash succeeded.");
+
+ // Wrong sized authority hash.
+ let input_values_bad_authority_hash = BinderInputValues {
+ authorityHash: vec![1u8; dice::HASH_SIZE + 1],
+ ..input_values_good.clone()
+ };
+
+ InputValues::try_from(&input_values_bad_authority_hash)
+ .expect_err("Conversion of input values with wrong sized authority hash succeeded.");
+
+ // Wrong sized hidden.
+ let input_values_bad_hidden = BinderInputValues {
+ authorityHash: vec![1u8; dice::HASH_SIZE + 1],
+ ..input_values_good.clone()
+ };
+
+ InputValues::try_from(&input_values_bad_hidden)
+ .expect_err("Conversion of input values with wrong sized hidden succeeded.");
+ }
+}