Skip to content

Commit 43b04ae

Browse files
committedMar 11, 2025
Add support for the DiceTcbInfo x509 extension.
This extension is defined in the TCG Dice Attestation Architecture spec v1.0.0 in §6.1.1. We only support the `fwids` data field from the DiceTcbInfo extension. Additional fields may be implemented as needed. The TCG spec states that certs with the DiceTcbInfo extension MUST include the RFC 5288 AuthorityKeyIdentifier. We do not enforce this restriction but our implementation does not preclude the generation of a conformant cert chain from the KDL.
1 parent d3b39ac commit 43b04ae

File tree

4 files changed

+141
-3
lines changed

4 files changed

+141
-3
lines changed
 

‎Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ license = "MPL-2.0"
99
[dependencies]
1010
clap = { version = "4.5.31", features = ["derive"] }
1111
const-oid = { version = "0.9.6", features = ["db"] }
12+
der = { version = "0.7.9", features = ["std"] }
1213
digest = "0.10.7"
1314
ed25519-dalek = { version = "2.1.1", features = ["pem", "rand_core", "std", "zeroize"] }
1415
flagset = "0.4.6"

‎src/config.rs

+19
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ pub enum X509Extensions {
125125
AuthorityKeyIdentifier(AuthorityKeyIdentifierExtension),
126126
ExtendedKeyUsage(ExtendedKeyUsageExtension),
127127
CertificatePolicies(CertificatePoliciesExtension),
128+
DiceTcbInfo(DiceTcbInfoExtension),
128129
}
129130

130131
#[derive(knuffel::Decode, Debug)]
@@ -255,6 +256,24 @@ pub enum CertificatePolicy {
255256
Oid(#[knuffel(argument)] String),
256257
}
257258

259+
#[derive(knuffel::Decode, Debug)]
260+
pub struct DiceTcbInfoExtension {
261+
#[knuffel(property)]
262+
pub critical: bool,
263+
264+
#[knuffel(child, unwrap(children(name = "fwid")))]
265+
pub fwid_list: Vec<Fwid>,
266+
}
267+
268+
#[derive(knuffel::Decode, Debug)]
269+
pub struct Fwid {
270+
#[knuffel(child, unwrap(argument))]
271+
pub digest_algorithm: DigestAlgorithm,
272+
273+
#[knuffel(child, unwrap(argument))]
274+
pub digest: String,
275+
}
276+
258277
impl TryFrom<&CertificatePolicy> for PolicyInformation {
259278
type Error = miette::Error;
260279

‎src/lib.rs

+120-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

55
use const_oid::{AssociatedOid, ObjectIdentifier};
6-
use digest::Digest;
6+
use der::Sequence;
7+
use digest::{typenum::Unsigned, Digest, OutputSizeUser};
78
use flagset::FlagSet;
8-
use miette::{IntoDiagnostic, Result};
9+
use miette::{IntoDiagnostic, Result, WrapErr};
910
use sha1::Sha1;
11+
use sha2::{Sha256, Sha384, Sha512};
1012
use x509_cert::{
1113
attr::AttributeTypeAndValue,
1214
der::{
13-
asn1::{PrintableStringRef, SetOfVec, Utf8StringRef},
15+
self,
16+
asn1::{OctetString, PrintableStringRef, SetOfVec, Utf8StringRef},
1417
Decode as _, Encode as _,
1518
},
1619
ext::pkix::{certpolicy::PolicyInformation, BasicConstraints, KeyUsage},
@@ -26,6 +29,7 @@ pub mod ed25519;
2629
pub mod p384;
2730
pub mod rsa;
2831

32+
use crate::config::DigestAlgorithm;
2933
use crate::p384::P384KeyPair;
3034
use crate::rsa::RsaKeyPair;
3135
use ed25519::Ed25519KeyPair;
@@ -176,6 +180,9 @@ impl dyn Extension {
176180
config::X509Extensions::CertificatePolicies(x) => {
177181
Ok(Box::new(CertificatePoliciesExtension::from_config(x)?))
178182
}
183+
config::X509Extensions::DiceTcbInfo(c) => {
184+
Ok(Box::new(DiceTcbInfoExtension::from_config(c)?))
185+
}
179186
}
180187
}
181188
}
@@ -501,3 +508,113 @@ impl CertificatePoliciesExtension {
501508
})
502509
}
503510
}
511+
512+
// DICE Attestation Architecture §6.1.1:
513+
// FWID ::== SEQUENCE {
514+
#[derive(Debug, Sequence)]
515+
pub struct Fwid {
516+
// hashAlg OBJECT IDENTIFIER,
517+
hash_algorithm: ObjectIdentifier,
518+
// digest OCTET STRING
519+
digest: OctetString,
520+
}
521+
522+
impl Fwid {
523+
fn from_config(config: &config::Fwid) -> Result<Self> {
524+
let (hash_algorithm, length) = match config.digest_algorithm {
525+
DigestAlgorithm::Sha_256 => {
526+
(Sha256::OID, <Sha256 as OutputSizeUser>::OutputSize::USIZE)
527+
}
528+
DigestAlgorithm::Sha_384 => {
529+
(Sha384::OID, <Sha384 as OutputSizeUser>::OutputSize::USIZE)
530+
}
531+
DigestAlgorithm::Sha_512 => {
532+
(Sha512::OID, <Sha512 as OutputSizeUser>::OutputSize::USIZE)
533+
}
534+
};
535+
536+
let digest = hex::decode(&config.digest)
537+
.into_diagnostic()
538+
.wrap_err("Decode FWID digest")?;
539+
540+
if digest.len() != length {
541+
return Err(miette::miette!(
542+
"Unexpected digest length: expected {}, got {}",
543+
length,
544+
digest.len(),
545+
));
546+
}
547+
548+
let digest = OctetString::new(digest)
549+
.into_diagnostic()
550+
.wrap_err("digest to OctetString")?;
551+
552+
Ok(Fwid {
553+
digest,
554+
hash_algorithm,
555+
})
556+
}
557+
}
558+
559+
// NOTE: All fields in this structure is optional and we only implement support
560+
// for the ones that we currently need. Additional fields should be added as
561+
// needed.
562+
//
563+
// DICE Attestation Architecture §6.1.1:
564+
// DiceTcbInfo ::== SEQUENCE {
565+
#[derive(Debug, Sequence)]
566+
pub struct DiceTcbInfo {
567+
// fwids[6] IMPLICIT FWIDLIST OPTIONAL,
568+
// where FWIDLIST ::== SEQUENCE SIZE (1..MAX) OF FWID
569+
#[asn1(context_specific = "6", tag_mode = "IMPLICIT", optional = "true")]
570+
fwids: Option<Vec<Fwid>>,
571+
}
572+
573+
#[derive(Debug)]
574+
pub struct DiceTcbInfoExtension {
575+
der: Vec<u8>,
576+
is_critical: bool,
577+
}
578+
579+
impl Extension for DiceTcbInfoExtension {
580+
fn oid(&self) -> ObjectIdentifier {
581+
Self::OID
582+
}
583+
584+
fn is_critical(&self) -> bool {
585+
self.is_critical
586+
}
587+
588+
fn as_der(&self) -> &[u8] {
589+
&self.der
590+
}
591+
}
592+
593+
impl DiceTcbInfoExtension {
594+
// tcg-dice-TcbInfo from ASN.1 in DICE Attestation Architecture §6.1.1
595+
const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.23.133.5.4.1");
596+
597+
pub fn from_config(config: &config::DiceTcbInfoExtension) -> Result<Self> {
598+
let mut fwids: Vec<Fwid> = Vec::new();
599+
600+
for fwid in &config.fwid_list {
601+
let fwid = Fwid::from_config(fwid).wrap_err("Fwid from config")?;
602+
603+
fwids.push(fwid);
604+
}
605+
606+
let fwids = if !fwids.is_empty() { Some(fwids) } else { None };
607+
608+
let tcb_info = DiceTcbInfo { fwids };
609+
610+
let der = tcb_info
611+
.to_der()
612+
.into_diagnostic()
613+
.wrap_err("fwid list to DER")?;
614+
615+
Ok(DiceTcbInfoExtension {
616+
der,
617+
is_critical: config.critical,
618+
})
619+
}
620+
}

0 commit comments

Comments
 (0)