Move bcc.entry module to its own file
This is a pure refactoring - no behavior changes are intended; I
literally just cut'n'pasted then fixed up the imports.
Suggested by Andrew to conform to the style guide in
https://r.android.com/c/platform/tools/security/+/2055890/2..5/remote_provisioning/cert_validator/src/bcc.rs#b97.
Bug: 225177477
Test: atest libcert_request_validator_tests
Change-Id: I4c3f51f93642b1e4a23b743f4dae758261ddd298
diff --git a/remote_provisioning/cert_validator/src/bcc.rs b/remote_provisioning/cert_validator/src/bcc.rs
index f157cf7..32d7494 100644
--- a/remote_provisioning/cert_validator/src/bcc.rs
+++ b/remote_provisioning/cert_validator/src/bcc.rs
@@ -1,20 +1,16 @@
//! This module provides functions for validating chains of bcc certificates
-use crate::dice;
-use crate::publickey;
-use crate::valueas::ValueAs;
+pub mod entry;
use self::entry::SubjectPublicKey;
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{anyhow, Context, Result};
use coset::AsCborValue;
use coset::{
cbor::value::Value::{self, Array},
- iana::{self, EnumI64},
- Algorithm, CborSerializable,
+ CborSerializable,
CoseError::{self, EncodeFailed, UnexpectedItem},
- CoseKey, CoseSign1, Header, RegisteredLabel,
+ CoseKey, CoseSign1,
};
-use std::fmt;
use std::io::Read;
/// Represents a full Boot Certificate Chain (BCC). This consists of the root public key (which
@@ -113,376 +109,3 @@
pub fn get_label_value_as_bytes(key: &coset::CoseKey, label: i64) -> Result<&Vec<u8>> {
get_label_value(key, label)?.as_bytes().ok_or_else(|| anyhow!("Value not a bstr."))
}
-/// This module wraps the certificate validation functions intended for BccEntry.
-pub mod entry {
- use std::fmt::{Display, Formatter, Write};
-
- use super::*;
-
- /// Read a series of bcc file certificates and verify that the public key of
- /// any given cert's payload in the series correctly signs the next cose
- /// sign1 cert.
- pub fn check_sign1_cert_chain(certs: &[&str]) -> Result<()> {
- ensure!(!certs.is_empty());
- let mut payload = Payload::from_sign1(&read(certs[0])?)
- .context("Failed to read the first bccEntry payload")?;
- for item in certs.iter().skip(1) {
- payload.check().context("Validation of BccPayload entries failed.")?;
- payload =
- payload.check_sign1(&read(item).context("Failed to read the bccEntry payload")?)?;
- }
- Ok(())
- }
-
- /// Read a given cbor array containing bcc entries and verify that the public key
- /// of any given cert's payload in the series correctly signs the next cose sign1
- /// cert.
- pub fn check_sign1_chain_array(cbor_arr: &[Value]) -> Result<()> {
- ensure!(!cbor_arr.is_empty());
-
- let mut writeme: Vec<u8> = Vec::new();
- ciborium::ser::into_writer(&cbor_arr[0], &mut writeme)?;
- let mut payload =
- Payload::from_sign1(&CoseSign1::from_slice(&writeme).map_err(cose_error)?)
- .context("Failed to read bccEntry payload")?;
- for item in cbor_arr.iter().skip(1) {
- payload.check().context("Validation of BccPayload entries failed")?;
- writeme = Vec::new();
- ciborium::ser::into_writer(item, &mut writeme)?;
- let next_sign1 = &CoseSign1::from_slice(&writeme).map_err(cose_error)?;
- payload = payload.check_sign1(next_sign1).context("Failed to read bccEntry payload")?;
- }
- Ok(())
- }
-
- /// Read a file name as string and create the BccEntry as COSE_sign1 structure.
- pub fn read(fname: &str) -> Result<CoseSign1> {
- let mut f = std::fs::File::open(fname)?;
- let mut content = Vec::new();
- f.read_to_end(&mut content)?;
- CoseSign1::from_slice(&content).map_err(cose_error)
- }
-
- /// Validate the protected header of a bcc entry with respect to the provided
- /// alg (typically originating from the subject public key of the payload).
- pub fn check_protected_header(alg: &Option<Algorithm>, header: &Header) -> Result<()> {
- ensure!(&header.alg == alg);
- ensure!(header
- .crit
- .iter()
- .all(|l| l == &RegisteredLabel::Assigned(iana::HeaderParameter::Alg)));
- Ok(())
- }
- /// Struct describing BccPayload cbor of the BccEntry.
- #[derive(Debug)]
- pub struct Payload(Value);
- impl Payload {
- /// Construct the Payload from the parent BccEntry COSE_sign1 structure.
- pub fn from_sign1(sign1: &CoseSign1) -> Result<Payload> {
- Self::from_slice(sign1.payload.as_ref().ok_or_else(|| anyhow!("no payload"))?)
- }
-
- /// Validate entries in the Payload to be correct.
- pub fn check(&self) -> Result<()> {
- // Validate required fields.
- self.map_lookup(dice::ISS)?.as_string()?;
- self.map_lookup(dice::SUB)?.as_string()?;
- SubjectPublicKey::from_payload(self)?.check().context("Public key failed checking")?;
- self.map_lookup(dice::KEY_USAGE)?
- .as_bytes()
- .ok_or_else(|| anyhow!("Payload Key usage not bytes"))?;
-
- // Validate required and optional fields. The required fields are those defined
- // to be present for CDI_Certificates in the open-DICE profile.
- // TODO: Check if the optional fields are present, and if so, ensure that
- // the operations applied to the mandatory fields actually reproduce the
- // values in the optional fields as specified in open-DICE.
- self.0.map_lookup(dice::CODE_HASH).context("Code hash must be present.")?;
- self.0.map_lookup(dice::CONFIG_DESC).context("Config descriptor must be present.")?;
- self.0.map_lookup(dice::AUTHORITY_HASH).context("Authority hash must be present.")?;
- self.0.map_lookup(dice::MODE).context("Mode must be present.")?;
-
- // Verify that each key that does exist has the expected type.
- self.0
- .check_bytes_val_if_key_in_map(dice::CODE_HASH)
- .context("Code Hash value not bytes.")?;
- self.0
- .check_bytes_val_if_key_in_map(dice::CODE_DESC)
- .context("Code Descriptor value not bytes.")?;
- self.0
- .check_bytes_val_if_key_in_map(dice::CONFIG_HASH)
- .context("Configuration Hash value not bytes.")?;
- self.0
- .check_bytes_val_if_key_in_map(dice::CONFIG_DESC)
- .context("Configuration descriptor value not bytes.")?;
- self.0
- .check_bytes_val_if_key_in_map(dice::AUTHORITY_HASH)
- .context("Authority Hash value not bytes.")?;
- self.0
- .check_bytes_val_if_key_in_map(dice::AUTHORITY_DESC)
- .context("Authority descriptor value not bytes.")?;
- self.0.check_bytes_val_if_key_in_map(dice::MODE).context("Mode value not bytes.")?;
- Ok(())
- }
-
- /// Verify that the public key of this payload correctly signs the provided
- /// BccEntry sign1 object.
- pub fn check_sign1(&self, sign1: &CoseSign1) -> Result<Payload> {
- let pkey = SubjectPublicKey::from_payload(self)
- .context("Failed to construct Public key from the Bcc payload.")?;
- let new_payload = Self::check_sign1_signature(&pkey, sign1)?;
- ensure!(
- self.map_lookup(dice::SUB)? == new_payload.map_lookup(dice::ISS)?,
- "Subject/Issuer mismatch"
- );
- Ok(new_payload)
- }
-
- pub(super) fn check_sign1_signature(
- pkey: &SubjectPublicKey,
- sign1: &CoseSign1,
- ) -> Result<Payload> {
- check_protected_header(&pkey.0.alg, &sign1.protected.header)
- .context("Validation of bcc entry protected header failed.")?;
- let v = publickey::PublicKey::from_cose_key(&pkey.0)
- .context("Extracting the Public key from coseKey failed.")?;
- sign1
- .verify_signature(b"", |s, m| v.verify(s, m, &pkey.0.alg))
- .context("public key incorrectly signs the given cose_sign1 cert.")?;
- let new_payload = Payload::from_sign1(sign1)
- .context("Failed to extract bcc payload from cose_sign1")?;
- Ok(new_payload)
- }
-
- fn from_slice(b: &[u8]) -> Result<Self> {
- Ok(Payload(coset::cbor::de::from_reader(b).map_err(|e| anyhow!("CborError: {}", e))?))
- }
-
- fn map_lookup(&self, key: i64) -> Result<&Value> {
- Ok(&self
- .0
- .as_map()
- .ok_or_else(|| anyhow!("not a map"))?
- .iter()
- .find(|(k, _v)| k == &Value::from(key))
- .ok_or_else(|| anyhow!("missing key {}", key))?
- .1)
- }
- }
-
- /// Struct wrapping the CoseKey for BccEntry.BccPayload.SubjectPublicKey
- /// and the methods used for its validation.
- pub struct SubjectPublicKey(CoseKey);
- impl SubjectPublicKey {
- pub(super) fn from_cose_key(cose_key: CoseKey) -> Self {
- Self(cose_key)
- }
-
- /// Construct the SubjectPublicKey from the (bccEntry's) Payload.
- pub fn from_payload(payload: &Payload) -> Result<SubjectPublicKey> {
- let bytes = payload
- .map_lookup(dice::SUBJECT_PUBLIC_KEY)?
- .as_bytes()
- .ok_or_else(|| anyhow!("public key not bytes"))?;
- Self::from_slice(bytes)
- }
-
- fn from_slice(bytes: &[u8]) -> Result<SubjectPublicKey> {
- Ok(SubjectPublicKey(CoseKey::from_slice(bytes).map_err(cose_error)?))
- }
-
- /// Perform validation on the items in the public key.
- pub fn check(&self) -> Result<()> {
- let pkey = &self.0;
- if !pkey.key_ops.is_empty() {
- ensure!(pkey
- .key_ops
- .contains(&coset::KeyOperation::Assigned(iana::KeyOperation::Verify)));
- }
- match pkey.kty {
- coset::KeyType::Assigned(iana::KeyType::OKP) => {
- ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)));
- let crv = get_label_value(pkey, iana::OkpKeyParameter::Crv as i64)?;
- ensure!(crv == &Value::from(iana::EllipticCurve::Ed25519 as i64));
- }
- coset::KeyType::Assigned(iana::KeyType::EC2) => {
- ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::ES256)));
- let crv = get_label_value(pkey, iana::Ec2KeyParameter::Crv as i64)?;
- ensure!(crv == &Value::from(iana::EllipticCurve::P_256 as i64));
- }
- _ => bail!("Unexpected KeyType value: {:?}", pkey.kty),
- }
- Ok(())
- }
- }
-
- struct ConfigDesc(Vec<(Value, Value)>);
-
- impl AsCborValue for ConfigDesc {
- /*
- * CDDL (from keymint/ProtectedData.aidl):
- *
- * bstr .cbor { // Configuration Descriptor
- * ? -70002 : tstr, // Component name
- * ? -70003 : int, // Firmware version
- * ? -70004 : null, // Resettable
- * },
- */
-
- fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
- match value {
- Value::Map(m) => Ok(Self(m)),
- _ => Err(UnexpectedItem("something", "a map")),
- }
- }
-
- fn to_cbor_value(self) -> Result<Value, CoseError> {
- // TODO: Implement when needed
- Err(EncodeFailed)
- }
- }
-
- impl CborSerializable for ConfigDesc {}
-
- impl Display for ConfigDesc {
- fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
- write_payload_label(f, dice::CONFIG_DESC)?;
- f.write_str(":\n")?;
- for (label, value) in &self.0 {
- f.write_str(" ")?;
- if let Ok(i) = label.as_i64() {
- write_config_desc_label(f, i)?;
- } else {
- write_value(f, label)?;
- }
- f.write_str(": ")?;
- write_value(f, value)?;
- f.write_char('\n')?;
- }
- Ok(())
- }
- }
-
- fn write_config_desc_label(f: &mut Formatter, label: i64) -> Result<(), fmt::Error> {
- match label {
- dice::COMPONENT_NAME => f.write_str("Component Name"),
- dice::FIRMWARE_VERSION => f.write_str("Firmware Version"),
- dice::RESETTABLE => f.write_str("Resettable"),
- _ => write!(f, "{}", label),
- }
- }
-
- impl Display for SubjectPublicKey {
- fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
- let pkey = &self.0;
- if pkey.kty != coset::KeyType::Assigned(iana::KeyType::OKP)
- || pkey.alg != Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA))
- {
- return Err(fmt::Error);
- }
-
- let mut separator = "";
- for (label, value) in &pkey.params {
- use coset::Label;
- use iana::OkpKeyParameter;
- if let Label::Int(i) = label {
- match OkpKeyParameter::from_i64(*i) {
- Some(OkpKeyParameter::Crv) => {
- if let Some(crv) =
- value.as_i64().ok().and_then(iana::EllipticCurve::from_i64)
- {
- f.write_str(separator)?;
- write!(f, "Curve: {:?}", crv)?;
- separator = " ";
- }
- }
- Some(OkpKeyParameter::X) => {
- if let Ok(x) = ValueAs::as_bytes(value) {
- f.write_str(separator)?;
- f.write_str("X: ")?;
- write_bytes_in_hex(f, x)?;
- separator = " ";
- }
- }
- _ => (),
- }
- }
- }
-
- Ok(())
- }
- }
-
- impl Display for Payload {
- fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
- for (label, value) in self.0.as_map().ok_or(fmt::Error)? {
- if let Ok(i) = label.as_i64() {
- if i == dice::CONFIG_DESC {
- write_config_desc(f, value)?;
- continue;
- } else if i == dice::SUBJECT_PUBLIC_KEY {
- write_payload_label(f, i)?;
- f.write_str(": ")?;
- write_subject_public_key(f, value)?;
- continue;
- }
- write_payload_label(f, i)?;
- } else {
- write_value(f, label)?;
- }
- f.write_str(": ")?;
- write_value(f, value)?;
- f.write_char('\n')?;
- }
- Ok(())
- }
- }
-
- fn write_payload_label(f: &mut Formatter, label: i64) -> Result<(), fmt::Error> {
- match label {
- dice::ISS => f.write_str("Issuer"),
- dice::SUB => f.write_str("Subject"),
- dice::CODE_HASH => f.write_str("Code Hash"),
- dice::CODE_DESC => f.write_str("Code Desc"),
- dice::CONFIG_DESC => f.write_str("Config Desc"),
- dice::CONFIG_HASH => f.write_str("Config Hash"),
- dice::AUTHORITY_HASH => f.write_str("Authority Hash"),
- dice::AUTHORITY_DESC => f.write_str("Authority Desc"),
- dice::MODE => f.write_str("Mode"),
- dice::SUBJECT_PUBLIC_KEY => f.write_str("Subject Public Key"),
- dice::KEY_USAGE => f.write_str("Key Usage"),
- _ => write!(f, "{}", label),
- }
- }
-
- fn write_config_desc(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
- let bytes = value.as_bytes().ok_or(fmt::Error)?;
- let config_desc = ConfigDesc::from_slice(bytes).map_err(|_| fmt::Error)?;
- write!(f, "{}", config_desc)
- }
-
- fn write_subject_public_key(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
- let bytes = value.as_bytes().ok_or(fmt::Error)?;
- let subject_public_key = SubjectPublicKey::from_slice(bytes).map_err(|_| fmt::Error)?;
- write!(f, "{}", subject_public_key)
- }
-
- fn write_value(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
- if let Some(bytes) = value.as_bytes() {
- write_bytes_in_hex(f, bytes)
- } else if let Some(text) = value.as_text() {
- write!(f, "\"{}\"", text)
- } else if let Ok(integer) = value.as_i64() {
- write!(f, "{}", integer)
- } else {
- write!(f, "{:?}", value)
- }
- }
-
- fn write_bytes_in_hex(f: &mut Formatter, bytes: &[u8]) -> Result<(), fmt::Error> {
- for b in bytes {
- write!(f, "{:02x}", b)?
- }
- Ok(())
- }
-}
diff --git a/remote_provisioning/cert_validator/src/bcc/entry.rs b/remote_provisioning/cert_validator/src/bcc/entry.rs
new file mode 100644
index 0000000..2ebc12e
--- /dev/null
+++ b/remote_provisioning/cert_validator/src/bcc/entry.rs
@@ -0,0 +1,383 @@
+//! This module wraps the certificate validation functions intended for BccEntry.
+
+use super::{cose_error, get_label_value};
+use crate::dice;
+use crate::publickey;
+use crate::valueas::ValueAs;
+use anyhow::{anyhow, bail, ensure, Context, Result};
+use coset::AsCborValue;
+use coset::{
+ cbor::value::Value,
+ iana::{self, EnumI64},
+ Algorithm, CborSerializable,
+ CoseError::{self, EncodeFailed, UnexpectedItem},
+ CoseKey, CoseSign1, Header, RegisteredLabel,
+};
+use std::fmt::{self, Display, Formatter, Write};
+use std::io::Read;
+
+/// Read a series of bcc file certificates and verify that the public key of
+/// any given cert's payload in the series correctly signs the next cose
+/// sign1 cert.
+pub fn check_sign1_cert_chain(certs: &[&str]) -> Result<()> {
+ ensure!(!certs.is_empty());
+ let mut payload = Payload::from_sign1(&read(certs[0])?)
+ .context("Failed to read the first bccEntry payload")?;
+ for item in certs.iter().skip(1) {
+ payload.check().context("Validation of BccPayload entries failed.")?;
+ payload =
+ payload.check_sign1(&read(item).context("Failed to read the bccEntry payload")?)?;
+ }
+ Ok(())
+}
+
+/// Read a given cbor array containing bcc entries and verify that the public key
+/// of any given cert's payload in the series correctly signs the next cose sign1
+/// cert.
+pub fn check_sign1_chain_array(cbor_arr: &[Value]) -> Result<()> {
+ ensure!(!cbor_arr.is_empty());
+
+ let mut writeme: Vec<u8> = Vec::new();
+ ciborium::ser::into_writer(&cbor_arr[0], &mut writeme)?;
+ let mut payload = Payload::from_sign1(&CoseSign1::from_slice(&writeme).map_err(cose_error)?)
+ .context("Failed to read bccEntry payload")?;
+ for item in cbor_arr.iter().skip(1) {
+ payload.check().context("Validation of BccPayload entries failed")?;
+ writeme = Vec::new();
+ ciborium::ser::into_writer(item, &mut writeme)?;
+ let next_sign1 = &CoseSign1::from_slice(&writeme).map_err(cose_error)?;
+ payload = payload.check_sign1(next_sign1).context("Failed to read bccEntry payload")?;
+ }
+ Ok(())
+}
+
+/// Read a file name as string and create the BccEntry as COSE_sign1 structure.
+pub fn read(fname: &str) -> Result<CoseSign1> {
+ let mut f = std::fs::File::open(fname)?;
+ let mut content = Vec::new();
+ f.read_to_end(&mut content)?;
+ CoseSign1::from_slice(&content).map_err(cose_error)
+}
+
+/// Validate the protected header of a bcc entry with respect to the provided
+/// alg (typically originating from the subject public key of the payload).
+pub fn check_protected_header(alg: &Option<Algorithm>, header: &Header) -> Result<()> {
+ ensure!(&header.alg == alg);
+ ensure!(header
+ .crit
+ .iter()
+ .all(|l| l == &RegisteredLabel::Assigned(iana::HeaderParameter::Alg)));
+ Ok(())
+}
+/// Struct describing BccPayload cbor of the BccEntry.
+#[derive(Debug)]
+pub struct Payload(Value);
+impl Payload {
+ /// Construct the Payload from the parent BccEntry COSE_sign1 structure.
+ pub fn from_sign1(sign1: &CoseSign1) -> Result<Payload> {
+ Self::from_slice(sign1.payload.as_ref().ok_or_else(|| anyhow!("no payload"))?)
+ }
+
+ /// Validate entries in the Payload to be correct.
+ pub fn check(&self) -> Result<()> {
+ // Validate required fields.
+ self.map_lookup(dice::ISS)?.as_string()?;
+ self.map_lookup(dice::SUB)?.as_string()?;
+ SubjectPublicKey::from_payload(self)?.check().context("Public key failed checking")?;
+ self.map_lookup(dice::KEY_USAGE)?
+ .as_bytes()
+ .ok_or_else(|| anyhow!("Payload Key usage not bytes"))?;
+
+ // Validate required and optional fields. The required fields are those defined
+ // to be present for CDI_Certificates in the open-DICE profile.
+ // TODO: Check if the optional fields are present, and if so, ensure that
+ // the operations applied to the mandatory fields actually reproduce the
+ // values in the optional fields as specified in open-DICE.
+ self.0.map_lookup(dice::CODE_HASH).context("Code hash must be present.")?;
+ self.0.map_lookup(dice::CONFIG_DESC).context("Config descriptor must be present.")?;
+ self.0.map_lookup(dice::AUTHORITY_HASH).context("Authority hash must be present.")?;
+ self.0.map_lookup(dice::MODE).context("Mode must be present.")?;
+
+ // Verify that each key that does exist has the expected type.
+ self.0
+ .check_bytes_val_if_key_in_map(dice::CODE_HASH)
+ .context("Code Hash value not bytes.")?;
+ self.0
+ .check_bytes_val_if_key_in_map(dice::CODE_DESC)
+ .context("Code Descriptor value not bytes.")?;
+ self.0
+ .check_bytes_val_if_key_in_map(dice::CONFIG_HASH)
+ .context("Configuration Hash value not bytes.")?;
+ self.0
+ .check_bytes_val_if_key_in_map(dice::CONFIG_DESC)
+ .context("Configuration descriptor value not bytes.")?;
+ self.0
+ .check_bytes_val_if_key_in_map(dice::AUTHORITY_HASH)
+ .context("Authority Hash value not bytes.")?;
+ self.0
+ .check_bytes_val_if_key_in_map(dice::AUTHORITY_DESC)
+ .context("Authority descriptor value not bytes.")?;
+ self.0.check_bytes_val_if_key_in_map(dice::MODE).context("Mode value not bytes.")?;
+ Ok(())
+ }
+
+ /// Verify that the public key of this payload correctly signs the provided
+ /// BccEntry sign1 object.
+ pub fn check_sign1(&self, sign1: &CoseSign1) -> Result<Payload> {
+ let pkey = SubjectPublicKey::from_payload(self)
+ .context("Failed to construct Public key from the Bcc payload.")?;
+ let new_payload = Self::check_sign1_signature(&pkey, sign1)?;
+ ensure!(
+ self.map_lookup(dice::SUB)? == new_payload.map_lookup(dice::ISS)?,
+ "Subject/Issuer mismatch"
+ );
+ Ok(new_payload)
+ }
+
+ pub(super) fn check_sign1_signature(
+ pkey: &SubjectPublicKey,
+ sign1: &CoseSign1,
+ ) -> Result<Payload> {
+ check_protected_header(&pkey.0.alg, &sign1.protected.header)
+ .context("Validation of bcc entry protected header failed.")?;
+ let v = publickey::PublicKey::from_cose_key(&pkey.0)
+ .context("Extracting the Public key from coseKey failed.")?;
+ sign1
+ .verify_signature(b"", |s, m| v.verify(s, m, &pkey.0.alg))
+ .context("public key incorrectly signs the given cose_sign1 cert.")?;
+ let new_payload =
+ Payload::from_sign1(sign1).context("Failed to extract bcc payload from cose_sign1")?;
+ Ok(new_payload)
+ }
+
+ fn from_slice(b: &[u8]) -> Result<Self> {
+ Ok(Payload(coset::cbor::de::from_reader(b).map_err(|e| anyhow!("CborError: {}", e))?))
+ }
+
+ fn map_lookup(&self, key: i64) -> Result<&Value> {
+ Ok(&self
+ .0
+ .as_map()
+ .ok_or_else(|| anyhow!("not a map"))?
+ .iter()
+ .find(|(k, _v)| k == &Value::from(key))
+ .ok_or_else(|| anyhow!("missing key {}", key))?
+ .1)
+ }
+}
+
+/// Struct wrapping the CoseKey for BccEntry.BccPayload.SubjectPublicKey
+/// and the methods used for its validation.
+pub struct SubjectPublicKey(CoseKey);
+impl SubjectPublicKey {
+ pub(super) fn from_cose_key(cose_key: CoseKey) -> Self {
+ Self(cose_key)
+ }
+
+ /// Construct the SubjectPublicKey from the (bccEntry's) Payload.
+ pub fn from_payload(payload: &Payload) -> Result<SubjectPublicKey> {
+ let bytes = payload
+ .map_lookup(dice::SUBJECT_PUBLIC_KEY)?
+ .as_bytes()
+ .ok_or_else(|| anyhow!("public key not bytes"))?;
+ Self::from_slice(bytes)
+ }
+
+ fn from_slice(bytes: &[u8]) -> Result<SubjectPublicKey> {
+ Ok(SubjectPublicKey(CoseKey::from_slice(bytes).map_err(cose_error)?))
+ }
+
+ /// Perform validation on the items in the public key.
+ pub fn check(&self) -> Result<()> {
+ let pkey = &self.0;
+ if !pkey.key_ops.is_empty() {
+ ensure!(pkey
+ .key_ops
+ .contains(&coset::KeyOperation::Assigned(iana::KeyOperation::Verify)));
+ }
+ match pkey.kty {
+ coset::KeyType::Assigned(iana::KeyType::OKP) => {
+ ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)));
+ let crv = get_label_value(pkey, iana::OkpKeyParameter::Crv as i64)?;
+ ensure!(crv == &Value::from(iana::EllipticCurve::Ed25519 as i64));
+ }
+ coset::KeyType::Assigned(iana::KeyType::EC2) => {
+ ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::ES256)));
+ let crv = get_label_value(pkey, iana::Ec2KeyParameter::Crv as i64)?;
+ ensure!(crv == &Value::from(iana::EllipticCurve::P_256 as i64));
+ }
+ _ => bail!("Unexpected KeyType value: {:?}", pkey.kty),
+ }
+ Ok(())
+ }
+}
+
+struct ConfigDesc(Vec<(Value, Value)>);
+
+impl AsCborValue for ConfigDesc {
+ /*
+ * CDDL (from keymint/ProtectedData.aidl):
+ *
+ * bstr .cbor { // Configuration Descriptor
+ * ? -70002 : tstr, // Component name
+ * ? -70003 : int, // Firmware version
+ * ? -70004 : null, // Resettable
+ * },
+ */
+
+ fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
+ match value {
+ Value::Map(m) => Ok(Self(m)),
+ _ => Err(UnexpectedItem("something", "a map")),
+ }
+ }
+
+ fn to_cbor_value(self) -> Result<Value, CoseError> {
+ // TODO: Implement when needed
+ Err(EncodeFailed)
+ }
+}
+
+impl CborSerializable for ConfigDesc {}
+
+impl Display for ConfigDesc {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+ write_payload_label(f, dice::CONFIG_DESC)?;
+ f.write_str(":\n")?;
+ for (label, value) in &self.0 {
+ f.write_str(" ")?;
+ if let Ok(i) = label.as_i64() {
+ write_config_desc_label(f, i)?;
+ } else {
+ write_value(f, label)?;
+ }
+ f.write_str(": ")?;
+ write_value(f, value)?;
+ f.write_char('\n')?;
+ }
+ Ok(())
+ }
+}
+
+fn write_config_desc_label(f: &mut Formatter, label: i64) -> Result<(), fmt::Error> {
+ match label {
+ dice::COMPONENT_NAME => f.write_str("Component Name"),
+ dice::FIRMWARE_VERSION => f.write_str("Firmware Version"),
+ dice::RESETTABLE => f.write_str("Resettable"),
+ _ => write!(f, "{}", label),
+ }
+}
+
+impl Display for SubjectPublicKey {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+ let pkey = &self.0;
+ if pkey.kty != coset::KeyType::Assigned(iana::KeyType::OKP)
+ || pkey.alg != Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA))
+ {
+ return Err(fmt::Error);
+ }
+
+ let mut separator = "";
+ for (label, value) in &pkey.params {
+ use coset::Label;
+ use iana::OkpKeyParameter;
+ if let Label::Int(i) = label {
+ match OkpKeyParameter::from_i64(*i) {
+ Some(OkpKeyParameter::Crv) => {
+ if let Some(crv) =
+ value.as_i64().ok().and_then(iana::EllipticCurve::from_i64)
+ {
+ f.write_str(separator)?;
+ write!(f, "Curve: {:?}", crv)?;
+ separator = " ";
+ }
+ }
+ Some(OkpKeyParameter::X) => {
+ if let Ok(x) = ValueAs::as_bytes(value) {
+ f.write_str(separator)?;
+ f.write_str("X: ")?;
+ write_bytes_in_hex(f, x)?;
+ separator = " ";
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl Display for Payload {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+ for (label, value) in self.0.as_map().ok_or(fmt::Error)? {
+ if let Ok(i) = label.as_i64() {
+ if i == dice::CONFIG_DESC {
+ write_config_desc(f, value)?;
+ continue;
+ } else if i == dice::SUBJECT_PUBLIC_KEY {
+ write_payload_label(f, i)?;
+ f.write_str(": ")?;
+ write_subject_public_key(f, value)?;
+ continue;
+ }
+ write_payload_label(f, i)?;
+ } else {
+ write_value(f, label)?;
+ }
+ f.write_str(": ")?;
+ write_value(f, value)?;
+ f.write_char('\n')?;
+ }
+ Ok(())
+ }
+}
+
+fn write_payload_label(f: &mut Formatter, label: i64) -> Result<(), fmt::Error> {
+ match label {
+ dice::ISS => f.write_str("Issuer"),
+ dice::SUB => f.write_str("Subject"),
+ dice::CODE_HASH => f.write_str("Code Hash"),
+ dice::CODE_DESC => f.write_str("Code Desc"),
+ dice::CONFIG_DESC => f.write_str("Config Desc"),
+ dice::CONFIG_HASH => f.write_str("Config Hash"),
+ dice::AUTHORITY_HASH => f.write_str("Authority Hash"),
+ dice::AUTHORITY_DESC => f.write_str("Authority Desc"),
+ dice::MODE => f.write_str("Mode"),
+ dice::SUBJECT_PUBLIC_KEY => f.write_str("Subject Public Key"),
+ dice::KEY_USAGE => f.write_str("Key Usage"),
+ _ => write!(f, "{}", label),
+ }
+}
+
+fn write_config_desc(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
+ let bytes = value.as_bytes().ok_or(fmt::Error)?;
+ let config_desc = ConfigDesc::from_slice(bytes).map_err(|_| fmt::Error)?;
+ write!(f, "{}", config_desc)
+}
+
+fn write_subject_public_key(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
+ let bytes = value.as_bytes().ok_or(fmt::Error)?;
+ let subject_public_key = SubjectPublicKey::from_slice(bytes).map_err(|_| fmt::Error)?;
+ write!(f, "{}", subject_public_key)
+}
+
+fn write_value(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
+ if let Some(bytes) = value.as_bytes() {
+ write_bytes_in_hex(f, bytes)
+ } else if let Some(text) = value.as_text() {
+ write!(f, "\"{}\"", text)
+ } else if let Ok(integer) = value.as_i64() {
+ write!(f, "{}", integer)
+ } else {
+ write!(f, "{:?}", value)
+ }
+}
+
+fn write_bytes_in_hex(f: &mut Formatter, bytes: &[u8]) -> Result<(), fmt::Error> {
+ for b in bytes {
+ write!(f, "{:02x}", b)?
+ }
+ Ok(())
+}