Skip to content

Instantly share code, notes, and snippets.

@pamaury
Created August 29, 2024 09:59
Show Gist options
  • Save pamaury/9f7fd597f69a3c626ae86f58d7537b7b to your computer and use it in GitHub Desktop.
Save pamaury/9f7fd597f69a3c626ae86f58d7537b7b to your computer and use it in GitHub Desktop.
diff --git a/sw/device/silicon_creator/lib/cert/uds.hjson b/sw/device/silicon_creator/lib/cert/uds.hjson
index 6a9ec3621b..238a672f1b 100644
--- a/sw/device/silicon_creator/lib/cert/uds.hjson
+++ b/sw/device/silicon_creator/lib/cert/uds.hjson
@@ -98,6 +98,7 @@
// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md#certificate-details
// The standard extensions are fixed by the specification.
basic_constraints: { ca: true },
+ key_usage: { cert_sign: true },
private_extensions: [
{
type: "dice_tcb_info",
diff --git a/sw/host/ot_certs/src/asn1/x509.rs b/sw/host/ot_certs/src/asn1/x509.rs
index 72298b5567..103a920a16 100644
--- a/sw/host/ot_certs/src/asn1/x509.rs
+++ b/sw/host/ot_certs/src/asn1/x509.rs
@@ -10,7 +10,7 @@ use crate::asn1::builder::{concat_suffix, Builder};
use crate::asn1::{Oid, Tag};
use crate::template::{
AttributeType, BasicConstraints, Certificate, CertificateExtension, EcCurve, EcPublicKeyInfo,
- EcdsaSignature, HashAlgorithm, Name, Signature, SubjectPublicKeyInfo, Value,
+ EcdsaSignature, HashAlgorithm, KeyUsage, Name, Signature, SubjectPublicKeyInfo, Value,
};
impl HashAlgorithm {
@@ -150,7 +150,9 @@ impl X509 {
Self::push_basic_constraints_ext(builder, constraints)?;
}
Self::push_subject_alt_name_ext(builder, &cert.subject_alt_name)?;
- Self::push_key_usage_ext(builder)?;
+ if let Some(key_usage) = &cert.key_usage {
+ Self::push_key_usage_ext(builder, key_usage)?;
+ }
Self::push_auth_key_id_ext(builder, &cert.authority_key_identifier)?;
Self::push_subject_key_id_ext(builder, &cert.subject_key_identifier)?;
for ext in &cert.private_extensions {
@@ -271,7 +273,7 @@ impl X509 {
})
}
- pub fn push_key_usage_ext<B: Builder>(builder: &mut B) -> Result<()> {
+ pub fn push_key_usage_ext<B: Builder>(builder: &mut B, key_usage: &KeyUsage) -> Result<()> {
// From https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3
// KeyUsage ::= BIT STRING {
// digitalSignature (0),
@@ -293,12 +295,12 @@ impl X509 {
Some("key_usage".into()),
&Tag::BitString,
&[
- /* digitalSignature */ Value::Literal(false),
+ key_usage.digital_signature.clone().unwrap_or(Value::literal(false)),
/* nonRepudiation */ Value::Literal(false),
/* keyEncipherment */ Value::Literal(false),
/* dataEncipherment */ Value::Literal(false),
- /* keyAgreement */ Value::Literal(false),
- /* keyCertSign */ Value::Literal(true),
+ key_usage.key_agreement.clone().unwrap_or(Value::literal(false)),
+ key_usage.cert_sign.clone().unwrap_or(Value::literal(false)),
/* cRLSign */ Value::Literal(false),
/* encipherOnly */ Value::Literal(false),
/* decipherOnly */ Value::Literal(false),
diff --git a/sw/host/ot_certs/src/template/mod.rs b/sw/host/ot_certs/src/template/mod.rs
index adc992235e..b6ac3393a8 100644
--- a/sw/host/ot_certs/src/template/mod.rs
+++ b/sw/host/ot_certs/src/template/mod.rs
@@ -75,6 +75,7 @@ pub struct Certificate {
pub subject_key_identifier: Value<Vec<u8>>,
// X509 basic constraints extension, optional.
pub basic_constraints: Option<BasicConstraints>,
+ pub key_usage: Option<KeyUsage>,
/// X509 Subject Alternative Name extension, optional.
#[serde(default)]
pub subject_alt_name: Name,
@@ -99,6 +100,13 @@ pub struct BasicConstraints {
pub ca: Value<bool>,
}
+#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
+pub struct KeyUsage {
+ pub digital_signature: Option<Value<bool>>,
+ pub key_agreement: Option<Value<bool>>,
+ pub cert_sign: Option<Value<bool>>,
+}
+
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CertificateExtension {
@@ -466,6 +474,7 @@ mod tests {
},
authority_key_identifier: { var: "signing_pub_key_id" },
subject_key_identifier: { var: "owner_pub_key_id" },
+ key_usage: { key_agreement: true },
private_extensions: [
{
type: "dice_tcb_info",
@@ -562,6 +571,7 @@ mod tests {
authority_key_identifier: Value::variable("signing_pub_key_id"),
subject_key_identifier: Value::variable("owner_pub_key_id"),
basic_constraints: None,
+ key_usage: Some(KeyUsage { digital_signature: None, key_agreement: Some(Value::literal(true)), cert_sign: None }),
subject_alt_name: vec![],
private_extensions: vec![CertificateExtension::DiceTcbInfo(DiceTcbInfoExtension {
vendor: Some(Value::literal("OpenTitan")),
diff --git a/sw/host/ot_certs/src/template/subst.rs b/sw/host/ot_certs/src/template/subst.rs
index 42c3012dd1..69b84db140 100644
--- a/sw/host/ot_certs/src/template/subst.rs
+++ b/sw/host/ot_certs/src/template/subst.rs
@@ -14,8 +14,8 @@ use serde::{Deserialize, Serialize};
use crate::template::{
BasicConstraints, Certificate, CertificateExtension, Conversion, DiceTcbInfoExtension,
- DiceTcbInfoFlags, EcPublicKey, EcPublicKeyInfo, EcdsaSignature, FirmwareId, Signature,
- SubjectPublicKeyInfo, Template, Value, Variable, VariableType,
+ DiceTcbInfoFlags, EcPublicKey, EcPublicKeyInfo, EcdsaSignature, FirmwareId, KeyUsage,
+ Signature, SubjectPublicKeyInfo, Template, Value, Variable, VariableType,
};
/// Substitution value: this is the raw value loaded from a hjson/json file
@@ -362,6 +362,10 @@ impl Subst for Certificate {
.basic_constraints
.subst(data)
.context("cannot substitute basic constraints")?,
+ key_usage: self
+ .key_usage
+ .subst(data)
+ .context("cannot substitute key usage")?,
private_extensions: self
.private_extensions
.iter()
@@ -464,6 +468,25 @@ impl Subst for DiceTcbInfoFlags {
}
}
+impl Subst for KeyUsage {
+ fn subst(&self, data: &SubstData) -> Result<KeyUsage> {
+ Ok(KeyUsage {
+ digital_signature: self
+ .digital_signature
+ .subst(data)
+ .context("cannot substitute digital signature key usage")?,
+ key_agreement: self
+ .key_agreement
+ .subst(data)
+ .context("cannot substitute key agreement")?,
+ cert_sign: self
+ .cert_sign
+ .subst(data)
+ .context("cannot substitute cert sign")?,
+ })
+ }
+}
+
impl Subst for SubjectPublicKeyInfo {
fn subst(&self, data: &SubstData) -> Result<SubjectPublicKeyInfo> {
match self {
diff --git a/sw/host/ot_certs/src/x509.rs b/sw/host/ot_certs/src/x509.rs
index be131c4f75..a3d4859313 100644
--- a/sw/host/ot_certs/src/x509.rs
+++ b/sw/host/ot_certs/src/x509.rs
@@ -22,7 +22,7 @@ use crate::asn1::der;
use crate::asn1::x509;
use crate::template::{
- self, AttributeType, EcCurve, EcPublicKeyInfo, EcdsaSignature, Name, Signature,
+ self, AttributeType, EcCurve, EcPublicKeyInfo, EcdsaSignature, KeyUsage, Name, Signature,
SubjectPublicKeyInfo, Value,
};
@@ -221,6 +221,7 @@ pub fn parse_certificate(cert: &[u8]) -> Result<template::Certificate> {
extension::x509_get_extensions(&x509).context("could not parse X509 extensions")?;
let mut private_extensions = Vec::new();
let mut basic_constraints = None;
+ let mut key_usage: Option<KeyUsage> = None;
for ext in raw_extensions {
match ext.object.nid() {
// Ignore extensions that are standard and handled by openssl.
@@ -234,7 +235,11 @@ pub fn parse_certificate(cert: &[u8]) -> Result<template::Certificate> {
.context("could not parse X509 basic constraints")?,
);
}
- Nid::KEY_USAGE => (),
+ Nid::KEY_USAGE => {
+ key_usage = Some(
+ extension::parse_key_usage(&ext).context("could not parse X509 key usage")?,
+ );
+ }
Nid::AUTHORITY_KEY_IDENTIFIER => (),
Nid::SUBJECT_ALT_NAME => (),
Nid::SUBJECT_KEY_IDENTIFIER => (),
@@ -266,6 +271,7 @@ pub fn parse_certificate(cert: &[u8]) -> Result<template::Certificate> {
.context("the certificate has not subject key id")?,
),
basic_constraints,
+ key_usage,
subject_alt_name: get_subject_alt_name(&x509)?,
private_extensions,
signature: extract_signature(&x509)?,
diff --git a/sw/host/ot_certs/src/x509/extension.rs b/sw/host/ot_certs/src/x509/extension.rs
index 37d6533881..0cf4dd773f 100644
--- a/sw/host/ot_certs/src/x509/extension.rs
+++ b/sw/host/ot_certs/src/x509/extension.rs
@@ -12,7 +12,7 @@ use openssl::x509::X509;
use crate::asn1::Oid;
use crate::template::{
BasicConstraints, CertificateExtension, DiceTcbInfoExtension, DiceTcbInfoFlags, FirmwareId,
- HashAlgorithm, Value,
+ HashAlgorithm, KeyUsage, Value,
};
/// X509 extension reference.
@@ -228,6 +228,29 @@ impl<'a> asn1::SimpleAsn1Readable<'a> for DiceTcbInfoFlags {
}
}
+impl<'a> asn1::SimpleAsn1Readable<'a> for KeyUsage {
+ const TAG: asn1::Tag = asn1::OwnedBitString::TAG;
+ fn parse_data(_data: &'a [u8]) -> asn1::ParseResult<Self> {
+ let result = asn1::OwnedBitString::parse_data(_data)?;
+ let bs = result.as_bitstring();
+ // List of bits that this parser supports. Any bit set outside of
+ // these will be an error because we cannot record it.
+ const PARSED_BITS: &[usize] = &[0, 4, 5];
+ let len = bs.as_bytes().len() * 8 - bs.padding_bits() as usize;
+ for i in 0..len {
+ if bs.has_bit_set(i) && !PARSED_BITS.contains(&i) {
+ // FIXME This will not return a very readable error message, should improve this
+ return asn1::ParseResult::Err(asn1::ParseError::new(asn1::ParseErrorKind::ExtraData))
+ }
+ }
+ Ok(KeyUsage {
+ digital_signature: Some(Value::Literal(bs.has_bit_set(0))),
+ key_agreement: Some(Value::Literal(bs.has_bit_set(4))),
+ cert_sign: Some(Value::Literal(bs.has_bit_set(5))),
+ })
+ }
+}
+
/// Try to parse an X509 extension as a DICE TCB info extension.
pub fn parse_dice_tcb_info_extension(ext: &[u8]) -> Result<DiceTcbInfoExtension> {
asn1::parse_single::<DiceTcbInfo>(ext)
@@ -252,6 +275,10 @@ impl BasicConstraintsInternal {
}
}
+pub fn parse_key_usage(ext: &X509ExtensionRef) -> Result<KeyUsage> {
+ Ok(asn1::parse_single::<KeyUsage>(ext.data.as_slice())?)
+}
+
pub fn parse_basic_constraints(ext: &X509ExtensionRef) -> Result<BasicConstraints> {
asn1::parse_single::<BasicConstraintsInternal>(ext.data.as_slice())
.context("cannot parse DICE extension")?
diff --git a/sw/host/ot_certs/tests/example.hjson b/sw/host/ot_certs/tests/example.hjson
index f63b4fb44c..47061ff97d 100644
--- a/sw/host/ot_certs/tests/example.hjson
+++ b/sw/host/ot_certs/tests/example.hjson
@@ -40,6 +40,11 @@
{ tpm_model: "TPM Model" },
{ tpm_version: "TPM Version" },
],
+ key_usage: {
+ digital_signature: true,
+ key_agreement: false,
+ cert_sign: true,
+ }
private_extensions: [
{
type: "dice_tcb_info",
diff --git a/sw/host/ot_certs/tests/example_data.json b/sw/host/ot_certs/tests/example_data.json
index a9e2fc31cd..9c97329dcb 100644
--- a/sw/host/ot_certs/tests/example_data.json
+++ b/sw/host/ot_certs/tests/example_data.json
@@ -24,5 +24,8 @@
"basic_constraints_ca": true,
"tpm_vendor": "TPM Vendor",
"tpm_model": "TPM Model",
- "tpm_version": "TPM Version"
+ "tpm_version": "TPM Version",
+ "key_usage_digital_signature": true,
+ "key_usage_key_agreement": false,
+ "key_usage_cert_sign": true
}
diff --git a/sw/host/ot_certs/tests/generic.hjson b/sw/host/ot_certs/tests/generic.hjson
index aea5c16132..87da5c2792 100644
--- a/sw/host/ot_certs/tests/generic.hjson
+++ b/sw/host/ot_certs/tests/generic.hjson
@@ -104,6 +104,15 @@
type: "string",
size: 100,
}
+ key_usage_cert_sign: {
+ type: "boolean"
+ }
+ key_usage_key_agreement: {
+ type: "boolean"
+ }
+ key_usage_digital_signature: {
+ type: "boolean"
+ }
},
certificate: {
@@ -135,6 +144,11 @@
{ tpm_model: { var: "tpm_model" } },
{ tpm_version: { var: "tpm_version" } },
],
+ key_usage: {
+ digital_signature: {var: "key_usage_digital_signature" },
+ key_agreement: {var: "key_usage_key_agreement" },
+ cert_sign: {var: "key_usage_cert_sign" },
+ }
private_extensions: [
{
type: "dice_tcb_info",
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment