blob: 6e746abd0b6c1e16d74ee298a6ebe8180cf183f7 [file] [log] [blame]
use crate::publickey::PublicKey;
use std::fmt::{self, Display, Formatter};
use thiserror::Error;
/// Enumeration of modes used in the DICE chain payloads.
#[derive(Debug, Default, Copy, Clone)]
pub enum DiceMode {
/// This mode also acts as a catch-all for configurations which do not fit the other modes and
/// invalid modes.
#[default]
NotConfigured,
/// The device is operating normally under secure configuration.
Normal,
/// At least one criteria for [`Normal`] is not met and the device is not in a secure state.
Debug,
/// A recovery or maintenance mode of some kind.
Recovery,
}
/// The payload of a DICE chain entry.
#[derive(Debug)]
pub struct Payload {
issuer: String,
subject: String,
subject_public_key: PublicKey,
mode: DiceMode,
code_desc: Option<Vec<u8>>,
code_hash: Vec<u8>,
config_desc: ConfigDesc,
config_hash: Option<Vec<u8>>,
authority_desc: Option<Vec<u8>>,
authority_hash: Vec<u8>,
}
impl Payload {
/// Gets the issuer of the payload.
pub fn issuer(&self) -> &str {
&self.issuer
}
/// Gets the subject of the payload.
pub fn subject(&self) -> &str {
&self.subject
}
/// Gets the subject public key of the payload.
pub fn subject_public_key(&self) -> &PublicKey {
&self.subject_public_key
}
/// Gets the mode of the payload.
pub fn mode(&self) -> DiceMode {
self.mode
}
/// Gets the code descriptor of the payload.
pub fn code_desc(&self) -> Option<&[u8]> {
self.code_desc.as_deref()
}
/// Gets the code hash of the payload.
pub fn code_hash(&self) -> &[u8] {
&self.code_hash
}
/// Gets the configuration descriptor of the payload.
pub fn config_desc(&self) -> &ConfigDesc {
&self.config_desc
}
/// Gets the configuration hash of the payload.
pub fn config_hash(&self) -> Option<&[u8]> {
self.config_hash.as_deref()
}
/// Gets the authority descriptor of the payload.
pub fn authority_desc(&self) -> Option<&[u8]> {
self.authority_desc.as_deref()
}
/// Gets the authority hash of the payload.
pub fn authority_hash(&self) -> &[u8] {
&self.authority_hash
}
}
impl Display for Payload {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
writeln!(f, "Issuer: {}", self.issuer)?;
writeln!(f, "Subject: {}", self.subject)?;
writeln!(f, "Mode: {:?}", self.mode)?;
if let Some(code_desc) = &self.code_desc {
writeln!(f, "Code Desc: {}", hex::encode(code_desc))?;
}
writeln!(f, "Code Hash: {}", hex::encode(&self.code_hash))?;
if let Some(config_hash) = &self.config_hash {
writeln!(f, "Config Hash: {}", hex::encode(config_hash))?;
}
if let Some(authority_desc) = &self.authority_desc {
writeln!(f, "Authority Desc: {}", hex::encode(authority_desc))?;
}
writeln!(f, "Authority Hash: {}", hex::encode(&self.authority_hash))?;
writeln!(f, "Config Desc:")?;
write!(f, "{}", &self.config_desc)?;
Ok(())
}
}
#[derive(Error, Debug, PartialEq, Eq)]
pub(crate) enum PayloadBuilderError {
#[error("issuer empty")]
IssuerEmpty,
#[error("subject empty")]
SubjectEmpty,
#[error("bad code hash size")]
CodeHashSize,
#[error("bad config hash size")]
ConfigHashSize,
#[error("bad authority hash size")]
AuthorityHashSize,
}
pub(crate) struct PayloadBuilder(Payload);
impl PayloadBuilder {
/// Constructs a new builder with the given subject public key.
pub fn with_subject_public_key(subject_public_key: PublicKey) -> Self {
Self(Payload {
issuer: Default::default(),
subject: Default::default(),
subject_public_key,
mode: Default::default(),
code_desc: Default::default(),
code_hash: Default::default(),
config_desc: Default::default(),
config_hash: Default::default(),
authority_desc: Default::default(),
authority_hash: Default::default(),
})
}
/// Builds the [`Payload`] after validating the fields.
pub fn build(self) -> Result<Payload, PayloadBuilderError> {
if self.0.issuer.is_empty() {
return Err(PayloadBuilderError::IssuerEmpty);
}
if self.0.subject.is_empty() {
return Err(PayloadBuilderError::SubjectEmpty);
}
let used_hash_size = self.0.code_hash.len();
if used_hash_size != 32 && used_hash_size != 64 {
return Err(PayloadBuilderError::CodeHashSize);
}
if let Some(ref config_hash) = self.0.config_hash {
if config_hash.len() != used_hash_size {
return Err(PayloadBuilderError::ConfigHashSize);
}
}
if self.0.authority_hash.len() != used_hash_size {
return Err(PayloadBuilderError::AuthorityHashSize);
}
Ok(self.0)
}
/// Sets the issuer of the payload.
#[must_use]
pub fn issuer<S: Into<String>>(mut self, issuer: S) -> Self {
self.0.issuer = issuer.into();
self
}
/// Sets the subject of the payload.
#[must_use]
pub fn subject<S: Into<String>>(mut self, subject: S) -> Self {
self.0.subject = subject.into();
self
}
/// Sets the mode of the payload.
#[must_use]
pub fn mode(mut self, mode: DiceMode) -> Self {
self.0.mode = mode;
self
}
/// Sets the code descriptor of the payload.
#[must_use]
pub fn code_desc(mut self, code_desc: Option<Vec<u8>>) -> Self {
self.0.code_desc = code_desc;
self
}
/// Sets the code hash of the payload.
#[must_use]
pub fn code_hash(mut self, code_hash: Vec<u8>) -> Self {
self.0.code_hash = code_hash;
self
}
/// Sets the configuration descriptor of the payload.
#[must_use]
pub fn config_desc(mut self, config_desc: ConfigDesc) -> Self {
self.0.config_desc = config_desc;
self
}
/// Sets the configuration hash of the payload.
#[must_use]
pub fn config_hash(mut self, config_hash: Option<Vec<u8>>) -> Self {
self.0.config_hash = config_hash;
self
}
/// Sets the authority descriptor of the payload.
#[must_use]
pub fn authority_desc(mut self, authority_desc: Option<Vec<u8>>) -> Self {
self.0.authority_desc = authority_desc;
self
}
/// Sets the authority hash of the payload.
#[must_use]
pub fn authority_hash(mut self, authority_hash: Vec<u8>) -> Self {
self.0.authority_hash = authority_hash;
self
}
}
/// Fields from the configuration descriptor.
#[derive(Debug, Default, Clone)]
pub struct ConfigDesc {
component_name: Option<String>,
component_version: Option<i64>,
resettable: bool,
}
impl ConfigDesc {
/// Gets the component name.
pub fn component_name(&self) -> Option<&str> {
self.component_name.as_deref()
}
/// Gets the component version.
pub fn component_version(&self) -> Option<i64> {
self.component_version
}
/// Returns whether the component is factory resettable.
pub fn resettable(&self) -> bool {
self.resettable
}
}
impl Display for ConfigDesc {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
if let Some(component_name) = &self.component_name {
writeln!(f, "Component Name: {}", component_name)?;
}
if let Some(component_version) = &self.component_version {
writeln!(f, "Component Version: {}", component_version)?;
}
if self.resettable {
writeln!(f, "Resettable")?;
}
Ok(())
}
}
pub(crate) struct ConfigDescBuilder(ConfigDesc);
impl ConfigDescBuilder {
/// Constructs a new builder with default values.
pub fn new() -> Self {
Self(ConfigDesc::default())
}
/// Builds the [`ConfigDesc`].
pub fn build(self) -> ConfigDesc {
self.0
}
/// Sets the component name.
#[must_use]
pub fn component_name(mut self, name: Option<String>) -> Self {
self.0.component_name = name;
self
}
/// Sets the component version.
#[must_use]
pub fn component_version(mut self, version: Option<i64>) -> Self {
self.0.component_version = version;
self
}
/// Sets whether the component is factory resettable.
#[must_use]
pub fn resettable(mut self, resettable: bool) -> Self {
self.0.resettable = resettable;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::publickey::testkeys::{PrivateKey, P256_KEY_PEM};
#[test]
fn payload_builder_valid() {
valid_payload().build().unwrap();
}
#[test]
fn payload_builder_valid_512_bit_hashes() {
valid_payload()
.code_hash(vec![1; 64])
.authority_hash(vec![2; 64])
.config_hash(Some(vec![3; 64]))
.build()
.unwrap();
}
#[test]
fn payload_builder_valid_256_bit_hashes() {
valid_payload()
.code_hash(vec![1; 32])
.authority_hash(vec![2; 32])
.config_hash(Some(vec![3; 32]))
.build()
.unwrap();
}
#[test]
fn payload_builder_empty_issuer() {
let err = valid_payload().issuer("").build().unwrap_err();
assert_eq!(err, PayloadBuilderError::IssuerEmpty);
}
#[test]
fn payload_builder_empty_subject() {
let err = valid_payload().subject("").build().unwrap_err();
assert_eq!(err, PayloadBuilderError::SubjectEmpty);
}
#[test]
fn payload_builder_bad_code_hash_size() {
let err = valid_payload().code_hash(vec![1; 48]).build().unwrap_err();
assert_eq!(err, PayloadBuilderError::CodeHashSize);
}
#[test]
fn payload_builder_bad_authority_hash_size() {
let err = valid_payload().authority_hash(vec![1; 48]).build().unwrap_err();
assert_eq!(err, PayloadBuilderError::AuthorityHashSize);
}
#[test]
fn payload_builder_inconsistent_authority_hash_size() {
let err =
valid_payload().code_hash(vec![1; 32]).authority_hash(vec![1; 64]).build().unwrap_err();
assert_eq!(err, PayloadBuilderError::AuthorityHashSize);
}
#[test]
fn payload_builder_bad_config_hash_size() {
let err = valid_payload().config_hash(Some(vec![1; 48])).build().unwrap_err();
assert_eq!(err, PayloadBuilderError::ConfigHashSize);
}
#[test]
fn payload_builder_inconsistent_config_hash_size() {
let err = valid_payload()
.code_hash(vec![1; 64])
.config_hash(Some(vec![1; 32]))
.build()
.unwrap_err();
assert_eq!(err, PayloadBuilderError::ConfigHashSize);
}
fn valid_payload() -> PayloadBuilder {
let key = PrivateKey::from_pem(P256_KEY_PEM[0]).public_key();
PayloadBuilder::with_subject_public_key(key)
.issuer("issuer")
.subject("subject")
.code_hash(vec![1; 64])
.authority_hash(vec![2; 64])
}
}