Encode and decode functions for DPE commands and responses
The format of the commands and responses are mostly defined in the DPE
specification. A few arguments that the spec leaves for profiles to
define are CBOR-encoded similarly to standard fields. These are not yet
defined in the Open Profile specification, but will be in the next
version.
Change-Id: I1532dce911a7fb9d8410bc3ea173e184fc4923aa
Reviewed-on: https://pigweed-review.googlesource.com/c/open-dice/+/209175
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Reviewed-by: Andrew Scull <ascull@google.com>
Reviewed-by: Marshall Pierce <marshallpierce@google.com>
Commit-Queue: Darren Krahn <dkrahn@google.com>
diff --git a/dpe-rs/Cargo.toml b/dpe-rs/Cargo.toml
index c491b03..69113c7 100644
--- a/dpe-rs/Cargo.toml
+++ b/dpe-rs/Cargo.toml
@@ -28,6 +28,8 @@
log = "0.4.20"
minicbor = "0.19.1"
noise-protocol = "0.2.0"
+num-derive = "0.4.2"
+num-traits = { version = "0.2.19", default-features = false }
rand_core = "0.6.4"
zeroize = { version = "1.7.0", features = ["zeroize_derive"], default-features = false }
diff --git a/dpe-rs/src/cbor.rs b/dpe-rs/src/cbor.rs
index d0dc1fa..39e9fb0 100644
--- a/dpe-rs/src/cbor.rs
+++ b/dpe-rs/src/cbor.rs
@@ -152,3 +152,31 @@
.map_err(|_| ErrCode::InternalError)?;
Ok(())
}
+
+/// Tokenizes a given CBOR encoded value. This is intended for testing and
+/// panics on failure.
+#[cfg(test)]
+#[allow(unused_results, clippy::unwrap_used)]
+pub(crate) fn tokenize_cbor_for_debug(
+ cbor: &[u8],
+) -> heapless::Vec<minicbor::data::Type, 100> {
+ let mut vec = heapless::Vec::<minicbor::data::Type, 100>::new();
+ let mut decoder = Decoder::new(cbor);
+ while let Ok(t) = decoder.datatype() {
+ if vec.push(t).is_err() {
+ break;
+ }
+ match t {
+ minicbor::data::Type::Array => {
+ decoder.array().unwrap();
+ }
+ minicbor::data::Type::Map => {
+ decoder.map().unwrap();
+ }
+ _ => {
+ decoder.skip().unwrap();
+ }
+ }
+ }
+ vec
+}
diff --git a/dpe-rs/src/constants.rs b/dpe-rs/src/constants.rs
index 2c235e7..f42954e 100644
--- a/dpe-rs/src/constants.rs
+++ b/dpe-rs/src/constants.rs
@@ -50,3 +50,31 @@
/// The maximum size in bytes of a session handshake payload.
pub(crate) const MAX_HANDSHAKE_PAYLOAD_SIZE: usize = 8;
+
+/// The size in bytes of a CDI.
+pub(crate) const DICE_CDI_SIZE: usize = 32;
+
+/// The size in bytes of a UDS.
+pub(crate) const DICE_UDS_SIZE: usize = 64;
+
+/// The size in bytes of a DPE context handle.
+pub(crate) const DPE_HANDLE_SIZE: usize = 16;
+
+/// The maximum size in bytes of a message buffer. This is the largest buffer
+/// size the DPE will support.
+pub(crate) const DPE_MAX_MESSAGE_SIZE: usize = 8192;
+
+/// The maximum size in bytes of a small message buffer.
+pub(crate) const DPE_MAX_SMALL_MESSAGE_SIZE: usize = 256;
+
+/// The maximum size in bytes of a certificate.
+pub(crate) const DPE_MAX_CERTIFICATE_SIZE: usize = 1024;
+
+/// The maximum number of certificates that can appear in a certificate chain.
+pub(crate) const DPE_MAX_CERTIFICATES_PER_CHAIN: usize = 4;
+
+/// The maximum number of internal inputs that can be included in a message.
+pub(crate) const DPE_MAX_INTERNAL_INPUTS: usize = 8;
+
+/// The maximum number of version slots supported by a DPE context.
+pub(crate) const DPE_MAX_VERSION_SLOTS: usize = 16;
diff --git a/dpe-rs/src/encode.rs b/dpe-rs/src/encode.rs
new file mode 100644
index 0000000..bbaf508
--- /dev/null
+++ b/dpe-rs/src/encode.rs
@@ -0,0 +1,1797 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+//! Types and functions for encoding and decoding command/response messages.
+
+use crate::args::{ArgId, ArgMap, ArgTypeMap, ArgTypeSelector, ArgValue};
+use crate::byte_array_wrapper;
+use crate::cbor::{
+ cbor_decoder_from_message, cbor_encoder_from_message, encode_bytes_prefix,
+ DecoderExt,
+};
+use crate::constants::*;
+use crate::crypto::{HandshakePayload, Hash};
+use crate::error::{DpeResult, ErrCode};
+use crate::memory::{Message, SizedMessage};
+use heapless::Vec;
+use log::{debug, error};
+use minicbor::Decoder;
+use num_derive::{FromPrimitive, ToPrimitive};
+use zeroize::ZeroizeOnDrop;
+
+// Both session and command messages are encoded as a CBOR array with two
+// elements. See the following snippets from the DPE specification.
+//
+// session-message = [
+// session-id: uint,
+// message: bytes, ; Ciphertext, unless using the plaintext session.
+// ]
+//
+// command-message = [
+// command-id: $command-id,
+// input-args: $input-args,
+// ]
+//
+// response-message = [
+// error-code: $error-code,
+// output-args: $output-args,
+// ]
+const MESSAGE_ARRAY_SIZE: u64 = 2;
+
+byte_array_wrapper!(Uds, DICE_UDS_SIZE, "UDS");
+byte_array_wrapper!(Cdi, DICE_CDI_SIZE, "CDI");
+byte_array_wrapper!(ContextHandle, DPE_HANDLE_SIZE, "context handle");
+
+impl ContextHandle {
+ /// Creates a `ContextHandle` from the given slice and returns an option
+ /// which is `None` if the slice is empty. If the slice is not empty, the
+ /// behavior is similar to [`ContextHandle::from_slice`].
+ pub(crate) fn from_slice_to_option(s: &[u8]) -> DpeResult<Option<Self>> {
+ if s.is_empty() {
+ Ok(None)
+ } else {
+ Ok(Some(Self::from_slice(s)?))
+ }
+ }
+}
+
+/// A message type with a smaller buffer. This saves memory when we're confident
+/// the contents will fit.
+pub(crate) type SmallMessage = SizedMessage<DPE_MAX_SMALL_MESSAGE_SIZE>;
+
+/// A Vec wrapper to represent a single encoded certificate.
+#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, ZeroizeOnDrop)]
+pub(crate) struct Certificate(pub(crate) Vec<u8, DPE_MAX_CERTIFICATE_SIZE>);
+
+/// A Vec wrapper to represent a certificate chain.
+#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
+pub(crate) struct CertificateChain(
+ pub(crate) Vec<Certificate, DPE_MAX_CERTIFICATES_PER_CHAIN>,
+);
+
+/// A usize wrapper to represent a LocalityId.
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
+pub(crate) struct LocalityId(pub(crate) usize);
+
+impl TryFrom<LocalityId> for u16 {
+ type Error = ErrCode;
+ fn try_from(value: LocalityId) -> DpeResult<Self> {
+ num_traits::FromPrimitive::from_usize(value.0).ok_or_else(|| {
+ error!("Invalid locality ID");
+ ErrCode::InternalError
+ })
+ }
+}
+
+/// A usize wrapper to represent a SessionId.
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
+pub(crate) struct SessionId(pub(crate) usize);
+
+impl TryFrom<SessionId> for u16 {
+ type Error = ErrCode;
+ fn try_from(value: SessionId) -> DpeResult<Self> {
+ num_traits::FromPrimitive::from_usize(value.0).ok_or_else(|| {
+ error!("Invalid session ID");
+ ErrCode::InternalError
+ })
+ }
+}
+
+/// Defines the possible initialization types and their associated data.
+#[derive(Clone, Debug, Eq, PartialEq, Hash, ZeroizeOnDrop)]
+pub(crate) enum InitType {
+ /// Used when a DPE context is initialized from a UDS using a seed provided
+ /// by the client.
+ Uds {
+ /// The UDS seed provided by the client.
+ external_uds_seed: Uds,
+ },
+ /// Used when a DPE context is initialized from a UDS using only values
+ /// available to the DPE but not available to the client.
+ InternalUds,
+ /// Used when a DPE context is initialized from a pair of CDIs provided by
+ /// the client.
+ Cdis {
+ /// The signing CDI value provided by the client.
+ cdi_sign: Cdi,
+ /// The sealing CDI value provided by the client.
+ cdi_seal: Cdi,
+ },
+ /// Used when a DPE context is initialized from CDI values available to the
+ /// DPE but not available to the client.
+ InternalCdis,
+}
+
+/// Defines the supported internal input types. The enum discriminants match the
+/// encoded CBOR values. When an internal input is indicated as part of a
+/// context derivation, the corresponding information is included in the CDI
+/// derivation and possibly an associated certificate.
+#[derive(
+ Clone, Copy, Debug, Eq, PartialEq, Hash, FromPrimitive, ToPrimitive,
+)]
+pub(crate) enum InternalInputType {
+ /// Associated with information the DPE has about its own identity. This
+ /// information is included in the context's certificate info.
+ DpeInfo = 1,
+ /// Associated with information the DPE has about its own DICE attestation
+ /// data. This information is included in the context's certificate info.
+ DpeDice = 2,
+ /// Associated with a value that can be rotated in some way. This value
+ /// remains internal to the DPE and is not included in certificate info.
+ RotationValue = 3,
+ /// Associated with a monotonic counter internal do the DPE. This value
+ /// remains internal to the DPE and is not included in certificate info.
+ MonotonicCounter = 4,
+}
+
+impl TryFrom<u32> for InternalInputType {
+ type Error = ErrCode;
+ fn try_from(value: u32) -> DpeResult<Self> {
+ num_traits::FromPrimitive::from_u32(value).ok_or_else(|| {
+ error!("Unknown internal input type");
+ ErrCode::InvalidArgument
+ })
+ }
+}
+
+/// A command selector type with discriminants that match the encoded CBOR
+/// values. See the DPE specification for details on each of the commands.
+#[derive(
+ Clone, Copy, Debug, Eq, PartialEq, Hash, FromPrimitive, ToPrimitive,
+)]
+pub(crate) enum CommandSelector {
+ /// The GetProfile command.
+ GetProfile = 1,
+ /// The OpenSession command.
+ OpenSession = 2,
+ /// The CloseSession command.
+ CloseSession = 3,
+ /// The SyncSession command.
+ SyncSession = 4,
+ /// The InitializeContext command.
+ InitializeContext = 7,
+ /// The DeriveContext command.
+ DeriveContext = 8,
+ /// The GetCertificateChain command.
+ GetCertificateChain = 16,
+ /// The CertifyKey command.
+ CertifyKey = 9,
+ /// The Sign command.
+ Sign = 10,
+ /// The Seal command.
+ Seal = 11,
+ /// The Unseal command.
+ Unseal = 12,
+ /// The DeriveSealingPublicKey command.
+ DeriveSealingPublicKey = 13,
+ /// The RotateContextHandle command.
+ RotateContextHandle = 14,
+ /// The DestroyContext command.
+ DestroyContext = 15,
+}
+
+impl TryFrom<u32> for CommandSelector {
+ type Error = ErrCode;
+ fn try_from(value: u32) -> DpeResult<Self> {
+ num_traits::FromPrimitive::from_u32(value).ok_or_else(|| {
+ error!("Unknown command id");
+ ErrCode::InvalidCommand
+ })
+ }
+}
+
+// Context initialization data is encoded as a CBOR map, these are the keys.
+#[derive(
+ Clone, Copy, Debug, Eq, PartialEq, Hash, FromPrimitive, ToPrimitive,
+)]
+enum InitTypeMapKey {
+ InitType = 1,
+ ExternalSeed = 2,
+ CdiSign = 3,
+ CdiSeal = 4,
+}
+
+impl TryFrom<u32> for InitTypeMapKey {
+ type Error = ErrCode;
+ fn try_from(value: u32) -> DpeResult<Self> {
+ num_traits::FromPrimitive::from_u32(value).ok_or_else(|| {
+ error!("Unknown initialization seed field");
+ ErrCode::InvalidArgument
+ })
+ }
+}
+
+/// Indicates the type of value encoded into an initialization seed.
+///
+/// See [`decode_init_seed`].
+#[derive(
+ Clone, Copy, Debug, Eq, PartialEq, Hash, FromPrimitive, ToPrimitive,
+)]
+pub(crate) enum InitTypeSelector {
+ /// The initialization value is a UDS seed.
+ Uds = 1,
+ /// The initialization value is a CDI.
+ Cdi = 2,
+}
+
+impl TryFrom<u32> for InitTypeSelector {
+ type Error = ErrCode;
+ fn try_from(value: u32) -> DpeResult<Self> {
+ num_traits::FromPrimitive::from_u32(value).ok_or_else(|| {
+ error!("Unknown initialization type");
+ ErrCode::InvalidArgument
+ })
+ }
+}
+
+/// Represents the mode value in DICE input. The discriminants match the CBOR
+/// encoded values. See the Open Profile for DICE specification for details.
+#[derive(
+ Clone, Copy, Debug, Eq, PartialEq, Hash, FromPrimitive, ToPrimitive,
+)]
+pub(crate) enum DiceInputMode {
+ /// The `Not Configured` mode.
+ NotInitialized = 0,
+ /// The `Normal` mode.
+ Normal = 1,
+ /// The `Debug` mode.
+ Debug = 2,
+ /// The `Recovery` mode (aka maintenance mode).
+ Recovery = 3,
+}
+
+impl TryFrom<u8> for DiceInputMode {
+ type Error = ErrCode;
+ fn try_from(value: u8) -> DpeResult<Self> {
+ num_traits::FromPrimitive::from_u8(value).ok_or_else(|| {
+ error!("Unknown mode value");
+ ErrCode::InvalidArgument
+ })
+ }
+}
+
+// The DICE input fields are encoded as a CBOR map, these are the keys.
+#[derive(
+ Clone, Copy, Debug, Eq, PartialEq, Hash, FromPrimitive, ToPrimitive,
+)]
+enum DiceInputMapKey {
+ VersionSlot = 1,
+ VersionValue = 2,
+ CodeHash = 3,
+ CodeDescriptor = 4,
+ ConfigType = 5,
+ ConfigValue = 6,
+ AuthorityHash = 7,
+ AuthorityDescriptor = 8,
+ Mode = 9,
+ Hidden = 10,
+}
+
+impl TryFrom<u32> for DiceInputMapKey {
+ type Error = ErrCode;
+ fn try_from(value: u32) -> DpeResult<Self> {
+ num_traits::FromPrimitive::from_u32(value).ok_or_else(|| {
+ error!("Unknown input map key");
+ ErrCode::InvalidArgument
+ })
+ }
+}
+
+/// Represents a config value as defined by the Open Profile for DICE.
+#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
+pub(crate) enum DiceInputConfig<'a> {
+ /// No config value provided by the client.
+ #[default]
+ EmptyConfig,
+ /// The inline 64-byte value provided by the client.
+ ConfigInlineValue(Hash),
+ /// The free-form configuration descriptor provided by the client.
+ ConfigDescriptor(&'a [u8]),
+}
+
+/// Represents a complete set of DICE input values as defined by the Open
+/// Profile for DICE.
+#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
+pub(crate) struct DiceInput<'a> {
+ /// The `Code` input value.
+ pub(crate) code_hash: Option<Hash>,
+ /// An optional code descriptor (not included in the CDI derivation).
+ pub(crate) code_descriptor: Option<&'a [u8]>,
+ /// The `Configuration Data` input value.
+ pub(crate) config: DiceInputConfig<'a>,
+ /// The `Authority Data` input value as a hash. One of this field or the
+ /// `authority_descriptor` field is required.
+ pub(crate) authority_hash: Option<Hash>,
+ /// The `Authority Data` input value as a descriptor. One of this field or
+ /// the `authority_hash` field is required.
+ pub(crate) authority_descriptor: Option<&'a [u8]>,
+ /// The `Mode Decision` input value.
+ pub(crate) mode: Option<DiceInputMode>,
+ /// The `Hidden Inputs` input value.
+ pub(crate) hidden: Option<Hash>,
+}
+
+/// Decodes a CBOR-encoded set of internal input selectors.
+pub(crate) fn decode_internal_inputs(
+ cbor: &[u8],
+) -> DpeResult<Vec<InternalInputType, DPE_MAX_INTERNAL_INPUTS>> {
+ debug!("decode_internal_inputs");
+ let mut values: Vec<InternalInputType, DPE_MAX_INTERNAL_INPUTS> =
+ Default::default();
+ if !cbor.is_empty() {
+ let mut decoder = Decoder::new(cbor);
+ let size = decoder.array()?.ok_or_else(|| {
+ error!("Indefinite arrays not supported");
+ ErrCode::InvalidArgument
+ })?;
+ for _ in 0..size {
+ values
+ .push(InternalInputType::try_from(decoder.u32()?)?)
+ .map_err(|_| ErrCode::InternalError)?;
+ }
+ }
+ Ok(values)
+}
+
+/// Decodes a CBOR-encoded locality selector.
+pub(crate) fn decode_locality(
+ cbor: &[u8],
+ current_locality: LocalityId,
+) -> DpeResult<LocalityId> {
+ debug!("decode_locality");
+ if cbor.is_empty() {
+ Ok(current_locality)
+ } else {
+ Ok(LocalityId(Decoder::new(cbor).u16()?.into()))
+ }
+}
+
+/// Decodes the `seed` argument of the `InitializeContext` command.
+pub(crate) fn decode_init_seed(seed: &[u8]) -> DpeResult<InitType> {
+ let mut init_type_selector: Option<InitTypeSelector> = None;
+ let mut external_uds_seed: Option<Uds> = None;
+ let mut cdi_sign: Option<Cdi> = None;
+ let mut cdi_seal: Option<Cdi> = None;
+
+ let mut decoder = Decoder::new(seed);
+ let num_pairs = decoder.map()?.unwrap_or(0);
+ for _ in 0..num_pairs {
+ let map_key: InitTypeMapKey = decoder.u32()?.try_into()?;
+ match map_key {
+ InitTypeMapKey::InitType => {
+ init_type_selector = Some(decoder.u32()?.try_into()?);
+ }
+ InitTypeMapKey::ExternalSeed => {
+ external_uds_seed = Some(Uds::from_slice(decoder.bytes()?)?);
+ }
+ InitTypeMapKey::CdiSign => {
+ cdi_sign = Some(Cdi::from_slice(decoder.bytes()?)?);
+ }
+ InitTypeMapKey::CdiSeal => {
+ cdi_seal = Some(Cdi::from_slice(decoder.bytes()?)?);
+ }
+ }
+ }
+
+ let init_type_selector = match init_type_selector {
+ None => {
+ error!("No initialization type selector");
+ return Err(ErrCode::InvalidArgument);
+ }
+ Some(value) => value,
+ };
+
+ let init_type = match init_type_selector {
+ InitTypeSelector::Uds => {
+ if let Some(external_uds_seed) = external_uds_seed {
+ InitType::Uds { external_uds_seed }
+ } else {
+ InitType::InternalUds
+ }
+ }
+ InitTypeSelector::Cdi => {
+ if let Some(cdi_sign) = cdi_sign {
+ let cdi_seal = cdi_seal.unwrap_or_else(|| cdi_sign.clone());
+ InitType::Cdis { cdi_sign, cdi_seal }
+ } else {
+ InitType::InternalCdis
+ }
+ }
+ };
+ Ok(init_type)
+}
+
+/// Decodes the `input-data` argument of the `DeriveContext` command.
+pub(crate) fn decode_dice_input<'a>(
+ encoded_dice_input: &'a [u8],
+) -> DpeResult<(Option<(usize, u64)>, DiceInput<'a>)> {
+ debug!("decode_dice_input");
+ let mut dice_input: DiceInput<'a> = Default::default();
+ let mut decoder = Decoder::new(encoded_dice_input);
+ let mut tmp_version_slot: Option<usize> = None;
+ let mut tmp_version_value: Option<u64> = None;
+ let mut tmp_config_type: Option<u8> = None;
+ let mut tmp_config_value: Option<&[u8]> = None;
+ let num_pairs = decoder.map()?.unwrap_or(0);
+ for _ in 0..num_pairs {
+ let map_key: DiceInputMapKey = decoder.u32()?.try_into()?;
+ match map_key {
+ DiceInputMapKey::VersionSlot => {
+ let slot: usize = decoder.u8()?.into();
+ if slot >= DPE_MAX_VERSION_SLOTS {
+ error!("Invalid version slot");
+ return Err(ErrCode::InvalidArgument);
+ }
+ tmp_version_slot = Some(slot);
+ }
+ DiceInputMapKey::VersionValue => {
+ tmp_version_value = Some(decoder.u64()?);
+ }
+ DiceInputMapKey::CodeHash => {
+ dice_input.code_hash =
+ Some(Hash::from_slice(decoder.bytes()?)?);
+ }
+ DiceInputMapKey::CodeDescriptor => {
+ dice_input.code_descriptor = Some(decoder.bytes()?);
+ }
+ DiceInputMapKey::ConfigType => {
+ tmp_config_type = Some(decoder.u8()?);
+ }
+ DiceInputMapKey::ConfigValue => {
+ tmp_config_value = Some(decoder.bytes()?);
+ }
+ DiceInputMapKey::AuthorityHash => {
+ dice_input.authority_hash =
+ Some(Hash::from_slice(decoder.bytes()?)?);
+ }
+ DiceInputMapKey::AuthorityDescriptor => {
+ dice_input.authority_descriptor = Some(decoder.bytes()?);
+ }
+ DiceInputMapKey::Mode => {
+ dice_input.mode = Some(decoder.u8()?.try_into()?);
+ }
+ DiceInputMapKey::Hidden => {
+ dice_input.hidden = Some(Hash::from_slice(decoder.bytes()?)?);
+ }
+ };
+ }
+ let version_info = match (tmp_version_slot, tmp_version_value) {
+ (None, None) => None,
+ (Some(slot), Some(value)) => Some((slot, value)),
+ _ => {
+ error!("Incomplete version info");
+ return Err(ErrCode::InvalidArgument);
+ }
+ };
+ dice_input.config = match (tmp_config_type, tmp_config_value) {
+ (Some(0), Some(value)) => {
+ DiceInputConfig::ConfigInlineValue(Hash::from_slice(value)?)
+ }
+ (Some(1), Some(value)) => DiceInputConfig::ConfigDescriptor(value),
+ _ => {
+ error!("Incomplete config info");
+ return Err(ErrCode::InvalidArgument);
+ }
+ };
+ // Ensure mandatory fields are populated.
+ if dice_input.code_hash.is_none()
+ || dice_input.authority_hash.is_none()
+ || dice_input.mode.is_none()
+ {
+ error!("Missing mandatory input fields");
+ return Err(ErrCode::InvalidArgument);
+ }
+ Ok((version_info, dice_input))
+}
+
+/// Decodes the `unseal-policy` argument of the `Unseal` command.
+pub(crate) fn decode_unseal_policy(
+ encoded_policy: &[u8],
+) -> DpeResult<[u64; DPE_MAX_VERSION_SLOTS]> {
+ let mut target_versions = [0; DPE_MAX_VERSION_SLOTS];
+ let mut decoder = Decoder::new(encoded_policy);
+ let map_size = decoder.map()?.ok_or_else(|| {
+ error!("Indefinite CBOR maps not supported");
+ ErrCode::InvalidArgument
+ })?;
+ for _ in 0..map_size {
+ let i = decoder.u16()? as usize;
+ if i >= DPE_MAX_VERSION_SLOTS {
+ error!("Invalid version slot in unseal policy");
+ return Err(ErrCode::InvalidArgument);
+ }
+ *target_versions.get_mut(i).ok_or(ErrCode::InternalError)? =
+ decoder.u64()?;
+ }
+ Ok(target_versions)
+}
+
+/// Encodes a DPE command response indicating an error occurred.
+///
+/// The response will contain the given `err_code` and written to the given
+/// `response` buffer. Any existing data in the buffer is cleared.
+///
+/// # Errors
+///
+/// This function is infallible given the precondition that a message buffer is
+/// always large enough to hold an error response. It uses `unwrap()` but these
+/// will not panic as long as the precondition holds.
+#[allow(clippy::unwrap_used)]
+pub(crate) fn create_error_response(err_code: ErrCode, response: &mut Message) {
+ fn encode_error_response(
+ err_code: ErrCode,
+ response: &mut Message,
+ ) -> DpeResult<()> {
+ let _ = cbor_encoder_from_message(response)
+ .array(MESSAGE_ARRAY_SIZE)?
+ .u32(err_code as u32)?
+ .bytes(&[0])?;
+ Ok(())
+ }
+ response.clear();
+ encode_error_response(err_code, response).unwrap();
+}
+
+/// Like [`create_error_response`] but includes a plaintext session header.
+///
+/// # Errors
+///
+/// This function is infallible given the precondition that a message buffer is
+/// always large enough to hold an error response. It uses `unwrap()` but these
+/// will not panic as long as the precondition holds.
+#[allow(clippy::unwrap_used)]
+pub(crate) fn create_plaintext_session_error_response(
+ err_code: ErrCode,
+ response: &mut Message,
+) {
+ fn encode_session_prefix(response_size: usize) -> DpeResult<SmallMessage> {
+ let mut prefix = SmallMessage::new();
+ let _ = cbor_encoder_from_message(&mut prefix)
+ .array(MESSAGE_ARRAY_SIZE)?
+ .u16(0)?;
+ encode_bytes_prefix(&mut prefix, response_size)?;
+ Ok(prefix)
+ }
+ create_error_response(err_code, response);
+ let prefix = encode_session_prefix(response.len()).unwrap();
+ response.insert_prefix(prefix.as_slice()).unwrap();
+}
+
+/// Encodes the given `args` into the `encoded_args` buffer.
+///
+/// Any existing data in the buffer is cleared.
+pub(crate) fn encode_args(
+ args: &ArgMap,
+ encoded_args: &mut Message,
+) -> DpeResult<()> {
+ encoded_args.clear();
+ let mut encoder = cbor_encoder_from_message(encoded_args);
+ let _ = encoder.map(args.len().try_into()?)?;
+ for (arg_id, arg_value) in args {
+ let _ = encoder.u32(*arg_id)?;
+ match arg_value {
+ ArgValue::Int(value) => {
+ let _ = encoder.u64(*value)?;
+ }
+ ArgValue::Bool(value) => {
+ let _ = encoder.bool(*value)?;
+ }
+ _ => {
+ let _ = encoder.bytes(arg_value.try_into_slice()?)?;
+ }
+ }
+ }
+ Ok(())
+}
+
+fn decode_args_internal<'a>(
+ encoded_args: &'a [u8],
+ arg_types: &[(ArgId, ArgTypeSelector)],
+) -> DpeResult<ArgMap<'a>> {
+ debug!("Decoding arguments of type: {:?}", arg_types);
+ let mut decoder = Decoder::new(encoded_args);
+ let num_pairs = match decoder.map() {
+ Err(_) => {
+ error!("Arguments not encoded as CBOR map");
+ return Err(ErrCode::InvalidCommand);
+ }
+ Ok(None) => {
+ error!("Indefinite argument maps not supported");
+ return Err(ErrCode::InvalidCommand);
+ }
+ Ok(Some(num)) => num,
+ };
+ let mut arg_types_map: ArgTypeMap = Default::default();
+ for (id, value) in arg_types {
+ let _ = arg_types_map
+ .insert(*id, *value)
+ .map_err(|_| ErrCode::OutOfMemory)?;
+ }
+ let mut args: ArgMap = Default::default();
+ for _ in 0..num_pairs {
+ let arg_id = decoder.u32()?;
+ match arg_types_map.get(&arg_id).cloned().unwrap_or_default() {
+ ArgTypeSelector::Unknown => {
+ error!("Unknown argument id");
+ return Err(ErrCode::InvalidArgument);
+ }
+ ArgTypeSelector::Bytes => {
+ let _ = args
+ .insert(arg_id, ArgValue::from_slice(decoder.bytes()?))
+ .map_err(|_| ErrCode::OutOfMemory)?;
+ }
+ ArgTypeSelector::Int => {
+ let _ = args
+ .insert(arg_id, ArgValue::from_u64(decoder.u64()?))
+ .map_err(|_| ErrCode::OutOfMemory)?;
+ }
+ ArgTypeSelector::Bool => {
+ let _ = args
+ .insert(arg_id, ArgValue::from_bool(decoder.bool()?))
+ .map_err(|_| ErrCode::OutOfMemory)?;
+ }
+ ArgTypeSelector::Other => {
+ let start = decoder.position();
+ decoder.skip()?;
+ let end = decoder.position();
+ let _ = args
+ .insert(
+ arg_id,
+ ArgValue::from_slice(
+ encoded_args
+ .get(start..end)
+ .ok_or(ErrCode::InvalidCommand)?,
+ ),
+ )
+ .map_err(|_| ErrCode::OutOfMemory)?;
+ }
+ }
+ }
+ Ok(args)
+}
+
+/// Decodes CBOR-encoded `encoded_args` according to the given `arg_types`.
+///
+/// Arguments not present will be assigned the provided `arg_defaults`.
+///
+/// # Errors
+///
+/// * Returns `InvalidCommand` if the arguments cannot be decoded.
+/// * Returns `InvalidArgument` if an argument encoding is not supported.
+/// * Returns `OutOfMemory` if an argument is too large.
+/// * Returns `InternalError` if a default value is missing.
+pub(crate) fn decode_args<'a>(
+ encoded_args: &'a [u8],
+ arg_types: &[(ArgId, ArgTypeSelector)],
+ arg_defaults: &[(ArgId, ArgValue<'a>)],
+) -> DpeResult<ArgMap<'a>> {
+ let mut args = decode_args_internal(encoded_args, arg_types)?;
+ for (arg_id, default_value) in arg_defaults {
+ if !args.contains_key(arg_id) {
+ let _ = args
+ .insert(*arg_id, default_value.clone())
+ .map_err(|_| ErrCode::OutOfMemory)?;
+ }
+ }
+ for (arg_id, arg_type) in arg_types {
+ if !args.contains_key(arg_id) {
+ if *arg_type == ArgTypeSelector::Bytes
+ || *arg_type == ArgTypeSelector::Other
+ {
+ // The default value for any byte array is the empty array.
+ let _ = args
+ .insert(*arg_id, ArgValue::from_slice(&[]))
+ .map_err(|_| ErrCode::OutOfMemory)?;
+ } else {
+ error!("No default value found for argument {}", arg_id);
+ return Err(ErrCode::InternalError);
+ }
+ }
+ }
+ Ok(args)
+}
+
+/// Encodes the `exported-cdi` output argument of the `DeriveContext` command.
+pub(crate) fn encode_cdis_for_export(
+ cdi_sign: &Cdi,
+ cdi_seal: &Cdi,
+ encoded_cdis: &mut SmallMessage,
+) -> DpeResult<()> {
+ let _ = cbor_encoder_from_message(encoded_cdis)
+ .array(2)?
+ .bytes(cdi_sign.as_slice())?
+ .bytes(cdi_seal.as_slice())?;
+ Ok(())
+}
+
+/// Encodes a certificate chain for the `GetCertificateChain` command.
+pub(crate) fn encode_certificate_chain(
+ certificates: &CertificateChain,
+ encoded_certificate_chain: &mut Message,
+) -> DpeResult<()> {
+ let mut encoder = cbor_encoder_from_message(encoded_certificate_chain);
+ let _ = encoder.array(certificates.0.len() as u64)?;
+ for certificate in &certificates.0 {
+ let _ = encoder.bytes(certificate.0.as_slice())?;
+ }
+ Ok(())
+}
+
+/// Decodes and removes a session message header.
+///
+/// The session ID is returned and the remainder of the message remains in the
+/// message buffer.
+pub(crate) fn decode_and_remove_session_message_header(
+ message: &mut Message,
+) -> DpeResult<SessionId> {
+ let mut decoder = cbor_decoder_from_message(message);
+ // We're expecting a CBOR array with two elements, a session ID and the
+ // message content.
+ if !decoder.array().is_ok_and(|len| len == Some(MESSAGE_ARRAY_SIZE)) {
+ error!("Failed to decode session message");
+ return Err(ErrCode::InvalidCommand);
+ }
+ let session_id =
+ SessionId(decoder.u16().map_err(|_| ErrCode::InvalidCommand)?.into());
+
+ let remainder_position = decoder.decode_bytes_prefix()?;
+ message.remove_prefix(remainder_position)?;
+ Ok(session_id)
+}
+
+/// Encodes and inserts a session message header containing `session_id`.
+pub(crate) fn encode_and_insert_session_message_header(
+ session_id: SessionId,
+ message: &mut Message,
+) -> DpeResult<()> {
+ let mut prefix = SmallMessage::new();
+ let _ = cbor_encoder_from_message(&mut prefix)
+ .array(MESSAGE_ARRAY_SIZE)?
+ .u16(session_id.try_into()?)?;
+ encode_bytes_prefix(&mut prefix, message.len())?;
+ message.insert_prefix(prefix.as_slice())?;
+ Ok(())
+}
+
+/// Decodes and removes a command message header.
+///
+/// The command ID is returned and the remainder of the message remains in the
+/// message buffer.
+pub(crate) fn decode_and_remove_command_header(
+ message: &mut Message,
+) -> DpeResult<CommandSelector> {
+ let mut decoder = cbor_decoder_from_message(message);
+ // We're expecting a CBOR array with two elements, a command ID and the
+ // message content.
+ let command_id: CommandSelector = decoder
+ .array()
+ .ok()
+ .and_then(|len| match len {
+ Some(MESSAGE_ARRAY_SIZE) => decoder.u32().ok(),
+ _ => None,
+ })
+ .ok_or_else(|| {
+ error!("Failed to decode command message");
+ ErrCode::InvalidCommand
+ })?
+ .try_into()
+ .map_err(|_| {
+ error!("Unknown command id");
+ ErrCode::InvalidCommand
+ })?;
+ let remainder_position = decoder.decode_bytes_prefix()?;
+ message.remove_prefix(remainder_position)?;
+ Ok(command_id)
+}
+
+/// Encodes and inserts a success response message header.
+///
+/// If an error occurs use [`create_error_response`] instead.
+pub(crate) fn encode_and_insert_response_header(
+ message: &mut Message,
+) -> DpeResult<()> {
+ let mut prefix = SmallMessage::new();
+ let _ = cbor_encoder_from_message(&mut prefix)
+ .array(MESSAGE_ARRAY_SIZE)?
+ .u32(0)?;
+ encode_bytes_prefix(&mut prefix, message.len())?;
+ message.insert_prefix(prefix.as_slice())?;
+ Ok(())
+}
+
+/// Encodes a session ID into a handshake payload.
+pub(crate) fn encode_handshake_payload(
+ session_id: SessionId,
+) -> DpeResult<HandshakePayload> {
+ let mut payload = HandshakePayload::new();
+ let _ = cbor_encoder_from_message(&mut payload)
+ .u16(session_id.0.try_into()?)?;
+ Ok(payload)
+}
+
+/// Encodes a profile descriptor containing only the profile name.
+pub(crate) fn encode_profile_descriptor_from_name(
+ name: &str,
+ message: &mut Message,
+) -> DpeResult<()> {
+ const PROFILE_NAME_ATTRIBUTE: u32 = 1;
+ let _ = cbor_encoder_from_message(message)
+ .map(1)?
+ .u32(PROFILE_NAME_ATTRIBUTE)?
+ .str(name)?;
+ Ok(())
+}
+
+#[cfg(test)]
+pub(crate) mod test {
+ use super::*;
+ use crate::cbor::tokenize_cbor_for_debug;
+
+ /// Encodes and logs input args.
+ pub(crate) fn encode_args_for_testing(
+ args: &ArgMap,
+ encoded_args: &mut Message,
+ ) -> DpeResult<()> {
+ encode_args(args, encoded_args)?;
+ debug!(
+ "Raw input args: {:?}",
+ tokenize_cbor_for_debug(encoded_args.as_slice())
+ );
+ Ok(())
+ }
+
+ /// Encodes and inserts a command message header containing `command_id`.
+ ///
+ /// This function is for testing and panics on failure.
+ #[allow(clippy::unwrap_used)]
+ pub(crate) fn encode_and_insert_command_header_for_testing(
+ command_id: CommandSelector,
+ message: &mut Message,
+ ) {
+ let mut prefix = SmallMessage::new();
+ let _ = cbor_encoder_from_message(&mut prefix)
+ .array(MESSAGE_ARRAY_SIZE)
+ .unwrap()
+ .u32(command_id as u32)
+ .unwrap();
+ encode_bytes_prefix(&mut prefix, message.len()).unwrap();
+ message.insert_prefix(prefix.as_slice()).unwrap();
+ }
+
+ /// Decodes a command response.
+ ///
+ /// This function is for testing and panics on failure to decode.
+ #[allow(clippy::unwrap_used)]
+ pub(crate) fn decode_response_for_testing<'a>(
+ message: &'a Message,
+ arg_types: &[(ArgId, ArgTypeSelector)],
+ ) -> DpeResult<ArgMap<'a>> {
+ debug!(
+ "Raw response: {:?}",
+ tokenize_cbor_for_debug(message.as_slice())
+ );
+ let mut decoder = cbor_decoder_from_message(message);
+ assert_eq!(decoder.array().unwrap(), Some(MESSAGE_ARRAY_SIZE));
+ let err_code = decoder.u32().unwrap();
+ if err_code != 0 {
+ return Err(err_code.into());
+ }
+ assert_eq!(decoder.datatype().unwrap(), minicbor::data::Type::Bytes);
+ debug!(
+ "Raw output args: {:?}",
+ tokenize_cbor_for_debug(decoder.probe().bytes().unwrap())
+ );
+ Ok(decode_args_internal(decoder.bytes().unwrap(), arg_types).unwrap())
+ }
+
+ /// Decodes an error response.
+ ///
+ /// This function is for testing and panics on failure to decode.
+ #[allow(clippy::unwrap_used)]
+ pub(crate) fn decode_error_response_for_testing(
+ message: &[u8],
+ ) -> DpeResult<()> {
+ let mut decoder = Decoder::new(message);
+ assert_eq!(decoder.array().unwrap(), Some(MESSAGE_ARRAY_SIZE));
+ let err_code = decoder.u32().unwrap();
+ assert_ne!(err_code, 0);
+ Err(err_code.into())
+ }
+
+ /// Decodes a certificate chain.
+ ///
+ /// This function is for testing and panics on failure to decode.
+ #[allow(clippy::unwrap_used)]
+ pub(crate) fn decode_certificate_chain_for_testing(
+ encoded: &Message,
+ ) -> CertificateChain {
+ let mut decoder = cbor_decoder_from_message(encoded);
+ let num_certs = decoder.array().unwrap().unwrap();
+ let mut chain: CertificateChain = Default::default();
+ for _ in 0..num_certs {
+ chain
+ .0
+ .push(Certificate(
+ Vec::from_slice(decoder.bytes().unwrap()).unwrap(),
+ ))
+ .unwrap();
+ }
+ chain
+ }
+
+ /// Encodes a `seed` argument for the `InitializeContext` command.
+ ///
+ /// This function is for testing and panics on failure to encode.
+ #[allow(unused_results, clippy::unwrap_used)]
+ pub(crate) fn encode_init_seed_for_testing(
+ init_type: Option<InitTypeSelector>,
+ external_uds_seed: Option<&[u8]>,
+ cdi_sign: Option<&[u8]>,
+ cdi_seal: Option<&[u8]>,
+ ) -> SmallMessage {
+ let mut map_size = 0;
+ if init_type.is_some() {
+ map_size += 1;
+ }
+ if external_uds_seed.is_some() {
+ map_size += 1;
+ }
+ if cdi_sign.is_some() {
+ map_size += 1;
+ }
+ if cdi_seal.is_some() {
+ map_size += 1;
+ }
+
+ let mut encoded_seed = SmallMessage::new();
+ let mut encoder = cbor_encoder_from_message(&mut encoded_seed);
+ encoder.map(map_size).unwrap();
+ if let Some(init_type) = init_type {
+ encoder.u8(InitTypeMapKey::InitType as u8).unwrap();
+ encoder.u8(init_type as u8).unwrap();
+ }
+ if let Some(seed) = external_uds_seed {
+ encoder.u8(InitTypeMapKey::ExternalSeed as u8).unwrap();
+ encoder.bytes(seed).unwrap();
+ }
+ if let Some(cdi) = cdi_sign {
+ encoder.u8(InitTypeMapKey::CdiSign as u8).unwrap();
+ encoder.bytes(cdi).unwrap();
+ }
+ if let Some(cdi) = cdi_seal {
+ encoder.u8(InitTypeMapKey::CdiSeal as u8).unwrap();
+ encoder.bytes(cdi).unwrap();
+ }
+ encoded_seed
+ }
+
+ /// Encodes a set of [`InternalInputType`]s for the `DeriveContext` command.
+ ///
+ /// This function is for testing and panics on failure to encode.
+ #[allow(unused_results, clippy::unwrap_used)]
+ pub(crate) fn encode_internal_inputs_for_testing(
+ inputs: &[InternalInputType],
+ ) -> SmallMessage {
+ let mut encoded_inputs = SmallMessage::new();
+ let mut encoder = cbor_encoder_from_message(&mut encoded_inputs);
+ encoder.array(inputs.len() as u64).unwrap();
+ for input in inputs {
+ encoder.u32(*input as u32).unwrap();
+ }
+ encoded_inputs
+ }
+
+ /// Encodes a [`LocalityId`].
+ ///
+ /// This function is for testing and panics on failure to encode.
+ #[allow(unused_results, clippy::unwrap_used)]
+ pub(crate) fn encode_locality_for_testing(
+ locality_id: LocalityId,
+ ) -> SmallMessage {
+ let mut encoded_locality = SmallMessage::new();
+ cbor_encoder_from_message(&mut encoded_locality)
+ .u16(locality_id.try_into().unwrap())
+ .unwrap();
+ encoded_locality
+ }
+
+ /// Encodes an `input` argument for the `DeriveContext` command.
+ ///
+ /// The version info indicates a slot and value to populate when processing
+ /// this input. This function is for testing and panics on failure to
+ /// encode.
+ pub(crate) fn encode_dice_input_for_testing(
+ version_info: Option<(usize, u64)>,
+ dice_input: &DiceInput,
+ ) -> Message {
+ encode_dice_input_for_testing_with_errors(
+ version_info,
+ dice_input,
+ false,
+ false,
+ false,
+ false,
+ )
+ }
+
+ #[allow(unused_results, clippy::unwrap_used)]
+ fn encode_dice_input_for_testing_with_errors(
+ version_info: Option<(usize, u64)>,
+ dice_input: &DiceInput,
+ omit_version_slot: bool,
+ omit_version_value: bool,
+ omit_config_type: bool,
+ omit_config_value: bool,
+ ) -> Message {
+ let mut cbor = Message::new();
+ let mut encoder = cbor_encoder_from_message(&mut cbor);
+ let mut map_size: u64 = 2;
+ if version_info.is_some() {
+ map_size += 2;
+ }
+ if dice_input.code_hash.is_some() {
+ map_size += 1;
+ }
+ if dice_input.code_descriptor.is_some() {
+ map_size += 1;
+ }
+ if dice_input.authority_hash.is_some() {
+ map_size += 1;
+ }
+ if dice_input.authority_descriptor.is_some() {
+ map_size += 1;
+ }
+ if dice_input.mode.is_some() {
+ map_size += 1;
+ }
+ if dice_input.hidden.is_some() {
+ map_size += 1;
+ }
+ encoder.map(map_size).unwrap();
+ if let Some(version_info) = version_info {
+ if !omit_version_slot {
+ encoder.u32(DiceInputMapKey::VersionSlot as u32).unwrap();
+ encoder.u8(version_info.0 as u8).unwrap();
+ }
+ if !omit_version_value {
+ encoder.u32(DiceInputMapKey::VersionValue as u32).unwrap();
+ encoder.u64(version_info.1).unwrap();
+ }
+ }
+ if let Some(hash) = &dice_input.code_hash {
+ encoder.u32(DiceInputMapKey::CodeHash as u32).unwrap();
+ encoder.bytes(hash.as_slice()).unwrap();
+ }
+ if let Some(descriptor) = &dice_input.code_descriptor {
+ encoder.u32(DiceInputMapKey::CodeDescriptor as u32).unwrap();
+ encoder.bytes(descriptor).unwrap();
+ }
+ if !omit_config_type {
+ encoder.u32(DiceInputMapKey::ConfigType as u32).unwrap();
+ encoder
+ .u8(match &dice_input.config {
+ DiceInputConfig::EmptyConfig => 0,
+ DiceInputConfig::ConfigInlineValue(_) => 0,
+ DiceInputConfig::ConfigDescriptor(_) => 1,
+ })
+ .unwrap();
+ }
+ if !omit_config_value {
+ encoder.u32(DiceInputMapKey::ConfigValue as u32).unwrap();
+ encoder
+ .bytes(match &dice_input.config {
+ DiceInputConfig::EmptyConfig => &[0; 64],
+ DiceInputConfig::ConfigInlineValue(value) => {
+ value.as_slice()
+ }
+ DiceInputConfig::ConfigDescriptor(value) => value,
+ })
+ .unwrap();
+ }
+ if let Some(hash) = &dice_input.authority_hash {
+ encoder.u32(DiceInputMapKey::AuthorityHash as u32).unwrap();
+ encoder.bytes(hash.as_slice()).unwrap();
+ }
+ if let Some(descriptor) = &dice_input.authority_descriptor {
+ encoder.u32(DiceInputMapKey::AuthorityDescriptor as u32).unwrap();
+ encoder.bytes(descriptor).unwrap();
+ }
+ if let Some(mode) = &dice_input.mode {
+ encoder.u32(DiceInputMapKey::Mode as u32).unwrap();
+ encoder.u8(*mode as u8).unwrap();
+ }
+ if let Some(hidden) = &dice_input.hidden {
+ encoder.u32(DiceInputMapKey::Hidden as u32).unwrap();
+ encoder.bytes(hidden.as_slice()).unwrap();
+ }
+ cbor
+ }
+
+ /// Encodes an unseal policy argument for the sealing commands.
+ #[allow(clippy::unwrap_used)]
+ pub(crate) fn encode_unseal_policy_for_testing(
+ versions: &[u64; DPE_MAX_VERSION_SLOTS],
+ ) -> SmallMessage {
+ let mut encoded_policy = SmallMessage::new();
+ let mut encoder = cbor_encoder_from_message(&mut encoded_policy);
+ let count = versions.iter().filter(|&&v| v != 0).count();
+ let _ = encoder.map(count as u64).unwrap();
+ for (slot, &value) in
+ versions.iter().enumerate().take(DPE_MAX_VERSION_SLOTS)
+ {
+ if value != 0 {
+ let _ = encoder.u16(slot as u16).unwrap();
+ let _ = encoder.u64(value).unwrap();
+ }
+ }
+ encoded_policy
+ }
+
+ fn test_init() {
+ let _ = env_logger::builder().is_test(true).try_init();
+ }
+
+ #[test]
+ fn context_handle_decode() {
+ test_init();
+ assert_eq!(None, ContextHandle::from_slice_to_option(&[]).unwrap());
+ assert_ne!(
+ None,
+ ContextHandle::from_slice_to_option(&[0; DPE_HANDLE_SIZE]).unwrap()
+ );
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ ContextHandle::from_slice_to_option(&[0; DPE_HANDLE_SIZE - 1])
+ .unwrap_err()
+ );
+ }
+
+ #[test]
+ fn internal_input_decode() {
+ test_init();
+ assert_eq!(InternalInputType::DpeInfo, 1u32.try_into().unwrap());
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ <InternalInputType as TryFrom<u32>>::try_from(0).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn command_selector_decode() {
+ test_init();
+ assert_eq!(CommandSelector::GetProfile, 1u32.try_into().unwrap());
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ <CommandSelector as TryFrom<u32>>::try_from(0).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn init_type_map_key_decode() {
+ test_init();
+ assert_eq!(InitTypeMapKey::InitType, 1u32.try_into().unwrap());
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ <InitTypeMapKey as TryFrom<u32>>::try_from(0).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn init_type_selector_decode() {
+ test_init();
+ assert_eq!(InitTypeSelector::Uds, 1u32.try_into().unwrap());
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ <InitTypeSelector as TryFrom<u32>>::try_from(0).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn dice_input_mode_decode() {
+ test_init();
+ assert_eq!(DiceInputMode::Normal, 1u8.try_into().unwrap());
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ <DiceInputMode as TryFrom<u8>>::try_from(10).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn dice_input_map_key_decode() {
+ test_init();
+ assert_eq!(DiceInputMapKey::VersionSlot, 1u32.try_into().unwrap());
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ <DiceInputMapKey as TryFrom<u32>>::try_from(0).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn internal_inputs_decode() {
+ test_init();
+ let before =
+ [InternalInputType::DpeInfo, InternalInputType::MonotonicCounter];
+ let after = decode_internal_inputs(
+ encode_internal_inputs_for_testing(&before).as_slice(),
+ )
+ .unwrap();
+ assert_eq!(before, after);
+ }
+
+ #[test]
+ fn locality_decode() {
+ test_init();
+ let default = LocalityId(0);
+ let before = LocalityId(0xFFFF);
+ let after = decode_locality(
+ encode_locality_for_testing(before).as_slice(),
+ default,
+ )
+ .unwrap();
+ assert_eq!(before, after);
+ assert_eq!(default, decode_locality(&[], default).unwrap());
+ }
+
+ #[test]
+ fn init_seed_decode() {
+ test_init();
+ let uds_value = Uds([0; DICE_UDS_SIZE]);
+ let cdi_sign = Cdi([1; DICE_CDI_SIZE]);
+ let cdi_seal = Cdi([2; DICE_CDI_SIZE]);
+ let invalid_seed = encode_init_seed_for_testing(None, None, None, None);
+ let uds_internal_init = InitType::InternalUds;
+ let uds_internal_seed = encode_init_seed_for_testing(
+ Some(InitTypeSelector::Uds),
+ None,
+ None,
+ None,
+ );
+ let uds_external_init =
+ InitType::Uds { external_uds_seed: uds_value.clone() };
+ let uds_external_seed = encode_init_seed_for_testing(
+ Some(InitTypeSelector::Uds),
+ Some(uds_value.as_slice()),
+ None,
+ None,
+ );
+ let cdi_internal_init = InitType::InternalCdis;
+ let cdi_internal_seed = encode_init_seed_for_testing(
+ Some(InitTypeSelector::Cdi),
+ None,
+ None,
+ None,
+ );
+ let cdi_external_init = InitType::Cdis {
+ cdi_sign: cdi_sign.clone(),
+ cdi_seal: cdi_seal.clone(),
+ };
+ let cdi_external_seed = encode_init_seed_for_testing(
+ Some(InitTypeSelector::Cdi),
+ None,
+ Some(cdi_sign.as_slice()),
+ Some(cdi_seal.as_slice()),
+ );
+ let cdi_external_sign_init = InitType::Cdis {
+ cdi_sign: cdi_sign.clone(),
+ cdi_seal: cdi_sign.clone(),
+ };
+ let cdi_external_sign_seed = encode_init_seed_for_testing(
+ Some(InitTypeSelector::Cdi),
+ None,
+ Some(cdi_sign.as_slice()),
+ None,
+ );
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ decode_init_seed(invalid_seed.as_slice()).unwrap_err()
+ );
+ assert_eq!(
+ uds_internal_init,
+ decode_init_seed(uds_internal_seed.as_slice()).unwrap()
+ );
+ assert_eq!(
+ uds_external_init,
+ decode_init_seed(uds_external_seed.as_slice()).unwrap()
+ );
+ assert_eq!(
+ cdi_internal_init,
+ decode_init_seed(cdi_internal_seed.as_slice()).unwrap()
+ );
+ assert_eq!(
+ cdi_external_init,
+ decode_init_seed(cdi_external_seed.as_slice()).unwrap()
+ );
+ assert_eq!(
+ cdi_external_sign_init,
+ decode_init_seed(cdi_external_sign_seed.as_slice()).unwrap()
+ );
+ }
+
+ #[test]
+ fn dice_input_decode() {
+ test_init();
+ let version_info = (0, 1);
+ let invalid_version_info = (DPE_MAX_VERSION_SLOTS, 2);
+ let hash_value = Hash::from_array(&[0; HASH_SIZE]);
+ let descriptor_value = Message::new();
+ let empty_dice_input: DiceInput = Default::default();
+ let minimal_dice_input = DiceInput {
+ code_hash: Some(hash_value.clone()),
+ code_descriptor: None,
+ config: DiceInputConfig::ConfigInlineValue(hash_value.clone()),
+ authority_hash: Some(hash_value.clone()),
+ authority_descriptor: None,
+ mode: Some(DiceInputMode::Normal),
+ hidden: None,
+ };
+ let full_dice_input = DiceInput {
+ code_hash: Some(hash_value.clone()),
+ code_descriptor: Some(descriptor_value.as_slice()),
+ config: DiceInputConfig::ConfigDescriptor(
+ descriptor_value.as_slice(),
+ ),
+ authority_hash: Some(hash_value.clone()),
+ authority_descriptor: Some(descriptor_value.as_slice()),
+ mode: Some(DiceInputMode::Normal),
+ hidden: Some(hash_value.clone()),
+ };
+
+ let encoded_dice_input =
+ encode_dice_input_for_testing(None, &empty_dice_input);
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ decode_dice_input(encoded_dice_input.as_slice(),).unwrap_err()
+ );
+
+ let encoded_dice_input =
+ encode_dice_input_for_testing(None, &minimal_dice_input);
+ let (decoded_version_info, decoded_dice_input) =
+ decode_dice_input(encoded_dice_input.as_slice()).unwrap();
+ assert_eq!(minimal_dice_input, decoded_dice_input);
+ assert_eq!(None, decoded_version_info);
+
+ let encoded_dice_input = encode_dice_input_for_testing(
+ Some(invalid_version_info),
+ &minimal_dice_input,
+ );
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ decode_dice_input(encoded_dice_input.as_slice(),).unwrap_err()
+ );
+
+ let encoded_dice_input =
+ encode_dice_input_for_testing(Some(version_info), &full_dice_input);
+ let (decoded_version_info, decoded_dice_input) =
+ decode_dice_input(encoded_dice_input.as_slice()).unwrap();
+ assert_eq!(full_dice_input, decoded_dice_input);
+ assert_eq!(Some(version_info), decoded_version_info);
+
+ for (
+ omit_version_slot,
+ omit_version_value,
+ omit_config_type,
+ omit_config_value,
+ ) in [
+ (true, false, false, false),
+ (false, true, false, false),
+ (false, false, true, false),
+ (false, false, false, true),
+ ] {
+ let encoded_dice_input = encode_dice_input_for_testing_with_errors(
+ Some(version_info),
+ &full_dice_input,
+ omit_version_slot,
+ omit_version_value,
+ omit_config_type,
+ omit_config_value,
+ );
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ decode_dice_input(encoded_dice_input.as_slice(),).unwrap_err()
+ );
+ }
+ }
+
+ #[test]
+ fn unseal_policy_decode() {
+ test_init();
+ let empty_versions = [0; DPE_MAX_VERSION_SLOTS];
+ let mut one_version = empty_versions.clone();
+ one_version[0] = 1;
+ let full_versions = [2; DPE_MAX_VERSION_SLOTS];
+
+ assert_eq!(
+ empty_versions,
+ decode_unseal_policy(
+ encode_unseal_policy_for_testing(&empty_versions).as_slice()
+ )
+ .unwrap()
+ );
+
+ assert_eq!(
+ one_version,
+ decode_unseal_policy(
+ encode_unseal_policy_for_testing(&one_version).as_slice()
+ )
+ .unwrap()
+ );
+
+ assert_eq!(
+ full_versions,
+ decode_unseal_policy(
+ encode_unseal_policy_for_testing(&full_versions).as_slice()
+ )
+ .unwrap()
+ );
+
+ let mut buffer = SmallMessage::new();
+ let mut encoder = cbor_encoder_from_message(&mut buffer);
+ let _ = encoder.begin_map().unwrap().end().unwrap();
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ decode_unseal_policy(buffer.as_slice()).unwrap_err()
+ );
+
+ buffer.clear();
+ let mut encoder = cbor_encoder_from_message(&mut buffer);
+ let _ = encoder.map(DPE_MAX_VERSION_SLOTS as u64 + 1);
+ for i in 0..DPE_MAX_VERSION_SLOTS as u16 + 1 {
+ let _ = encoder.u16(i).unwrap().u64(100).unwrap();
+ }
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ decode_unseal_policy(buffer.as_slice()).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn check_error_response() {
+ let mut buffer = Message::new();
+ create_error_response(ErrCode::OutOfMemory, &mut buffer);
+ assert_eq!(
+ ErrCode::OutOfMemory,
+ decode_error_response_for_testing(buffer.as_slice()).unwrap_err()
+ );
+ create_plaintext_session_error_response(ErrCode::Canceled, &mut buffer);
+ let mut decoder = cbor_decoder_from_message(&buffer);
+ assert_eq!(2, decoder.array().unwrap().unwrap());
+ assert_eq!(0, decoder.u16().unwrap());
+ assert_eq!(
+ ErrCode::Canceled,
+ decode_error_response_for_testing(decoder.bytes().unwrap())
+ .unwrap_err()
+ );
+ }
+
+ #[test]
+ fn encode_decode_args() {
+ let mut buffer = Message::new();
+ let empty: [u8; 0] = Default::default();
+ let small = [0xFF; 50];
+ let large = [0xAA; 2000];
+ let arg_map = ArgMap::from_iter(
+ [
+ (1, ArgValue::from_bool(true)),
+ (2, ArgValue::from_slice(&empty)),
+ (3, ArgValue::from_slice(&small)),
+ (4, ArgValue::from_slice(&large)),
+ (5, ArgValue::from_u32(5)),
+ (6, ArgValue::from_u64(2)),
+ ]
+ .into_iter(),
+ );
+ let arg_types = [
+ (4, ArgTypeSelector::Bytes),
+ (5, ArgTypeSelector::Int),
+ (6, ArgTypeSelector::Int),
+ (1, ArgTypeSelector::Bool),
+ (2, ArgTypeSelector::Bytes),
+ (3, ArgTypeSelector::Bytes),
+ ];
+ let arg_defaults = [
+ (1, ArgValue::from_bool(false)),
+ (5, ArgValue::from_u32(12)),
+ (6, ArgValue::from_u64(25000)),
+ ];
+ encode_args(&arg_map, &mut buffer).unwrap();
+ {
+ let decoded_arg_map =
+ decode_args(buffer.as_slice(), &arg_types, &arg_defaults)
+ .unwrap();
+ assert_eq!(arg_map, decoded_arg_map);
+ // Since all fields are defined, defaults are superfluous.
+ let decoded_arg_map =
+ decode_args(buffer.as_slice(), &arg_types, &[]).unwrap();
+ assert_eq!(arg_map, decoded_arg_map);
+ }
+
+ let arg_map_empty = ArgMap::new();
+ let mut arg_map_with_defaults =
+ ArgMap::from_iter(arg_defaults.clone().into_iter());
+ // The Bytes args should default to empty.
+ let _ =
+ arg_map_with_defaults.insert(2, ArgValue::from_slice(&[])).unwrap();
+ let _ =
+ arg_map_with_defaults.insert(3, ArgValue::from_slice(&[])).unwrap();
+ let _ =
+ arg_map_with_defaults.insert(4, ArgValue::from_slice(&[])).unwrap();
+ encode_args(&arg_map_empty, &mut buffer).unwrap();
+ {
+ let decoded_arg_map =
+ decode_args(buffer.as_slice(), &arg_types, &arg_defaults)
+ .unwrap();
+ assert_eq!(arg_map_with_defaults, decoded_arg_map);
+ }
+ }
+
+ #[test]
+ fn decode_invalid_args() {
+ // Empty bytes -> not valid CBOR map
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_args(&[], &[], &[]).unwrap_err()
+ );
+ let mut buffer = Message::new();
+ // CBOR array -> not valid CBOR map
+ let _ = cbor_encoder_from_message(&mut buffer)
+ .array(MESSAGE_ARRAY_SIZE)
+ .unwrap()
+ .bool(true)
+ .unwrap()
+ .u16(7)
+ .unwrap();
+ let arg_types = [(1, ArgTypeSelector::Bool)];
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_args(buffer.as_slice(), &arg_types, &[]).unwrap_err()
+ );
+ buffer.clear();
+ // CBOR indefinite map -> should be not supported
+ let _ = cbor_encoder_from_message(&mut buffer)
+ .begin_map()
+ .unwrap()
+ .end()
+ .unwrap();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_args(buffer.as_slice(), &arg_types, &[]).unwrap_err()
+ );
+ // All args must be represented in arg types
+ let unknown_arg =
+ ArgMap::from_iter([(17, ArgValue::from_u32(17))].into_iter());
+ encode_args(&unknown_arg, &mut buffer).unwrap();
+ assert_eq!(
+ ErrCode::InvalidArgument,
+ decode_args(buffer.as_slice(), &arg_types, &[]).unwrap_err()
+ );
+ // All known args must be present or have a default value
+ let arg_map_empty = ArgMap::new();
+ encode_args(&arg_map_empty, &mut buffer).unwrap();
+ assert_eq!(
+ ErrCode::InternalError,
+ decode_args(buffer.as_slice(), &arg_types, &[]).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn cdi_export_encode() {
+ let cdi1 = Cdi::from_array(&[0xAA; DICE_CDI_SIZE]);
+ let cdi2 = Cdi::from_array(&[0xBB; DICE_CDI_SIZE]);
+ let mut buffer = SmallMessage::new();
+ encode_cdis_for_export(&cdi1, &cdi2, &mut buffer).unwrap();
+ let mut decoder = cbor_decoder_from_message(&buffer);
+ assert_eq!(decoder.array().unwrap().unwrap(), 2);
+ assert_eq!(cdi1, Cdi::from_slice(decoder.bytes().unwrap()).unwrap());
+ assert_eq!(cdi2, Cdi::from_slice(decoder.bytes().unwrap()).unwrap());
+ }
+
+ #[test]
+ fn certificate_chain_encode() {
+ let cert1 = Certificate(
+ Vec::from_slice(&[0x11; DPE_MAX_CERTIFICATE_SIZE]).unwrap(),
+ );
+ let cert2 = Certificate(Vec::from_slice(&[0x22; 1]).unwrap());
+ let cert3: Certificate = Default::default();
+ let chain = CertificateChain(
+ Vec::from_slice(&[cert1.clone(), cert2.clone(), cert3.clone()])
+ .unwrap(),
+ );
+ let mut buffer = Message::new();
+ encode_certificate_chain(&chain, &mut buffer).unwrap();
+ assert_eq!(chain, decode_certificate_chain_for_testing(&buffer));
+ }
+
+ #[test]
+ fn message_header() {
+ let session_id = SessionId(25);
+ let content = [0u8; 100];
+ let mut buffer = Message::from_slice(&content).unwrap();
+ encode_and_insert_session_message_header(session_id, &mut buffer)
+ .unwrap();
+ assert!(content.len() < buffer.len());
+ assert_eq!(
+ session_id,
+ decode_and_remove_session_message_header(&mut buffer).unwrap()
+ );
+ assert_eq!(content, buffer.as_slice());
+ }
+
+ #[test]
+ fn decode_invalid_message_header() {
+ let mut buffer = Message::new();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_session_message_header(&mut buffer).unwrap_err()
+ );
+ let mut buffer = Message::from_slice(&[0; 14]).unwrap();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_session_message_header(&mut buffer).unwrap_err()
+ );
+ // Add an unexpected array element.
+ buffer.clear();
+ let _ = cbor_encoder_from_message(&mut buffer)
+ .array(3)
+ .unwrap()
+ .u32(1)
+ .unwrap()
+ .u32(2)
+ .unwrap()
+ .u32(3)
+ .unwrap();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_session_message_header(&mut buffer).unwrap_err()
+ );
+ // Use an invalid id type.
+ buffer.clear();
+ let _ = cbor_encoder_from_message(&mut buffer)
+ .array(MESSAGE_ARRAY_SIZE)
+ .unwrap()
+ .bytes(&[])
+ .unwrap()
+ .u32(2)
+ .unwrap();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_session_message_header(&mut buffer).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn command_header() {
+ let command_id = CommandSelector::RotateContextHandle;
+ let content = [0u8; 100];
+ let mut buffer = Message::from_slice(&content).unwrap();
+ encode_and_insert_command_header_for_testing(command_id, &mut buffer);
+ assert!(content.len() < buffer.len());
+ assert_eq!(
+ command_id,
+ decode_and_remove_command_header(&mut buffer).unwrap()
+ );
+ assert_eq!(content, buffer.as_slice());
+ }
+
+ #[test]
+ fn decode_invalid_command_header() {
+ let mut buffer = Message::new();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_command_header(&mut buffer).unwrap_err()
+ );
+ let mut buffer = Message::from_slice(&[0; 14]).unwrap();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_command_header(&mut buffer).unwrap_err()
+ );
+ // Add an unexpected array element.
+ buffer.clear();
+ let _ = cbor_encoder_from_message(&mut buffer)
+ .array(3)
+ .unwrap()
+ .u32(1)
+ .unwrap()
+ .u32(2)
+ .unwrap()
+ .u32(3)
+ .unwrap();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_command_header(&mut buffer).unwrap_err()
+ );
+ // Use an invalid id type.
+ buffer.clear();
+ let _ = cbor_encoder_from_message(&mut buffer)
+ .array(MESSAGE_ARRAY_SIZE)
+ .unwrap()
+ .bytes(&[])
+ .unwrap()
+ .u32(2)
+ .unwrap();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_command_header(&mut buffer).unwrap_err()
+ );
+ // Use an invalid command id.
+ buffer.clear();
+ let _ = cbor_encoder_from_message(&mut buffer)
+ .array(MESSAGE_ARRAY_SIZE)
+ .unwrap()
+ .u32(1000)
+ .unwrap()
+ .u32(2)
+ .unwrap();
+ assert_eq!(
+ ErrCode::InvalidCommand,
+ decode_and_remove_command_header(&mut buffer).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn response_header() {
+ let content = [0u8; 100];
+ let mut buffer = Message::from_slice(&content).unwrap();
+ encode_and_insert_response_header(&mut buffer).unwrap();
+ assert!(content.len() < buffer.len());
+ let mut decoder = cbor_decoder_from_message(&buffer);
+ assert_eq!(2, decoder.array().unwrap().unwrap());
+ assert_eq!(0, decoder.u32().unwrap());
+ assert_eq!(content, decoder.bytes().unwrap());
+ let overflow_content = [0u8; DPE_MAX_MESSAGE_SIZE - 2];
+ let mut buffer = Message::from_slice(&overflow_content).unwrap();
+ assert_eq!(
+ ErrCode::OutOfMemory,
+ encode_and_insert_response_header(&mut buffer).unwrap_err()
+ );
+ }
+
+ #[test]
+ fn profile_descriptor() {
+ let name = "test";
+ let mut buffer = Message::new();
+ encode_profile_descriptor_from_name(name, &mut buffer).unwrap();
+ let mut decoder = cbor_decoder_from_message(&buffer);
+ assert_eq!(1, decoder.map().unwrap().unwrap());
+ assert_eq!(1, decoder.u32().unwrap());
+ assert_eq!(name, decoder.str().unwrap());
+ }
+
+ #[test]
+ fn handshake_payload() {
+ let session_id = SessionId(11);
+ let payload = encode_handshake_payload(session_id).unwrap();
+ let mut decoder = cbor_decoder_from_message(&payload);
+ assert_eq!(session_id.0 as u16, decoder.u16().unwrap());
+ }
+}
diff --git a/dpe-rs/src/lib.rs b/dpe-rs/src/lib.rs
index b995caf..02adb2a 100644
--- a/dpe-rs/src/lib.rs
+++ b/dpe-rs/src/lib.rs
@@ -52,6 +52,7 @@
pub mod cbor;
pub mod constants;
pub mod crypto;
+pub mod encode;
pub mod error;
pub mod memory;
pub mod noise;