Skip to content

Commit 75c57b9

Browse files
committed
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 75c57b9

File tree

5 files changed

+252
-3
lines changed

5 files changed

+252
-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"

examples/dice-tcb-info/config.kdl

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
key-pair "root" {
2+
ed25519
3+
}
4+
5+
entity "root" {
6+
country-name "US"
7+
organization-name "foo"
8+
common-name "root"
9+
}
10+
11+
certificate "root" {
12+
issuer-entity "root"
13+
issuer-key "root"
14+
15+
subject-entity "root"
16+
subject-key "root"
17+
18+
not-after "9999-12-31T23:59:59Z"
19+
serial-number "00"
20+
21+
extensions {
22+
basic-constraints critical=true ca=true
23+
subject-key-identifier critical=false
24+
25+
key-usage critical=true {
26+
key-cert-sign
27+
crl-sign
28+
}
29+
30+
certificate-policies critical=true {
31+
tcg-dice-kp-identity-init
32+
tcg-dice-kp-attest-init
33+
tcg-dice-kp-eca
34+
}
35+
}
36+
}
37+
38+
key-pair "device-id" {
39+
ed25519
40+
}
41+
42+
entity "device-id" {
43+
country-name "US"
44+
organization-name "foo"
45+
common-name "device-id"
46+
}
47+
48+
certificate "device-id" {
49+
issuer-certificate "root"
50+
issuer-key "root"
51+
52+
subject-entity "device-id"
53+
subject-key "device-id"
54+
55+
not-after "9999-12-31T23:59:59Z"
56+
serial-number "00"
57+
58+
extensions {
59+
basic-constraints critical=true ca=true
60+
key-usage critical=true {
61+
key-cert-sign
62+
}
63+
certificate-policies critical=true {
64+
tcg-dice-kp-attest-init
65+
tcg-dice-kp-eca
66+
}
67+
}
68+
}
69+
70+
key-pair "alias" {
71+
ed25519
72+
}
73+
74+
entity "alias" {
75+
country-name "US"
76+
organization-name "foo"
77+
common-name "alias"
78+
}
79+
80+
certificate "alias" {
81+
issuer-certificate "device-id"
82+
issuer-key "device-id"
83+
84+
subject-entity "alias"
85+
subject-key "alias"
86+
87+
not-after "9999-12-31T23:59:59Z"
88+
serial-number "00"
89+
90+
extensions {
91+
basic-constraints critical=true ca=true
92+
key-usage critical=true {
93+
digital-signature
94+
}
95+
certificate-policies critical=true {
96+
tcg-dice-kp-attest-init
97+
}
98+
dice-tcb-info critical=true {
99+
fwid-list {
100+
fwid {
101+
digest-algorithm "sha-256"
102+
digest "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
103+
}
104+
fwid {
105+
digest-algorithm "sha-384"
106+
digest "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
107+
}
108+
}
109+
}
110+
}
111+
}

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 are optional and we only implement
560+
// support for the ones that we currently need. Additional fields should
561+
// be added as 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)