Merge "[attestation] Verify client VM's DICE chain up to pvmfw payload" into main
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 1ab02e9..90008a9 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -113,6 +113,8 @@
"libciborium",
"libclient_vm_csr",
"libcoset",
+ "libcstr",
+ "libdiced_open_dice",
"libdiced_sample_inputs",
"liblibc",
"liblog_rust",
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index d9cffe0..0bdc927 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -37,7 +37,7 @@
use hyp::{get_mem_sharer, get_mmio_guard};
use libfdt::FdtError;
use log::{debug, error, info};
-use service_vm_comm::{ServiceVmRequest, VmType};
+use service_vm_comm::{RequestProcessingError, Response, ServiceVmRequest, VmType};
use service_vm_requests::process_request;
use virtio_drivers::{
device::socket::{VsockAddr, VMADDR_CID_HOST},
@@ -178,7 +178,15 @@
let mut vsock_stream = VsockStream::new(socket_device, host_addr())?;
while let ServiceVmRequest::Process(req) = vsock_stream.read_request()? {
- let response = process_request(req, bcc_handover.as_ref());
+ let mut response = process_request(req, bcc_handover.as_ref());
+ // TODO(b/185878400): We don't want to issue a certificate to pVM when the client VM
+ // attestation is unfinished. The following code should be removed once the
+ // verification is completed.
+ if vm_type() == VmType::ProtectedVm
+ && matches!(response, Response::RequestClientVmAttestation(_))
+ {
+ response = Response::Err(RequestProcessingError::OperationUnimplemented);
+ }
vsock_stream.write_response(&response)?;
vsock_stream.flush()?;
}
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 85c3efe..0260773 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -26,10 +26,15 @@
use ciborium::value::Value;
use client_vm_csr::generate_attestation_key_and_csr;
use coset::{CborSerializable, CoseMac0, CoseSign};
+use cstr::cstr;
+use diced_open_dice::{
+ retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
+ DiceConfigValues, DiceMode, InputValues, OwnedDiceArtifacts, HASH_SIZE, HIDDEN_SIZE,
+};
use log::info;
use service_vm_comm::{
ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
- Request, Response, VmType,
+ Request, RequestProcessingError, Response, VmType,
};
use service_vm_manager::ServiceVm;
use std::fs;
@@ -48,6 +53,19 @@
const UNSIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto_unsigned.bin";
const INSTANCE_IMG_PATH: &str = "/data/local/tmp/rialto_test/arm64/instance.img";
const TEST_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
+/// The following data are generated randomly with urandom.
+const CODE_HASH_MICRODROID: [u8; HASH_SIZE] = [
+ 0x08, 0x78, 0xc2, 0x5b, 0xe7, 0xea, 0x3d, 0x62, 0x70, 0x22, 0xd9, 0x1c, 0x4f, 0x3c, 0x2e, 0x2f,
+ 0x0f, 0x97, 0xa4, 0x6f, 0x6d, 0xd5, 0xe6, 0x4a, 0x6d, 0xbe, 0x34, 0x2e, 0x56, 0x04, 0xaf, 0xef,
+ 0x74, 0x3f, 0xec, 0xb8, 0x44, 0x11, 0xf4, 0x2f, 0x05, 0xb2, 0x06, 0xa3, 0x0e, 0x75, 0xb7, 0x40,
+ 0x9a, 0x4c, 0x58, 0xab, 0x96, 0xe7, 0x07, 0x97, 0x07, 0x86, 0x5c, 0xa1, 0x42, 0x12, 0xf0, 0x34,
+];
+const AUTHORITY_HASH_MICRODROID: [u8; HASH_SIZE] = [
+ 0xc7, 0x97, 0x5b, 0xa9, 0x9e, 0xbf, 0x0b, 0xeb, 0xe7, 0x7f, 0x69, 0x8f, 0x8e, 0xcf, 0x04, 0x7d,
+ 0x2c, 0x0f, 0x4d, 0xbe, 0xcb, 0xf5, 0xf1, 0x4c, 0x1d, 0x1c, 0xb7, 0x44, 0xdf, 0xf8, 0x40, 0x90,
+ 0x09, 0x65, 0xab, 0x01, 0x34, 0x3e, 0xc2, 0xc4, 0xf7, 0xa2, 0x3a, 0x5c, 0x4e, 0x76, 0x4f, 0x42,
+ 0xa8, 0x6c, 0xc9, 0xf1, 0x7b, 0x12, 0x80, 0xa4, 0xef, 0xa2, 0x4d, 0x72, 0xa1, 0x21, 0xe2, 0x47,
+];
#[test]
fn process_requests_in_protected_vm() -> Result<()> {
@@ -65,7 +83,7 @@
check_processing_reverse_request(&mut vm)?;
let key_pair = check_processing_generating_key_pair_request(&mut vm)?;
check_processing_generating_certificate_request(&mut vm, &key_pair.maced_public_key)?;
- check_attestation_request(&mut vm, &key_pair)?;
+ check_attestation_request(&mut vm, &key_pair, vm_type)?;
Ok(())
}
@@ -123,6 +141,7 @@
fn check_attestation_request(
vm: &mut ServiceVm,
remotely_provisioned_key_pair: &EcdsaP256KeyPair,
+ vm_type: VmType,
) -> Result<()> {
/// The following data was generated randomly with urandom.
const CHALLENGE: [u8; 16] = [
@@ -130,6 +149,7 @@
0x5c,
];
let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+ let dice_artifacts = extend_dice_artifacts_with_microdroid_payload(&dice_artifacts)?;
let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
let cert_chain = fs::read(TEST_CERT_CHAIN_PATH)?;
let (remaining, cert) = X509Certificate::from_der(&cert_chain)?;
@@ -151,6 +171,9 @@
match response {
Response::RequestClientVmAttestation(certificate) => {
+ // The end-to-end test for non-protected VM attestation works because both the service
+ // VM and the client VM use the same fake DICE chain.
+ assert_eq!(vm_type, VmType::NonProtectedVm);
check_certificate_for_client_vm(
&certificate,
&remotely_provisioned_key_pair.maced_public_key,
@@ -159,10 +182,43 @@
)?;
Ok(())
}
+ Response::Err(RequestProcessingError::InvalidDiceChain) => {
+ // The end-to-end test for protected VM attestation doesn't work because the service VM
+ // compares the fake DICE chain in the CSR with the real DICE chain.
+ // We cannot generate a valid DICE chain with the same payloads up to pvmfw.
+ assert_eq!(vm_type, VmType::ProtectedVm);
+ Ok(())
+ }
_ => bail!("Incorrect response type: {response:?}"),
}
}
+fn extend_dice_artifacts_with_microdroid_payload(
+ dice_artifacts: &dyn DiceArtifacts,
+) -> Result<OwnedDiceArtifacts> {
+ let config_values = DiceConfigValues {
+ component_name: Some(cstr!("Microdroid payload")),
+ component_version: Some(1),
+ resettable: true,
+ ..Default::default()
+ };
+ let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+ let input_values = InputValues::new(
+ CODE_HASH_MICRODROID,
+ Config::Descriptor(config_descriptor.as_slice()),
+ AUTHORITY_HASH_MICRODROID,
+ DiceMode::kDiceModeDebug,
+ [0u8; HIDDEN_SIZE], // hidden
+ );
+ retry_bcc_main_flow(
+ dice_artifacts.cdi_attest(),
+ dice_artifacts.cdi_seal(),
+ dice_artifacts.bcc().unwrap(),
+ &input_values,
+ )
+ .context("Failed to run BCC main flow for Microdroid")
+}
+
fn check_certificate_for_client_vm(
certificate: &[u8],
maced_public_key: &[u8],
@@ -205,8 +261,13 @@
let (remaining, extension) = parse_der(extension.value)?;
assert!(remaining.is_empty());
let attestation_ext = extension.as_sequence()?;
- assert_eq!(1, attestation_ext.len());
+ assert_eq!(2, attestation_ext.len());
assert_eq!(csr_payload.challenge, attestation_ext[0].as_slice()?);
+ let is_vm_secure = attestation_ext[1].as_bool()?;
+ assert!(
+ !is_vm_secure,
+ "The VM shouldn't be secure as the last payload added in the test is in Debug mode"
+ );
// Checks other fields on the certificate
assert_eq!(X509Version::V3, cert.version());
diff --git a/service_vm/comm/src/csr.rs b/service_vm/comm/src/csr.rs
index 757d080..2a27f90 100644
--- a/service_vm/comm/src/csr.rs
+++ b/service_vm/comm/src/csr.rs
@@ -100,7 +100,8 @@
}
}
-fn try_as_bytes(v: Value, context: &str) -> coset::Result<Vec<u8>> {
+/// Reads the provided value `v` as bytes array.
+pub fn try_as_bytes(v: Value, context: &str) -> coset::Result<Vec<u8>> {
if let Value::Bytes(data) = v {
Ok(data)
} else {
@@ -110,7 +111,8 @@
}
}
-fn cbor_value_type(v: &Value) -> &'static str {
+/// Reads the type of the provided value `v`.
+pub fn cbor_value_type(v: &Value) -> &'static str {
match v {
Value::Integer(_) => "int",
Value::Bytes(_) => "bstr",
diff --git a/service_vm/comm/src/lib.rs b/service_vm/comm/src/lib.rs
index bb85a26..c9de540 100644
--- a/service_vm/comm/src/lib.rs
+++ b/service_vm/comm/src/lib.rs
@@ -23,7 +23,7 @@
mod message;
mod vsock;
-pub use csr::{Csr, CsrPayload};
+pub use csr::{cbor_value_type, try_as_bytes, Csr, CsrPayload};
pub use message::{
ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
RequestProcessingError, Response, ServiceVmRequest,
diff --git a/service_vm/comm/src/message.rs b/service_vm/comm/src/message.rs
index 87c8378..80a9608 100644
--- a/service_vm/comm/src/message.rs
+++ b/service_vm/comm/src/message.rs
@@ -130,6 +130,9 @@
/// An error happened during the DER encoding/decoding.
DerError,
+
+ /// The DICE chain from the client VM is invalid.
+ InvalidDiceChain,
}
impl fmt::Display for RequestProcessingError {
@@ -155,6 +158,9 @@
Self::DerError => {
write!(f, "An error happened during the DER encoding/decoding")
}
+ Self::InvalidDiceChain => {
+ write!(f, "The DICE chain from the client VM is invalid")
+ }
}
}
}
diff --git a/service_vm/comm/src/vsock.rs b/service_vm/comm/src/vsock.rs
index aa7166d..7f7cf25 100644
--- a/service_vm/comm/src/vsock.rs
+++ b/service_vm/comm/src/vsock.rs
@@ -18,7 +18,7 @@
const NON_PROTECTED_VM_PORT: u32 = 5680;
/// VM Type.
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VmType {
/// Protected VM.
ProtectedVm,
diff --git a/service_vm/requests/src/cert.rs b/service_vm/requests/src/cert.rs
index 68ca382..2baca2a 100644
--- a/service_vm/requests/src/cert.rs
+++ b/service_vm/requests/src/cert.rs
@@ -49,6 +49,8 @@
pub(crate) struct AttestationExtension<'a> {
#[asn1(type = "OCTET STRING")]
attestation_challenge: &'a [u8],
+ /// Indicates whether the VM is operating under a secure configuration.
+ is_vm_secure: bool,
}
impl<'a> AssociatedOid for AttestationExtension<'a> {
@@ -56,8 +58,8 @@
}
impl<'a> AttestationExtension<'a> {
- pub(crate) fn new(challenge: &'a [u8]) -> Self {
- Self { attestation_challenge: challenge }
+ pub(crate) fn new(attestation_challenge: &'a [u8], is_vm_secure: bool) -> Self {
+ Self { attestation_challenge, is_vm_secure }
}
}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index e1f345c..bb92f4a 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -16,6 +16,7 @@
//! client VM.
use crate::cert;
+use crate::dice::{validate_client_vm_dice_chain_prefix_match, ClientVmDiceChain};
use crate::keyblob::decrypt_private_key;
use alloc::vec::Vec;
use bssl_avf::{rand_bytes, sha256, EcKey, EvpPKey};
@@ -43,21 +44,30 @@
})?;
let csr_payload = CsrPayload::from_cbor_slice(csr_payload)?;
+ // Validates the prefix of the Client VM DICE chain in the CSR.
+ let service_vm_dice_chain =
+ dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?;
+ let client_vm_dice_chain =
+ validate_client_vm_dice_chain_prefix_match(&csr.dice_cert_chain, service_vm_dice_chain)?;
+ // Validates the signatures in the Client VM DICE chain and extracts the partially decoded
+ // DiceChainEntryPayloads.
+ let client_vm_dice_chain =
+ ClientVmDiceChain::validate_signatures_and_parse_dice_chain(client_vm_dice_chain)?;
+
// AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
let aad = &[];
+ // Verifies the first signature with the leaf private key in the DICE chain.
// TODO(b/310931749): Verify the first signature with CDI_Leaf_Pub of
// the DICE chain in `cose_sign`.
+ // Verifies the second signature with the public key in the CSR payload.
let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
ecdsa_verify(&ec_public_key, signature, message)
})?;
let subject_public_key_info = EvpPKey::try_from(ec_public_key)?.subject_public_key_info()?;
- // TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
- // cert with RKP VM's DICE chain.
-
// Builds the TBSCertificate.
// The serial number can be up to 20 bytes according to RFC5280 s4.1.2.2.
// In this case, a serial number with a length of 20 bytes is used to ensure that each
@@ -66,7 +76,11 @@
rand_bytes(&mut serial_number)?;
let subject = Name::encode_from_string("CN=Android Protected Virtual Machine Key")?;
let rkp_cert = Certificate::from_der(¶ms.remotely_provisioned_cert)?;
- let attestation_ext = cert::AttestationExtension::new(&csr_payload.challenge).to_vec()?;
+ let attestation_ext = cert::AttestationExtension::new(
+ &csr_payload.challenge,
+ client_vm_dice_chain.all_entries_are_secure(),
+ )
+ .to_vec()?;
let tbs_cert = cert::build_tbs_certificate(
&serial_number,
rkp_cert.tbs_certificate.subject,
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
new file mode 100644
index 0000000..c220af6
--- /dev/null
+++ b/service_vm/requests/src/dice.rs
@@ -0,0 +1,321 @@
+// Copyright 2023, 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 contains functions related to DICE.
+
+use alloc::vec::Vec;
+use ciborium::value::Value;
+use core::cell::OnceCell;
+use core::result;
+use coset::{
+ self, iana, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation,
+};
+use diced_open_dice::{DiceMode, HASH_SIZE};
+use log::error;
+use service_vm_comm::{cbor_value_type, try_as_bytes, RequestProcessingError};
+
+type Result<T> = result::Result<T, RequestProcessingError>;
+
+const CODE_HASH: i64 = -4670545;
+const CONFIG_DESC: i64 = -4670548;
+const AUTHORITY_HASH: i64 = -4670549;
+const MODE: i64 = -4670551;
+const SUBJECT_PUBLIC_KEY: i64 = -4670552;
+
+/// Represents a partially decoded `DiceCertChain` from the client VM.
+/// The whole chain is defined as following:
+///
+/// DiceCertChain = [
+/// PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384, ; UDS_Pub
+/// + DiceChainEntry, ; First CDI_Certificate -> Last CDI_Certificate
+/// ]
+#[derive(Debug, Clone)]
+pub(crate) struct ClientVmDiceChain {
+ pub(crate) payloads: Vec<DiceChainEntryPayload>,
+}
+
+impl ClientVmDiceChain {
+ /// Validates the signatures of the entries in the `client_vm_dice_chain` as following:
+ ///
+ /// - The first entry of the `client_vm_dice_chain` must be signed with the root public key.
+ /// - After the first entry, each entry of the `client_vm_dice_chain` must be signed with the
+ /// subject public key of the previous entry.
+ ///
+ /// Returns a partially decoded client VM's DICE chain if the verification succeeds.
+ pub(crate) fn validate_signatures_and_parse_dice_chain(
+ mut client_vm_dice_chain: Vec<Value>,
+ ) -> Result<Self> {
+ let root_public_key =
+ CoseKey::from_cbor_value(client_vm_dice_chain.remove(0))?.try_into()?;
+
+ let mut payloads = Vec::with_capacity(client_vm_dice_chain.len());
+ let mut previous_public_key = &root_public_key;
+ for (i, value) in client_vm_dice_chain.into_iter().enumerate() {
+ let payload = DiceChainEntryPayload::validate_cose_signature_and_extract_payload(
+ value,
+ previous_public_key,
+ )
+ .map_err(|e| {
+ error!("Failed to verify the DICE chain entry {}: {:?}", i, e);
+ e
+ })?;
+ payloads.push(payload);
+ previous_public_key = &payloads.last().unwrap().subject_public_key;
+ }
+ // After successfully calling `validate_client_vm_dice_chain_prefix_match`, we can be
+ // certain that the client VM's DICE chain must contain at least three entries that
+ // describe:
+ // - pvmfw
+ // - Microdroid kernel
+ // - Apk/Apexes
+ assert!(
+ payloads.len() >= 3,
+ "The client VM DICE chain must contain at least three DiceChainEntryPayloads"
+ );
+ Ok(Self { payloads })
+ }
+
+ /// Returns true if all payloads in the DICE chain are in normal mode.
+ pub(crate) fn all_entries_are_secure(&self) -> bool {
+ self.payloads.iter().all(|p| p.mode == DiceMode::kDiceModeNormal)
+ }
+}
+
+/// Validates that the `client_vm_dice_chain` matches the `service_vm_dice_chain` up to the pvmfw
+/// entry.
+///
+/// Returns a CBOR value array of the client VM's DICE chain if the verification succeeds.
+pub(crate) fn validate_client_vm_dice_chain_prefix_match(
+ client_vm_dice_chain: &[u8],
+ service_vm_dice_chain: &[u8],
+) -> Result<Vec<Value>> {
+ let client_vm_dice_chain =
+ try_as_value_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
+ let service_vm_dice_chain =
+ try_as_value_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
+ if service_vm_dice_chain.len() < 3 {
+ // The service VM's DICE chain must contain the root key and at least two other entries
+ // that describe:
+ // - pvmfw
+ // - Service VM kernel
+ error!("The service VM DICE chain must contain at least three entries");
+ return Err(RequestProcessingError::InternalError);
+ }
+ // Ignores the last entry that describes service VM
+ let entries_up_to_pvmfw = &service_vm_dice_chain[0..(service_vm_dice_chain.len() - 1)];
+ if entries_up_to_pvmfw.len() + 2 != client_vm_dice_chain.len() {
+ // Client VM DICE chain = entries_up_to_pvmfw
+ // + Microdroid kernel entry (added in pvmfw)
+ // + Apk/Apexes entry (added in microdroid)
+ error!("The client VM's DICE chain must contain exactly two extra entries");
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ if entries_up_to_pvmfw != &client_vm_dice_chain[0..entries_up_to_pvmfw.len()] {
+ error!(
+ "The client VM's DICE chain does not match service VM's DICE chain up to \
+ the pvmfw entry"
+ );
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ Ok(client_vm_dice_chain)
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct PublicKey(CoseKey);
+
+impl TryFrom<CoseKey> for PublicKey {
+ type Error = RequestProcessingError;
+
+ fn try_from(key: CoseKey) -> Result<Self> {
+ if !key.key_ops.contains(&KeyOperation::Assigned(iana::KeyOperation::Verify)) {
+ error!("Public key does not support verification");
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ Ok(Self(key))
+ }
+}
+
+/// Represents a partially decoded `DiceChainEntryPayload`. The whole payload is defined in:
+///
+/// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+/// generateCertificateRequestV2.cddl
+#[derive(Debug, Clone)]
+pub(crate) struct DiceChainEntryPayload {
+ /// TODO(b/310931749): Verify the DICE chain entry using the subject public key.
+ #[allow(dead_code)]
+ subject_public_key: PublicKey,
+ mode: DiceMode,
+ /// TODO(b/271275206): Verify Microdroid kernel authority and code hashes.
+ #[allow(dead_code)]
+ code_hash: [u8; HASH_SIZE],
+ #[allow(dead_code)]
+ authority_hash: [u8; HASH_SIZE],
+ /// TODO(b/313815907): Parse the config descriptor and read Apk/Apexes info in it.
+ #[allow(dead_code)]
+ config_descriptor: Vec<u8>,
+}
+
+impl DiceChainEntryPayload {
+ /// Validates the signature of the provided CBOR value with the provided public key and
+ /// extracts payload from the value.
+ fn validate_cose_signature_and_extract_payload(
+ value: Value,
+ _authority_public_key: &PublicKey,
+ ) -> Result<Self> {
+ let cose_sign1 = CoseSign1::from_cbor_value(value)?;
+ // TODO(b/310931749): Verify the DICE chain entry using `authority_public_key`.
+
+ let payload = cose_sign1.payload.ok_or_else(|| {
+ error!("No payload found in the DICE chain entry");
+ RequestProcessingError::InvalidDiceChain
+ })?;
+ let payload = Value::from_slice(&payload)?;
+ let Value::Map(entries) = payload else {
+ return Err(CoseError::UnexpectedItem(cbor_value_type(&payload), "map").into());
+ };
+ build_payload(entries)
+ }
+}
+
+fn build_payload(entries: Vec<(Value, Value)>) -> Result<DiceChainEntryPayload> {
+ let mut builder = PayloadBuilder::default();
+ for (key, value) in entries.into_iter() {
+ let Some(Ok(key)) = key.as_integer().map(i64::try_from) else {
+ error!("Invalid key found in the DICE chain entry: {:?}", key);
+ return Err(RequestProcessingError::InvalidDiceChain);
+ };
+ match key {
+ SUBJECT_PUBLIC_KEY => {
+ let subject_public_key = try_as_bytes(value, "subject_public_key")?;
+ let subject_public_key = CoseKey::from_slice(&subject_public_key)?.try_into()?;
+ builder.subject_public_key(subject_public_key)?;
+ }
+ MODE => builder.mode(to_mode(value)?)?,
+ CODE_HASH => builder.code_hash(try_as_byte_array(value, "code_hash")?)?,
+ AUTHORITY_HASH => {
+ builder.authority_hash(try_as_byte_array(value, "authority_hash")?)?
+ }
+ CONFIG_DESC => builder.config_descriptor(try_as_bytes(value, "config_descriptor")?)?,
+ _ => {}
+ }
+ }
+ builder.build()
+}
+
+fn try_as_value_array(v: Value, context: &str) -> coset::Result<Vec<Value>> {
+ if let Value::Array(data) = v {
+ Ok(data)
+ } else {
+ let v_type = cbor_value_type(&v);
+ error!("The provided value type '{v_type}' is not of type 'bytes': {context}");
+ Err(CoseError::UnexpectedItem(v_type, "array"))
+ }
+}
+
+fn try_as_byte_array<const N: usize>(v: Value, context: &str) -> Result<[u8; N]> {
+ let data = try_as_bytes(v, context)?;
+ data.try_into().map_err(|e| {
+ error!("The provided value '{context}' is not an array of length {N}: {e:?}");
+ RequestProcessingError::InternalError
+ })
+}
+
+fn to_mode(value: Value) -> Result<DiceMode> {
+ let mode = match value {
+ // Mode is supposed to be encoded as a 1-byte bstr, but some implementations instead
+ // encode it as an integer. Accept either. See b/273552826.
+ // If Mode is omitted, it should be treated as if it was NotConfigured, according to
+ // the Open Profile for DICE spec.
+ Value::Bytes(bytes) => {
+ if bytes.len() != 1 {
+ error!("Bytes array with invalid length for mode: {:?}", bytes.len());
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ bytes[0].into()
+ }
+ Value::Integer(i) => i,
+ v => return Err(CoseError::UnexpectedItem(cbor_value_type(&v), "bstr or int").into()),
+ };
+ let mode = match mode {
+ x if x == (DiceMode::kDiceModeNormal as i64).into() => DiceMode::kDiceModeNormal,
+ x if x == (DiceMode::kDiceModeDebug as i64).into() => DiceMode::kDiceModeDebug,
+ x if x == (DiceMode::kDiceModeMaintenance as i64).into() => DiceMode::kDiceModeMaintenance,
+ // If Mode is invalid, it should be treated as if it was NotConfigured, according to
+ // the Open Profile for DICE spec.
+ _ => DiceMode::kDiceModeNotInitialized,
+ };
+ Ok(mode)
+}
+
+#[derive(Default, Debug, Clone)]
+struct PayloadBuilder {
+ subject_public_key: OnceCell<PublicKey>,
+ mode: OnceCell<DiceMode>,
+ code_hash: OnceCell<[u8; HASH_SIZE]>,
+ authority_hash: OnceCell<[u8; HASH_SIZE]>,
+ config_descriptor: OnceCell<Vec<u8>>,
+}
+
+fn set_once<T>(field: &OnceCell<T>, value: T, field_name: &str) -> Result<()> {
+ field.set(value).map_err(|_| {
+ error!("Field '{field_name}' is duplicated in the Payload");
+ RequestProcessingError::InvalidDiceChain
+ })
+}
+
+fn take_value<T>(field: &mut OnceCell<T>, field_name: &str) -> Result<T> {
+ field.take().ok_or_else(|| {
+ error!("Field '{field_name}' is missing in the Payload");
+ RequestProcessingError::InvalidDiceChain
+ })
+}
+
+impl PayloadBuilder {
+ fn subject_public_key(&mut self, key: PublicKey) -> Result<()> {
+ set_once(&self.subject_public_key, key, "subject_public_key")
+ }
+
+ fn mode(&mut self, mode: DiceMode) -> Result<()> {
+ set_once(&self.mode, mode, "mode")
+ }
+
+ fn code_hash(&mut self, code_hash: [u8; HASH_SIZE]) -> Result<()> {
+ set_once(&self.code_hash, code_hash, "code_hash")
+ }
+
+ fn authority_hash(&mut self, authority_hash: [u8; HASH_SIZE]) -> Result<()> {
+ set_once(&self.authority_hash, authority_hash, "authority_hash")
+ }
+
+ fn config_descriptor(&mut self, config_descriptor: Vec<u8>) -> Result<()> {
+ set_once(&self.config_descriptor, config_descriptor, "config_descriptor")
+ }
+
+ fn build(mut self) -> Result<DiceChainEntryPayload> {
+ let subject_public_key = take_value(&mut self.subject_public_key, "subject_public_key")?;
+ // If Mode is omitted, it should be treated as if it was NotConfigured, according to
+ // the Open Profile for DICE spec.
+ let mode = self.mode.take().unwrap_or(DiceMode::kDiceModeNotInitialized);
+ let code_hash = take_value(&mut self.code_hash, "code_hash")?;
+ let authority_hash = take_value(&mut self.authority_hash, "authority_hash")?;
+ let config_descriptor = take_value(&mut self.config_descriptor, "config_descriptor")?;
+ Ok(DiceChainEntryPayload {
+ subject_public_key,
+ mode,
+ code_hash,
+ authority_hash,
+ config_descriptor,
+ })
+ }
+}
diff --git a/service_vm/requests/src/lib.rs b/service_vm/requests/src/lib.rs
index 3f687a4..0dfac09 100644
--- a/service_vm/requests/src/lib.rs
+++ b/service_vm/requests/src/lib.rs
@@ -21,6 +21,7 @@
mod api;
mod cert;
mod client_vm;
+mod dice;
mod keyblob;
mod pub_key;
mod rkp;