blob: 2ebc12ee517487b0719dab43b5d24190c2d743aa [file] [log] [blame]
//! 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(())
}