Merge "Add missing <cassert> include" into main
diff --git a/remote_provisioning/hwtrust/src/cbor/dice.rs b/remote_provisioning/hwtrust/src/cbor/dice.rs
index cadc577..6336e25 100644
--- a/remote_provisioning/hwtrust/src/cbor/dice.rs
+++ b/remote_provisioning/hwtrust/src/cbor/dice.rs
@@ -1,7 +1,6 @@
//! Parsing and encoding DICE chain from and to CBOR.
use crate::cbor::cose_error;
-use crate::session::{KeyOpsType, Session};
use anyhow::Result;
use ciborium::value::Value;
use coset::iana::{self, EnumI64};
@@ -9,11 +8,22 @@
mod chain;
mod entry;
+mod profile;
+
+/// Type allowed for the COSE_Key object key_ops field in the DICE chain.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub(super) enum KeyOpsType {
+ /// The key_ops field must be an array as specified in the RFC 9052.
+ #[default]
+ Array,
+ /// The key_ops field can be either a single int or an array.
+ IntOrArray,
+}
/// Convert a `Value` into a `CoseKey`, respecting the `Session` options that might alter the
/// validation rules for `CoseKey`s in the DICE chain.
-fn cose_key_from_cbor_value(session: &Session, mut value: Value) -> Result<CoseKey> {
- if session.options.dice_chain_key_ops_type == KeyOpsType::IntOrArray {
+fn cose_key_from_cbor_value(mut value: Value, key_ops_type: KeyOpsType) -> Result<CoseKey> {
+ if key_ops_type == KeyOpsType::IntOrArray {
// Convert any integer key_ops into an array of the same integer so that the coset library
// can handle it.
if let Value::Map(ref mut entries) = value {
diff --git a/remote_provisioning/hwtrust/src/cbor/dice/chain.rs b/remote_provisioning/hwtrust/src/cbor/dice/chain.rs
index be8ecfc..73ca7d3 100644
--- a/remote_provisioning/hwtrust/src/cbor/dice/chain.rs
+++ b/remote_provisioning/hwtrust/src/cbor/dice/chain.rs
@@ -1,10 +1,10 @@
-use super::cose_key_from_cbor_value;
-use super::entry::Entry;
+use super::entry::{ConfigFormat, Entry};
+use super::{cose_key_from_cbor_value, KeyOpsType};
use crate::cbor::dice::entry::PayloadFields;
use crate::cbor::value_from_bytes;
-use crate::dice::{Chain, ChainForm, DegenerateChain, Payload};
+use crate::dice::{Chain, ChainForm, DegenerateChain, Payload, ProfileVersion};
use crate::publickey::PublicKey;
-use crate::session::{ConfigFormat, Session};
+use crate::session::Session;
use anyhow::{bail, Context, Result};
use ciborium::value::Value;
@@ -73,10 +73,13 @@
for (n, value) in values.enumerate() {
let entry = Entry::verify_cbor_value(value, previous_public_key)
.with_context(|| format!("Invalid entry at index {}", n))?;
- let config_format = if n == 0 {
- session.options.first_dice_chain_cert_config_format
+ let config_format = if n == 0
+ && session.options.dice_profile_range.contains(ProfileVersion::Android14)
+ {
+ // Context: b/261647022
+ ConfigFormat::AndroidOrIgnored
} else {
- ConfigFormat::Android
+ ConfigFormat::default()
};
let payload = Payload::from_cbor(session, entry.payload(), config_format)
.with_context(|| format!("Invalid payload at index {}", n))?;
@@ -97,7 +100,13 @@
_ => bail!("Expected an array of at least length 2, found: {:?}", value),
};
let mut it = array.into_iter();
- let root_public_key = cose_key_from_cbor_value(session, it.next().unwrap())
+ let key_ops_type = if session.options.dice_profile_range.contains(ProfileVersion::Android13) {
+ // Context: b/262599829#comment65
+ KeyOpsType::IntOrArray
+ } else {
+ KeyOpsType::default()
+ };
+ let root_public_key = cose_key_from_cbor_value(it.next().unwrap(), key_ops_type)
.context("Error parsing root public key CBOR")?;
let root_public_key = PublicKey::from_cose_key(&root_public_key).context("Invalid root key")?;
Ok((root_public_key, it))
@@ -109,7 +118,7 @@
use crate::cbor::serialize;
use crate::dice::{ConfigDesc, DiceMode, PayloadBuilder};
use crate::publickey::testkeys::{PrivateKey, ED25519_KEY_PEM, P256_KEY_PEM, P384_KEY_PEM};
- use crate::session::{KeyOpsType, Options};
+ use crate::session::Options;
use ciborium::cbor;
use coset::iana::{self, EnumI64};
use coset::AsCborValue;
@@ -152,9 +161,7 @@
#[test]
fn check_chain_valid_p256() {
let chain = fs::read("testdata/dice/valid_p256.chain").unwrap();
- let session = Session {
- options: Options { dice_chain_config_hash_unverified: true, ..Options::default() },
- };
+ let session = Session { options: Options::default() };
let chain = Chain::from_cbor(&session, &chain).unwrap();
assert_eq!(chain.payloads().len(), 3);
}
@@ -163,9 +170,7 @@
fn check_chain_valid_p256_value() {
let chain = fs::read("testdata/dice/valid_p256.chain").unwrap();
let chain = value_from_bytes(&chain).unwrap();
- let session = Session {
- options: Options { dice_chain_config_hash_unverified: true, ..Options::default() },
- };
+ let session = Session { options: Options::default() };
let chain = Chain::from_value(&session, chain).unwrap();
assert_eq!(chain.payloads().len(), 3);
}
@@ -244,12 +249,7 @@
let cbor = serialize(Value::Array(chain));
let session = Session { options: Options::default() };
Chain::from_cbor(&session, &cbor).unwrap_err();
- let session = Session {
- options: Options {
- dice_chain_key_ops_type: KeyOpsType::IntOrArray,
- ..Options::default()
- },
- };
+ let session = Session { options: Options::vsr13() };
Chain::from_cbor(&session, &cbor).unwrap();
}
diff --git a/remote_provisioning/hwtrust/src/cbor/dice/entry.rs b/remote_provisioning/hwtrust/src/cbor/dice/entry.rs
index 706eb58..9d01f7c 100644
--- a/remote_provisioning/hwtrust/src/cbor/dice/entry.rs
+++ b/remote_provisioning/hwtrust/src/cbor/dice/entry.rs
@@ -1,16 +1,19 @@
use super::cose_key_from_cbor_value;
+use super::profile::{ComponentVersionType, ModeType, Profile};
use crate::cbor::{cose_error, field_value::FieldValue, value_from_bytes};
use crate::dice::{
ComponentVersion, ConfigDesc, ConfigDescBuilder, DiceMode, Payload, PayloadBuilder,
+ ProfileVersion,
};
use crate::publickey::PublicKey;
-use crate::session::{ComponentVersionType, ConfigFormat, ModeType, Session};
+use crate::session::Session;
use anyhow::{anyhow, bail, ensure, Context, Result};
use ciborium::value::Value;
use coset::{AsCborValue, CoseSign1};
use openssl::sha::{sha256, sha384, sha512};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::HashMap;
+use std::str::FromStr;
const ISS: i64 = 1;
const SUB: i64 = 2;
@@ -23,6 +26,7 @@
const MODE: i64 = -4670551;
const SUBJECT_PUBLIC_KEY: i64 = -4670552;
const KEY_USAGE: i64 = -4670553;
+const PROFILE_NAME: i64 = -4670554;
const CONFIG_DESC_RESERVED_MAX: i64 = -70000;
const CONFIG_DESC_RESERVED_MIN: i64 = -70999;
@@ -52,13 +56,32 @@
}
}
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub(super) enum ConfigFormat {
+ /// The configuration descriptor format specified by Android.
+ #[default]
+ Android,
+ /// The configuration descriptor format is either that specified by Android or is ignored.
+ AndroidOrIgnored,
+}
+
impl Payload {
pub(super) fn from_cbor(
session: &Session,
bytes: &[u8],
config_format: ConfigFormat,
) -> Result<Self> {
- let f = PayloadFields::from_cbor(session, bytes, config_format)?;
+ let entries = cbor_map_from_slice(bytes)?;
+ let profile_version = PayloadFields::extract_profile_version(session, &entries)?;
+ Self::from_entries(&profile_version.into(), entries, config_format)
+ }
+
+ fn from_entries(
+ profile: &Profile,
+ entries: Vec<(Value, Value)>,
+ config_format: ConfigFormat,
+ ) -> Result<Self> {
+ let f = PayloadFields::from_entries(profile, entries, config_format)?;
PayloadBuilder::with_subject_public_key(f.subject_public_key)
.issuer(f.issuer)
.subject(f.subject)
@@ -93,6 +116,47 @@
bytes: &[u8],
config_format: ConfigFormat,
) -> Result<Self> {
+ let entries = cbor_map_from_slice(bytes)?;
+ let profile_version = Self::extract_profile_version(session, &entries)?;
+ Self::from_entries(&profile_version.into(), entries, config_format)
+ }
+
+ fn extract_profile_version(
+ session: &Session,
+ entries: &[(Value, Value)],
+ ) -> Result<ProfileVersion> {
+ let mut profile_name = FieldValue::new("profile name");
+ for (key, value) in entries.iter() {
+ if key == &Value::from(PROFILE_NAME) {
+ profile_name.set_once(value.clone())?;
+ }
+ }
+
+ let profile_version = match profile_name.into_optional_string()? {
+ None => {
+ let version = session.options.dice_profile_range.start();
+ ensure!(version <= ProfileVersion::Android14, "profile name is required");
+ version
+ }
+ Some(profile_name) => {
+ ProfileVersion::from_str(&profile_name).with_context(|| profile_name.clone())?
+ }
+ };
+ ensure!(
+ session.options.dice_profile_range.contains(profile_version),
+ "profile version \"{profile_version}\" is less than \"{}\" or greater than \"{}\"",
+ session.options.dice_profile_range.start(),
+ session.options.dice_profile_range.end(),
+ );
+
+ Ok(profile_version)
+ }
+
+ fn from_entries(
+ profile: &Profile,
+ entries: Vec<(Value, Value)>,
+ config_format: ConfigFormat,
+ ) -> Result<Self> {
let mut issuer = FieldValue::new("issuer");
let mut subject = FieldValue::new("subject");
let mut subject_public_key = FieldValue::new("subject public key");
@@ -104,8 +168,8 @@
let mut authority_desc = FieldValue::new("authority desc");
let mut authority_hash = FieldValue::new("authority hash");
let mut key_usage = FieldValue::new("key usage");
+ let mut profile_name = FieldValue::new("profile name");
- let entries = cbor_map_from_slice(bytes)?;
for (key, value) in entries.into_iter() {
if let Some(Ok(key)) = key.as_integer().map(TryInto::try_into) {
let field = match key {
@@ -120,6 +184,7 @@
AUTHORITY_DESC => &mut authority_desc,
AUTHORITY_HASH => &mut authority_hash,
KEY_USAGE => &mut key_usage,
+ PROFILE_NAME => &mut profile_name,
_ => bail!("Unknown key {}", key),
};
field.set_once(value)?
@@ -128,15 +193,15 @@
}
}
- validate_key_usage(session, key_usage)?;
+ validate_key_usage(profile, key_usage)?;
let (config_desc, config_hash) =
- validate_config(session, config_desc, config_hash, config_format).context("config")?;
+ validate_config(profile, config_desc, config_hash, config_format).context("config")?;
Ok(Self {
issuer: issuer.into_string()?,
subject: subject.into_string()?,
- subject_public_key: validate_subject_public_key(session, subject_public_key)?,
- mode: validate_mode(session, mode)?,
+ subject_public_key: validate_subject_public_key(profile, subject_public_key)?,
+ mode: validate_mode(profile, mode)?,
code_desc: code_desc.into_optional_bytes()?,
code_hash: code_hash.into_optional_bytes()?,
config_desc,
@@ -147,11 +212,11 @@
}
}
-fn validate_key_usage(session: &Session, key_usage: FieldValue) -> Result<()> {
+fn validate_key_usage(profile: &Profile, key_usage: FieldValue) -> Result<()> {
let key_usage = key_usage.into_bytes().context("key usage")?;
let key_cert_sign = 1 << 5;
if key_usage.len() > 1
- && session.options.dice_chain_allow_big_endian_key_usage
+ && profile.allow_big_endian_key_usage
&& key_usage[key_usage.len() - 1] == key_cert_sign
&& key_usage.iter().take(key_usage.len() - 1).all(|&x| x == 0)
{
@@ -167,19 +232,19 @@
}
fn validate_subject_public_key(
- session: &Session,
+ profile: &Profile,
subject_public_key: FieldValue,
) -> Result<PublicKey> {
let subject_public_key = subject_public_key.into_bytes()?;
let subject_public_key = value_from_bytes(&subject_public_key).context("decode CBOR")?;
- let subject_public_key = cose_key_from_cbor_value(session, subject_public_key)
+ let subject_public_key = cose_key_from_cbor_value(subject_public_key, profile.key_ops_type)
.context("parsing subject public key")?;
PublicKey::from_cose_key(&subject_public_key)
.context("parsing subject public key from COSE_key")
}
-fn validate_mode(session: &Session, mode: FieldValue) -> Result<Option<DiceMode>> {
- Ok(if !mode.is_bytes() && session.options.dice_chain_mode_type == ModeType::IntOrBytes {
+fn validate_mode(profile: &Profile, mode: FieldValue) -> Result<Option<DiceMode>> {
+ Ok(if !mode.is_bytes() && profile.mode_type == ModeType::IntOrBytes {
mode.into_optional_i64()?
} else {
mode.into_optional_bytes()?
@@ -200,7 +265,7 @@
}
fn validate_config(
- session: &Session,
+ profile: &Profile,
config_desc: FieldValue,
config_hash: FieldValue,
config_format: ConfigFormat,
@@ -208,11 +273,11 @@
let config_desc = config_desc.into_optional_bytes()?;
let config_hash = config_hash.into_optional_bytes()?;
if let Some(config_desc) = config_desc {
- let config = config_desc_from_slice(session, &config_desc).context("parsing descriptor");
- if config.is_err() && config_format == ConfigFormat::Permissive {
+ let config = config_desc_from_slice(profile, &config_desc).context("parsing descriptor");
+ if config.is_err() && config_format == ConfigFormat::AndroidOrIgnored {
return Ok((Some(ConfigDesc::default()), config_hash));
}
- if !session.options.dice_chain_config_hash_unverified {
+ if !profile.config_hash_unverified {
let Some(ref hash) = config_hash else { bail!("hash required") };
match hash.len() {
32 => ensure!(hash == &sha256(&config_desc)),
@@ -236,7 +301,7 @@
Ok(entries)
}
-fn config_desc_from_slice(session: &Session, bytes: &[u8]) -> Result<ConfigDesc> {
+fn config_desc_from_slice(profile: &Profile, bytes: &[u8]) -> Result<ConfigDesc> {
let entries = cbor_map_from_slice(bytes)?;
let mut component_name = FieldValue::new("component name");
@@ -277,7 +342,7 @@
Ok(ConfigDescBuilder::new()
.component_name(component_name.into_optional_string().context("Component name")?)
.component_version(
- validate_version(session, component_version).context("Component version")?,
+ validate_version(profile, component_version).context("Component version")?,
)
.resettable(resettable.is_null().context("Resettable")?)
.security_version(security_version.into_optional_u64().context("Security version")?)
@@ -285,11 +350,10 @@
.build())
}
-fn validate_version(session: &Session, field: FieldValue) -> Result<Option<ComponentVersion>> {
+fn validate_version(profile: &Profile, field: FieldValue) -> Result<Option<ComponentVersion>> {
Ok(
if !field.is_integer()
- && session.options.dice_chain_component_version_type
- == ComponentVersionType::IntOrString
+ && profile.component_version_type == ComponentVersionType::IntOrString
{
field.into_optional_string()?.map(ComponentVersion::String)
} else {
@@ -301,9 +365,10 @@
#[cfg(test)]
mod tests {
use super::*;
+ use crate::cbor::dice::KeyOpsType;
use crate::cbor::serialize;
use crate::publickey::testkeys::{PrivateKey, ED25519_KEY_PEM};
- use crate::session::{KeyOpsType, Options};
+ use crate::session::{DiceProfileRange, Options};
use ciborium::cbor;
use coset::iana::{self, EnumI64};
use coset::CborSerializable;
@@ -509,13 +574,11 @@
fn mode_int_debug() {
let mut fields = valid_payload_fields();
fields.insert(MODE, Value::from(2));
- let cbor = serialize_fields(fields);
- let session = Session { options: Options::default() };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
- let session = Session {
- options: Options { dice_chain_mode_type: ModeType::IntOrBytes, ..Options::default() },
- };
- let payload = Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+ let entries = encode_fields(fields);
+ Payload::from_entries(&Profile::default(), entries.clone(), ConfigFormat::Android)
+ .unwrap_err();
+ let profile = Profile { mode_type: ModeType::IntOrBytes, ..Profile::default() };
+ let payload = Payload::from_entries(&profile, entries, ConfigFormat::Android).unwrap();
assert_eq!(payload.mode(), DiceMode::Debug);
}
@@ -549,52 +612,44 @@
fn key_usage_big_endian() {
let mut fields = valid_payload_fields();
fields.insert(KEY_USAGE, Value::Bytes(vec![0x00, 0x20]));
- let cbor = serialize_fields(fields);
- let session = Session { options: Options::default() };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
- let session = Session {
- options: Options { dice_chain_allow_big_endian_key_usage: true, ..Options::default() },
- };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+ let entries = encode_fields(fields);
+ Payload::from_entries(&Profile::default(), entries.clone(), ConfigFormat::Android)
+ .unwrap_err();
+ let profile = Profile { allow_big_endian_key_usage: true, ..Profile::default() };
+ Payload::from_entries(&profile, entries, ConfigFormat::Android).unwrap();
}
#[test]
fn key_usage_big_endian_invalid() {
let mut fields = valid_payload_fields();
fields.insert(KEY_USAGE, Value::Bytes(vec![0x00, 0xfe, 0x20]));
- let cbor = serialize_fields(fields);
- let session = Session { options: Options::default() };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
- let session = Session {
- options: Options { dice_chain_allow_big_endian_key_usage: true, ..Options::default() },
- };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+ let entries = encode_fields(fields);
+ Payload::from_entries(&Profile::default(), entries.clone(), ConfigFormat::Android)
+ .unwrap_err();
+ let profile = Profile { allow_big_endian_key_usage: true, ..Profile::default() };
+ Payload::from_entries(&profile, entries, ConfigFormat::Android).unwrap_err();
}
#[test]
fn key_usage_invalid() {
let mut fields = valid_payload_fields();
fields.insert(KEY_USAGE, Value::Bytes(vec![0x00, 0x10]));
- let cbor = serialize_fields(fields);
- let session = Session { options: Options::default() };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
- let session = Session {
- options: Options { dice_chain_allow_big_endian_key_usage: true, ..Options::default() },
- };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+ let entries = encode_fields(fields);
+ Payload::from_entries(&Profile::default(), entries.clone(), ConfigFormat::Android)
+ .unwrap_err();
+ let profile = Profile { allow_big_endian_key_usage: true, ..Profile::default() };
+ Payload::from_entries(&profile, entries, ConfigFormat::Android).unwrap_err();
}
#[test]
fn key_usage_empty() {
let mut fields = valid_payload_fields();
fields.insert(KEY_USAGE, Value::Bytes(vec![]));
- let cbor = serialize_fields(fields);
- let session = Session { options: Options::default() };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
- let session = Session {
- options: Options { dice_chain_allow_big_endian_key_usage: true, ..Options::default() },
- };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+ let entries = encode_fields(fields);
+ Payload::from_entries(&Profile::default(), entries.clone(), ConfigFormat::Android)
+ .unwrap_err();
+ let profile = Profile { allow_big_endian_key_usage: true, ..Profile::default() };
+ Payload::from_entries(&profile, entries, ConfigFormat::Android).unwrap_err();
}
#[test]
@@ -665,7 +720,7 @@
let cbor = serialize_fields(fields);
let session = Session { options: Options::default() };
Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
- let payload = Payload::from_cbor(&session, &cbor, ConfigFormat::Permissive).unwrap();
+ let payload = Payload::from_cbor(&session, &cbor, ConfigFormat::AndroidOrIgnored).unwrap();
assert_eq!(payload.config_desc(), &ConfigDesc::default());
}
@@ -676,16 +731,12 @@
let config_hash = sha512(&config_desc).to_vec();
fields.insert(CONFIG_DESC, Value::Bytes(config_desc));
fields.insert(CONFIG_HASH, Value::Bytes(config_hash));
- let cbor = serialize_fields(fields);
- let session = Session {
- options: Options {
- dice_chain_component_version_type: ComponentVersionType::Int,
- ..Options::default()
- },
- };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
- let session = Session { options: Options::default() };
- let payload = Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+ let entries = encode_fields(fields);
+ let profile =
+ Profile { component_version_type: ComponentVersionType::Int, ..Profile::default() };
+ Payload::from_entries(&profile, entries.clone(), ConfigFormat::Android).unwrap_err();
+ let payload =
+ Payload::from_entries(&Profile::default(), entries, ConfigFormat::Android).unwrap();
assert_eq!(
payload.config_desc().component_version(),
Some(&ComponentVersion::String("It's version 4".to_string()))
@@ -736,9 +787,8 @@
fn config_hash_missing() {
let mut fields = valid_payload_fields();
fields.remove(&CONFIG_HASH);
- let cbor = serialize_fields(fields);
- let session = Session { options: Options::default() };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+ let entries = encode_fields(fields);
+ Payload::from_entries(&Profile::default(), entries, ConfigFormat::Android).unwrap_err();
}
#[test]
@@ -753,16 +803,119 @@
})
.unwrap();
fields.insert(SUBJECT_PUBLIC_KEY, Value::Bytes(serialize(subject_public_key)));
- let cbor = serialize_fields(fields);
- let session = Session { options: Options::default() };
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+ let entries = encode_fields(fields);
+ Payload::from_entries(&Profile::default(), entries.clone(), ConfigFormat::Android)
+ .unwrap_err();
+ let profile = Profile { key_ops_type: KeyOpsType::IntOrArray, ..Profile::default() };
+ Payload::from_entries(&profile, entries, ConfigFormat::Android).unwrap();
+ }
+
+ #[test]
+ fn extract_profile_version_named_profiles() {
+ let test_cases = [
+ ("android.14", ProfileVersion::Android14),
+ ("android.15", ProfileVersion::Android15),
+ ("android.16", ProfileVersion::Android16),
+ ];
+ for (profile_name, expected_version) in test_cases {
+ let mut fields = valid_payload_fields();
+ fields.insert(PROFILE_NAME, Value::from(profile_name));
+ let entries = encode_fields(fields);
+ let session = Session {
+ options: Options {
+ dice_profile_range: DiceProfileRange::new(expected_version, expected_version),
+ },
+ };
+ let profile_version =
+ PayloadFields::extract_profile_version(&session, &entries).unwrap();
+ assert_eq!(profile_version, expected_version);
+ }
+ }
+
+ #[test]
+ fn extract_profile_version_named_android_13_fails() {
let session = Session {
options: Options {
- dice_chain_key_ops_type: KeyOpsType::IntOrArray,
- ..Options::default()
+ dice_profile_range: DiceProfileRange::new(
+ ProfileVersion::Android13,
+ ProfileVersion::Android16,
+ ),
},
};
- Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+ let mut fields = valid_payload_fields();
+ fields.insert(PROFILE_NAME, Value::from("android.13"));
+ let entries = encode_fields(fields);
+ PayloadFields::extract_profile_version(&session, &entries).unwrap_err();
+ }
+
+ #[test]
+ fn extract_profile_version_multiple_profile_name_entries_fails() {
+ let session = Session {
+ options: Options {
+ dice_profile_range: DiceProfileRange::new(
+ ProfileVersion::Android13,
+ ProfileVersion::Android16,
+ ),
+ },
+ };
+ let mut fields = valid_payload_fields();
+ fields.insert(PROFILE_NAME, Value::from("android.15"));
+ let mut entries = encode_fields(fields);
+ entries.push((Value::from(PROFILE_NAME), Value::from("android.15")));
+ PayloadFields::extract_profile_version(&session, &entries).unwrap_err();
+ }
+
+ #[test]
+ fn extract_profile_version_out_of_range_fails() {
+ let session = Session {
+ options: Options {
+ dice_profile_range: DiceProfileRange::new(
+ ProfileVersion::Android15,
+ ProfileVersion::Android15,
+ ),
+ },
+ };
+ let mut fields = valid_payload_fields();
+ fields.insert(PROFILE_NAME, Value::from("android.14"));
+ let entries = encode_fields(fields.clone());
+ PayloadFields::extract_profile_version(&session, &entries).unwrap_err();
+ fields.insert(PROFILE_NAME, Value::from("android.16"));
+ let entries = encode_fields(fields);
+ PayloadFields::extract_profile_version(&session, &entries).unwrap_err();
+ }
+
+ #[test]
+ fn extract_profile_version_default_when_not_named_up_to_android_14() {
+ let entries = encode_fields(valid_payload_fields());
+ for expected_version in [ProfileVersion::Android13, ProfileVersion::Android14] {
+ let session = Session {
+ options: Options {
+ dice_profile_range: DiceProfileRange::new(
+ expected_version,
+ ProfileVersion::Android16,
+ ),
+ },
+ };
+ let profile_version =
+ PayloadFields::extract_profile_version(&session, &entries).unwrap();
+ assert_eq!(profile_version, expected_version);
+ }
+ }
+
+ #[test]
+ fn extract_profile_version_named_profile_required_from_android_15() {
+ let entries = encode_fields(valid_payload_fields());
+ for min_version in [ProfileVersion::Android15, ProfileVersion::Android16] {
+ let session = Session {
+ options: Options {
+ dice_profile_range: DiceProfileRange::new(
+ min_version,
+ ProfileVersion::Android16,
+ ),
+ },
+ };
+ PayloadFields::extract_profile_version(&session, &entries).unwrap_err();
+ }
}
fn valid_payload_fields() -> HashMap<i64, Value> {
@@ -783,8 +936,11 @@
])
}
- fn serialize_fields(mut fields: HashMap<i64, Value>) -> Vec<u8> {
- let value = Value::Map(fields.drain().map(|(k, v)| (Value::from(k), v)).collect());
- serialize(value)
+ fn encode_fields(mut fields: HashMap<i64, Value>) -> Vec<(Value, Value)> {
+ fields.drain().map(|(k, v)| (Value::from(k), v)).collect()
+ }
+
+ fn serialize_fields(fields: HashMap<i64, Value>) -> Vec<u8> {
+ serialize(Value::Map(encode_fields(fields)))
}
}
diff --git a/remote_provisioning/hwtrust/src/cbor/dice/profile.rs b/remote_provisioning/hwtrust/src/cbor/dice/profile.rs
new file mode 100644
index 0000000..308cd72
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/cbor/dice/profile.rs
@@ -0,0 +1,101 @@
+//! Defines the set of validation rules to apply for a DICE profile.
+
+use super::KeyOpsType;
+use crate::dice::ProfileVersion;
+
+/// Options that describe an Android Profile for DICE.
+#[derive(Default)]
+pub(super) struct Profile {
+ /// The types that are permitted for the key_ops field of COSE_Key objects in the DICE chain.
+ /// This option can be used for compatibility with the RKP HAL before v3 which diverged from
+ /// the COSE spec and allowed a single int instead of always requiring an array.
+ pub(super) key_ops_type: KeyOpsType,
+
+ /// The types that are permitted for the mode field of the DICE certificates. This option can
+ /// be used for compatibility with the RKP HAL v3 which allowed some deviations from the Open
+ /// Profile for DICE specification.
+ pub(super) mode_type: ModeType,
+
+ /// Whether to allow the key_usage field of the DICE certificates to be encoded in big-endian
+ /// byte order. This introduces ambiguity of the exact key usage being expressed but the keys
+ /// in the DICE chain are only used for verification so it may be preferable to allow for
+ /// compatibility with implementations that use the wrong endianness.
+ pub(super) allow_big_endian_key_usage: bool,
+
+ /// The types that are permitted for the component version field in the configuration
+ /// descriptor. The specification has changed the allowed types over time and this option
+ /// can be used to select which rules to apply.
+ pub(super) component_version_type: ComponentVersionType,
+
+ /// Whether the configuration hash is verified to be present and derived from the configuration
+ /// descriptor. This allows for compatibility with early versions of the RKP HAL which did not
+ /// enforce the requirements on the configuration hash as defined by the Open Profile for DICE.
+ pub(super) config_hash_unverified: bool,
+}
+
+/// Type allowed for the DICE certificate mode field.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub(super) enum ModeType {
+ /// The mode field must be a byte string holding a single byte as specified by the Open Profile
+ /// for DICE.
+ #[default]
+ Bytes,
+ /// The mode field can be either an int or a byte string holding a single byte.
+ IntOrBytes,
+}
+
+/// Type allowed for the DICE certificate configuration descriptor's component version field.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub(super) enum ComponentVersionType {
+ /// The component version can be either an int or a free-form string.
+ #[default]
+ IntOrString,
+ /// The component version must be an int.
+ Int,
+}
+
+impl Profile {
+ /// The rules for the profile used in Android 13.
+ pub(super) fn android13() -> Self {
+ Self {
+ // Context: b/262599829#comment65
+ key_ops_type: KeyOpsType::IntOrArray,
+ // Context: b/273552826
+ component_version_type: ComponentVersionType::Int,
+ config_hash_unverified: true,
+ ..Self::default()
+ }
+ }
+
+ /// The rules for the "android.14" profile.
+ pub(super) fn android14() -> Self {
+ Self {
+ // Context: b/273552826
+ mode_type: ModeType::IntOrBytes,
+ allow_big_endian_key_usage: true,
+ config_hash_unverified: true,
+ ..Self::default()
+ }
+ }
+
+ /// The rules for the "android.15" profile.
+ pub(super) fn android15() -> Self {
+ Self { config_hash_unverified: true, ..Self::default() }
+ }
+
+ /// The rules for the "android.16" profile..
+ pub(super) fn android16() -> Self {
+ Self::default()
+ }
+}
+
+impl From<ProfileVersion> for Profile {
+ fn from(version: ProfileVersion) -> Self {
+ match version {
+ ProfileVersion::Android13 => Profile::android13(),
+ ProfileVersion::Android14 => Profile::android14(),
+ ProfileVersion::Android15 => Profile::android15(),
+ ProfileVersion::Android16 => Profile::android16(),
+ }
+ }
+}
diff --git a/remote_provisioning/hwtrust/src/dice.rs b/remote_provisioning/hwtrust/src/dice.rs
index feaf984..7373abc 100644
--- a/remote_provisioning/hwtrust/src/dice.rs
+++ b/remote_provisioning/hwtrust/src/dice.rs
@@ -2,7 +2,9 @@
mod chain;
mod entry;
+mod profile;
pub use chain::{Chain, ChainForm, DegenerateChain};
pub use entry::{ComponentVersion, ConfigDesc, DiceMode, Payload};
pub(crate) use entry::{ConfigDescBuilder, PayloadBuilder};
+pub use profile::ProfileVersion;
diff --git a/remote_provisioning/hwtrust/src/dice/profile.rs b/remote_provisioning/hwtrust/src/dice/profile.rs
new file mode 100644
index 0000000..ae8d52f
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/dice/profile.rs
@@ -0,0 +1,54 @@
+use std::error::Error;
+use std::fmt::{self, Display, Formatter};
+use std::str::FromStr;
+
+/// Versions of the Android Profile for DICE.
+#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
+pub enum ProfileVersion {
+ /// The version of the Android Profile for DICE that aligns with Android 13.
+ Android13,
+ /// The version of the Android Profile for DICE that aligns with Android 14.
+ #[default]
+ Android14,
+ /// The version of the Android Profile for DICE that aligns with Android 15.
+ Android15,
+ /// The version of the Android Profile for DICE that aligns with Android 16.
+ Android16,
+}
+
+impl Display for ProfileVersion {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ let profile_name = match self {
+ Self::Android13 => "android.13",
+ Self::Android14 => "android.14",
+ Self::Android15 => "android.15",
+ Self::Android16 => "android.16",
+ };
+ write!(f, "{profile_name}",)
+ }
+}
+
+/// An error which can be returned when parsing an Android Profile for DICE version.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ParseProfileVersionError(());
+
+impl fmt::Display for ParseProfileVersionError {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ write!(fmt, "unknown profile version")
+ }
+}
+
+impl Error for ParseProfileVersionError {}
+
+impl FromStr for ProfileVersion {
+ type Err = ParseProfileVersionError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "android.14" => Self::Android14,
+ "android.15" => Self::Android15,
+ "android.16" => Self::Android16,
+ _ => return Err(ParseProfileVersionError(())),
+ })
+ }
+}
diff --git a/remote_provisioning/hwtrust/src/session.rs b/remote_provisioning/hwtrust/src/session.rs
index cc37cf3..2eaf563 100644
--- a/remote_provisioning/hwtrust/src/session.rs
+++ b/remote_provisioning/hwtrust/src/session.rs
@@ -1,5 +1,8 @@
//! Defines the context type for a session handling hwtrust data structures.
+use crate::dice::ProfileVersion;
+use std::ops::RangeInclusive;
+
/// The context for a session handling hwtrust data structures.
#[derive(Default)]
pub struct Session {
@@ -10,112 +13,80 @@
/// Options that control the behaviour of a session.
#[derive(Default)]
pub struct Options {
- /// The expected format for the configuration descriptor in the first certificate of the DICE
- /// chain. When the chain is ROM-rooted, the first certificate is generated by ROM so this
- /// option can be used for compatibility with ROMs.
- pub first_dice_chain_cert_config_format: ConfigFormat,
-
- /// The types that are permitted for the key_ops field of COSE_Key objects in the DICE chain.
- /// This option can be used for compatibility with the RKP HAL before v3 which diverged from
- /// the COSE spec and allowed a single int instead of always requiring an array.
- pub dice_chain_key_ops_type: KeyOpsType,
-
- /// The types that are permitted for the mode field of the DICE certificates. This option can
- /// be used for compatibility with the RKP HAL v3 which allowed some deviations from the Open
- /// Profile for DICE specification.
- pub dice_chain_mode_type: ModeType,
-
- /// Whether to allow the key_usage field of the DICE certificates to be encoded in big-endian
- /// byte order. This introduces ambiguity of the exact key usage being expressed but the keys
- /// in the DICE chain are only used for verification so it may be preferable to allow for
- /// compatibility with implementations that use the wrong endianness.
- pub dice_chain_allow_big_endian_key_usage: bool,
-
- /// The types that are permitted for the component version field in the configuration
- /// descriptor. The specification has changed the allowed types over time and this option
- /// can be used to select which rules to apply.
- pub dice_chain_component_version_type: ComponentVersionType,
-
- /// Whether the configuration hash is verified to be present and derived from the configuration
- /// descriptor. This allows for compatibility with early versions of the RKP HAL which did not
- /// enforce the requirements on the configuration hash as defined by the Open Profile for DICE.
- pub dice_chain_config_hash_unverified: bool,
+ /// The range of supported Android Profile for DICE versions.
+ pub dice_profile_range: DiceProfileRange,
}
-/// Format of the DICE configuration descriptor.
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum ConfigFormat {
- /// The configuration descriptor format specified by Android.
- #[default]
- Android,
- /// Any configuration descriptor format is allowed.
- Permissive,
+/// An inclusive range of Android Profile for DICE versions.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct DiceProfileRange(RangeInclusive<ProfileVersion>);
+
+impl DiceProfileRange {
+ /// Creates a new inclusive range of Android Profile for DICE versions.
+ pub fn new(start: ProfileVersion, end: ProfileVersion) -> Self {
+ Self(RangeInclusive::new(start, end))
+ }
+
+ /// Returns `true` if `version` is contained in the range.
+ pub fn contains(&self, version: ProfileVersion) -> bool {
+ self.0.contains(&version)
+ }
+
+ /// Returns the lower bound of the range.
+ pub fn start(&self) -> ProfileVersion {
+ *self.0.start()
+ }
+
+ /// Returns the upper bound of the range.
+ pub fn end(&self) -> ProfileVersion {
+ *self.0.end()
+ }
}
-/// Type allowed for the COSE_Key object key_ops field in the DICE chain.
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum KeyOpsType {
- /// The key_ops field must be an array as specified in the COSE RFC.
- #[default]
- Array,
- /// The key_ops field can be either a single int or an array as specified in the COSE RFC.
- IntOrArray,
-}
-
-/// Type allowed for the DICE certificate mode field.
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum ModeType {
- /// The mode field must be a byte string holding a single byte as specified by the Open Profile
- /// for DICE.
- #[default]
- Bytes,
- /// The mode field can be either an int or a byte string holding a single byte.
- IntOrBytes,
-}
-
-/// Type allowed for the DICE certificate configuration descriptor's component version field.
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum ComponentVersionType {
- /// The component version can be either an int or a free-form string.
- #[default]
- IntOrString,
- /// The component version must be an int.
- Int,
+impl Default for DiceProfileRange {
+ fn default() -> Self {
+ Self::new(ProfileVersion::Android14, ProfileVersion::Android16)
+ }
}
impl Options {
/// The options use by VSR 13.
pub fn vsr13() -> Self {
Self {
- // Context: b/262599829#comment65
- dice_chain_key_ops_type: KeyOpsType::IntOrArray,
- // Context: b/273552826
- dice_chain_component_version_type: ComponentVersionType::Int,
- dice_chain_config_hash_unverified: true,
- ..Options::default()
+ dice_profile_range: DiceProfileRange::new(
+ ProfileVersion::Android13,
+ ProfileVersion::Android13,
+ ),
}
}
/// The options use by VSR 14.
pub fn vsr14() -> Self {
Self {
- // Context: b/261647022
- first_dice_chain_cert_config_format: ConfigFormat::Permissive,
- // Context: b/273552826
- dice_chain_mode_type: ModeType::IntOrBytes,
- dice_chain_allow_big_endian_key_usage: true,
- dice_chain_config_hash_unverified: true,
- ..Options::default()
+ dice_profile_range: DiceProfileRange::new(
+ ProfileVersion::Android14,
+ ProfileVersion::Android14,
+ ),
}
}
/// The options use by VSR 15.
pub fn vsr15() -> Self {
- Self { dice_chain_config_hash_unverified: true, ..Options::default() }
+ Self {
+ dice_profile_range: DiceProfileRange::new(
+ ProfileVersion::Android14,
+ ProfileVersion::Android15,
+ ),
+ }
}
/// The options use by VSR 16.
pub fn vsr16() -> Self {
- Options::default()
+ Self {
+ dice_profile_range: DiceProfileRange::new(
+ ProfileVersion::Android14,
+ ProfileVersion::Android16,
+ ),
+ }
}
}