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,
+            ),
+        }
     }
 }