Diced: Add service diced an implementation of android.security.dice
Bug: 198197213
Test: diced_test
Change-Id: I7075c2e7ac8e48a13f4eb177f2e989ff1e6695a2
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.");
+ }
+}