1use anyhow::Result;
35use indexmap::IndexMap;
36use num_bigint_dig::BigUint;
37use serde::{Deserialize, Deserializer, Serialize, Serializer};
38
39pub mod subst;
40pub mod testgen;
41
42use crate::template::subst::{ConvertValue, SubstValue};
43
44#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
46#[serde(deny_unknown_fields)]
47pub struct Template {
48 pub name: String,
50 pub variables: IndexMap<String, VariableType>,
52 pub certificate: Certificate,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
58#[serde(deny_unknown_fields)]
59pub struct Certificate {
60 pub serial_number: Value<BigUint>,
62 pub not_before: Value<String>,
64 pub not_after: Value<String>,
66 pub issuer: Name,
68 pub subject: Name,
70 pub subject_public_key_info: SubjectPublicKeyInfo,
72 pub authority_key_identifier: Option<Value<Vec<u8>>>,
74 pub subject_key_identifier: Option<Value<Vec<u8>>>,
76 pub basic_constraints: Option<BasicConstraints>,
78 pub key_usage: Option<KeyUsage>,
79 #[serde(default)]
81 pub subject_alt_name: Name,
82 #[serde(default)]
84 pub private_extensions: Vec<CertificateExtension>,
85 pub signature: Signature,
87}
88
89pub type Name = Vec<IndexMap<AttributeType, Value<String>>>;
97
98#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
99pub struct BasicConstraints {
100 pub ca: Value<bool>,
101}
102
103#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
104pub struct KeyUsage {
105 pub digital_signature: Option<Value<bool>>,
106 pub key_agreement: Option<Value<bool>>,
107 pub cert_sign: Option<Value<bool>>,
108}
109
110#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
111#[serde(tag = "type", rename_all = "snake_case")]
112pub enum CertificateExtension {
113 DiceTcbInfo(DiceTcbInfoExtension),
115}
116
117#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
119#[serde(deny_unknown_fields)]
120pub struct DiceTcbInfoExtension {
121 pub model: Option<Value<String>>,
123 pub vendor: Option<Value<String>>,
125 pub version: Option<Value<String>>,
127 pub svn: Option<Value<BigUint>>,
129 pub layer: Option<Value<BigUint>>,
131 pub fw_ids: Option<Vec<FirmwareId>>,
133 pub flags: Option<DiceTcbInfoFlags>,
135}
136
137#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Hash, strum::Display, Serialize)]
138#[serde(rename_all = "snake_case")]
139pub enum AttributeType {
140 #[serde(alias = "c")]
141 Country,
142 #[serde(alias = "o")]
143 Organization,
144 #[serde(alias = "ou")]
145 OrganizationalUnit,
146 #[serde(alias = "st")]
147 State,
148 #[serde(alias = "cn")]
149 CommonName,
150 #[serde(alias = "sn")]
151 SerialNumber,
152 TpmVendor,
153 TpmModel,
154 TpmVersion,
155}
156
157#[derive(Clone, Debug, PartialEq, Eq)]
159pub enum Value<T> {
160 Variable(Variable),
162 Literal(T),
164}
165
166#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
168#[serde(deny_unknown_fields)]
169pub struct Variable {
170 pub name: String,
172 pub convert: Option<Conversion>,
174}
175
176impl<T> Value<T> {
177 pub fn variable(name: &str) -> Self {
179 Value::Variable(Variable {
180 name: name.into(),
181 convert: None,
182 })
183 }
184
185 pub fn convert(var: &str, conversion: Conversion) -> Self {
187 Value::Variable(Variable {
188 name: var.into(),
189 convert: Some(conversion),
190 })
191 }
192
193 pub fn literal(value: impl Into<T>) -> Self {
195 Value::Literal(value.into())
196 }
197
198 pub fn is_literal(&self) -> bool {
200 matches!(self, Self::Literal(_))
201 }
202
203 pub fn refers_to(&self, var_name: &str) -> bool {
205 match self {
206 Value::Literal(_) => false,
207 Value::Variable(Variable { name, .. }) => name == var_name,
208 }
209 }
210}
211
212impl<'de, T> Deserialize<'de> for Value<T>
215where
216 T: Deserialize<'de>,
217 SubstValue: ConvertValue<T>,
218{
219 fn deserialize<D>(deserializer: D) -> Result<Value<T>, D::Error>
220 where
221 D: Deserializer<'de>,
222 {
223 #[derive(Deserialize)]
224 #[serde(untagged)]
225 pub enum LocalValue {
226 Variable {
227 var: String,
228 convert: Option<Conversion>,
229 },
230 Literal(SubstValue),
231 }
232 match LocalValue::deserialize(deserializer) {
233 Ok(val) => match val {
234 LocalValue::Literal(raw_val) => {
235 let val = raw_val.convert(&None).map_err(serde::de::Error::custom)?;
236 Ok(Value::<T>::Literal(val))
237 }
238 LocalValue::Variable { var, convert, .. } => {
239 Ok(Value::<T>::Variable(Variable { name: var, convert }))
240 }
241 },
242 Err(_) => {
243 let msg = "could not parse value: expected either a literal (string, integer or array of bytes) or a variable (use the syntax {{var: \"name\"}}";
244 Err(serde::de::Error::custom(msg))
245 }
246 }
247 }
248}
249
250impl<T> Serialize for Value<T>
253where
254 SubstValue: ConvertValue<T>,
255{
256 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
257 where
258 S: Serializer,
259 {
260 #[derive(Serialize)]
261 #[serde(untagged)]
262 pub enum LocalValue {
263 Variable {
264 var: String,
265 convert: Option<Conversion>,
266 },
267 Literal(SubstValue),
268 }
269 let res = match self {
270 Value::Variable(Variable { name, convert }) => LocalValue::Variable {
271 var: name.clone(),
272 convert: *convert,
273 },
274 Value::Literal(x) => {
275 LocalValue::Literal(SubstValue::unconvert(x).map_err(serde::ser::Error::custom)?)
276 }
277 };
278 res.serialize(serializer)
279 }
280}
281
282#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
284#[serde(rename_all = "kebab-case")]
285pub enum Conversion {
286 LowercaseHex,
291 BigEndian,
293}
294
295#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
297#[serde(tag = "algorithm", rename_all = "kebab-case")]
298pub enum Signature {
299 EcdsaWithSha256 { value: Option<EcdsaSignature> },
300}
301
302#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
307#[serde(deny_unknown_fields)]
308pub struct EcdsaSignature {
309 pub r: Value<BigUint>,
310 pub s: Value<BigUint>,
311}
312
313#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
315#[serde(tag = "algorithm", rename_all = "kebab-case")]
316pub enum SubjectPublicKeyInfo {
317 EcPublicKey(EcPublicKeyInfo),
318}
319
320#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
322#[serde(deny_unknown_fields)]
323pub struct EcPublicKeyInfo {
324 pub curve: EcCurve,
325 pub public_key: EcPublicKey,
326}
327
328#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
331#[serde(deny_unknown_fields)]
332pub struct EcPublicKey {
333 pub x: Value<BigUint>,
334 pub y: Value<BigUint>,
335}
336
337#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
339pub enum EcCurve {
340 #[serde(rename = "prime256v1")]
341 Prime256v1,
342}
343
344#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
346#[serde(deny_unknown_fields)]
347pub struct DiceTcbInfoFlags {
348 pub not_configured: Value<bool>,
349 pub not_secure: Value<bool>,
350 pub recovery: Value<bool>,
351 pub debug: Value<bool>,
352}
353
354#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
356#[serde(deny_unknown_fields)]
357pub struct FirmwareId {
358 pub hash_algorithm: HashAlgorithm,
360 pub digest: Value<Vec<u8>>,
362}
363
364#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
366pub enum HashAlgorithm {
367 #[serde(rename = "sha256")]
368 Sha256,
369}
370
371#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
373#[serde(rename_all = "kebab-case")]
374pub enum SizeRange {
375 RangeSize(usize, usize),
377 ExactSize(usize),
379}
380
381impl SizeRange {
382 pub fn range(self) -> (usize, usize) {
383 match self {
384 Self::RangeSize(min_size, max_size) => (min_size, max_size),
385 Self::ExactSize(size) => (size, size),
386 }
387 }
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
392#[serde(tag = "type", rename_all = "kebab-case")]
393pub enum VariableType {
394 #[serde(rename_all = "kebab-case")]
396 ByteArray {
397 #[serde(flatten)]
398 size: SizeRange,
399
400 tweak_msb: Option<bool>,
402 },
403 Integer {
406 #[serde(flatten)]
407 size: SizeRange,
408 },
409 String {
411 #[serde(flatten)]
412 size: SizeRange,
413 },
414 Boolean,
416}
417
418impl VariableType {
419 pub fn size(&self) -> usize {
421 self.array_size().1
422 }
423
424 pub fn use_msb_tweak(&self) -> bool {
426 use VariableType::*;
427 matches!(
428 self,
429 ByteArray {
430 tweak_msb: Some(true),
431 ..
432 }
433 )
434 }
435
436 pub fn has_constant_array_size(&self) -> bool {
439 let (min_size, max_size) = self.array_size();
440 min_size == max_size
441 }
442
443 pub fn array_size(&self) -> (usize, usize) {
448 use VariableType::*;
449 match self {
450 ByteArray { size, .. } | String { size, .. } => size.range(),
451 Integer { size, .. } => (size.range().1, size.range().1),
453 Boolean => panic!("Boolean variable has no array size"),
454 }
455 }
456
457 pub fn int_size(&self, extra_bytes: usize) -> (usize, usize) {
465 use VariableType::*;
466 match self {
467 ByteArray {
468 tweak_msb: Some(true),
469 ..
470 } => {
471 if !self.has_constant_array_size() {
472 panic!("Tweak MSb of var-sized ByteArray is not supported");
473 }
474 (self.size() + extra_bytes, self.size() + extra_bytes)
475 }
476 ByteArray { .. } => panic!(
477 "Encoding ByteArray variable without tweak-msb as an integer is not supported"
478 ),
479 Integer { size, .. } => (size.range().0, size.range().1 + extra_bytes),
480 String { .. } => panic!("String variable has no integer size"),
481 Boolean => panic!("Boolean variable has no integer size"),
482 }
483 }
484}
485
486impl Template {
487 pub fn from_hjson_str(content: &str) -> Result<Template> {
488 Ok(deser_hjson::from_str(content)?)
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use indoc::indoc;
496
497 #[test]
499 fn cdi_owner() {
500 use SizeRange::*;
501
502 let input = indoc! {r#"
504 {
505 name: "cdi_owner",
506
507 variables: {
508 owner_pub_key_ec_x: {
509 type: "integer",
510 exact-size: 32,
511 },
512 owner_pub_key_ec_y: {
513 type: "integer",
514 exact-size: 32,
515 },
516 owner_pub_key_id: {
517 type: "byte-array",
518 exact-size: 20,
519 tweak-msb: true
520 },
521 signing_pub_key_id: {
522 type: "byte-array",
523 exact-size: 20,
524 tweak-msb: true
525 },
526 rom_ext_hash: {
527 type: "byte-array",
528 exact-size: 20,
529 },
530 ownership_manifest_hash: {
531 type: "byte-array",
532 exact-size: 20,
533 },
534 rom_ext_security_version: {
535 type: "byte-array",
536 exact-size: 4,
537 tweak-msb: true,
538 }
539 layer: {
540 type: "integer",
541 range-size: [1, 4],
542 }
543 cert_signature_r: {
544 type: "integer",
545 range-size: [24, 32],
546 },
547 cert_signature_s: {
548 type: "integer",
549 range-size: [24, 32],
550 },
551 },
552
553 certificate: {
554 serial_number: { var: "owner_pub_key_id", convert: "big-endian" },
555 issuer: [
556 { serial_number: { var: "signing_pub_key_id", convert: "lowercase-hex" } },
557 ],
558 not_before: "20230101000000Z",
559 not_after: "99991231235959Z",
560 subject: [
561 { serial_number: { var: "owner_pub_key_id", convert: "lowercase-hex" } },
562 ],
563 subject_public_key_info: {
564 algorithm: "ec-public-key",
565 curve: "prime256v1",
566 public_key: {
567 x: { var: "owner_pub_key_ec_x" },
568 y: { var: "owner_pub_key_ec_y" },
569 },
570 },
571 authority_key_identifier: { var: "signing_pub_key_id" },
572 subject_key_identifier: { var: "owner_pub_key_id" },
573 key_usage: { key_agreement: true },
574 private_extensions: [
575 {
576 type: "dice_tcb_info",
577 vendor: "OpenTitan",
578 model: "ROM_EXT",
579 svn: { var: "rom_ext_security_version", convert: "big-endian" },
580 layer: { var: "layer" },
581 version: "ES",
582 fw_ids: [
583 { hash_algorithm: "sha256", digest: { var: "rom_ext_hash" } },
584 { hash_algorithm: "sha256", digest: { var: "ownership_manifest_hash" } },
585 ],
586 flags: {
587 not_configured: true,
588 not_secure: false,
589 recovery: true,
590 debug: false,
591 }
592 },
593 ],
594 signature: {
595 algorithm: "ecdsa-with-sha256",
596 // The value field is optional: if not present, the signature will be cleared.
597 // Otherwise, we can reference the various fields of the signature.
598 value: {
599 r: { var: "cert_signature_r" },
600 s: { var: "cert_signature_s" }
601 }
602 }
603 }
604 }
605 "#};
606
607 let variables = IndexMap::from([
608 (
609 "owner_pub_key_ec_x".to_string(),
610 VariableType::Integer {
611 size: ExactSize(32),
612 },
613 ),
614 (
615 "owner_pub_key_ec_y".to_string(),
616 VariableType::Integer {
617 size: ExactSize(32),
618 },
619 ),
620 (
621 "owner_pub_key_id".to_string(),
622 VariableType::ByteArray {
623 size: ExactSize(20),
624 tweak_msb: Some(true),
625 },
626 ),
627 (
628 "signing_pub_key_id".to_string(),
629 VariableType::ByteArray {
630 size: ExactSize(20),
631 tweak_msb: Some(true),
632 },
633 ),
634 (
635 "rom_ext_hash".to_string(),
636 VariableType::ByteArray {
637 size: ExactSize(20),
638 tweak_msb: None,
639 },
640 ),
641 (
642 "ownership_manifest_hash".to_string(),
643 VariableType::ByteArray {
644 size: ExactSize(20),
645 tweak_msb: None,
646 },
647 ),
648 (
649 "rom_ext_security_version".to_string(),
650 VariableType::ByteArray {
651 size: ExactSize(4),
652 tweak_msb: Some(true),
653 },
654 ),
655 (
656 "layer".to_string(),
657 VariableType::Integer {
658 size: RangeSize(1, 4),
659 },
660 ),
661 (
662 "cert_signature_r".to_string(),
663 VariableType::Integer {
664 size: RangeSize(24, 32),
665 },
666 ),
667 (
668 "cert_signature_s".to_string(),
669 VariableType::Integer {
670 size: RangeSize(24, 32),
671 },
672 ),
673 ]);
674
675 let certificate = Certificate {
677 serial_number: Value::convert("owner_pub_key_id", Conversion::BigEndian),
678 issuer: vec![IndexMap::from([(
679 AttributeType::SerialNumber,
680 Value::convert("signing_pub_key_id", Conversion::LowercaseHex),
681 )])],
682 not_before: Value::literal("20230101000000Z"),
683 not_after: Value::literal("99991231235959Z"),
684 subject: vec![IndexMap::from([(
685 AttributeType::SerialNumber,
686 Value::convert("owner_pub_key_id", Conversion::LowercaseHex),
687 )])],
688 subject_public_key_info: SubjectPublicKeyInfo::EcPublicKey(EcPublicKeyInfo {
689 curve: EcCurve::Prime256v1,
690 public_key: EcPublicKey {
691 x: Value::variable("owner_pub_key_ec_x"),
692 y: Value::variable("owner_pub_key_ec_y"),
693 },
694 }),
695 authority_key_identifier: Some(Value::variable("signing_pub_key_id")),
696 subject_key_identifier: Some(Value::variable("owner_pub_key_id")),
697 basic_constraints: None,
698 key_usage: Some(KeyUsage {
699 digital_signature: None,
700 key_agreement: Some(Value::literal(true)),
701 cert_sign: None,
702 }),
703 subject_alt_name: vec![],
704 private_extensions: vec![CertificateExtension::DiceTcbInfo(DiceTcbInfoExtension {
705 vendor: Some(Value::literal("OpenTitan")),
706 model: Some(Value::literal("ROM_EXT")),
707 svn: Some(Value::convert(
708 "rom_ext_security_version",
709 Conversion::BigEndian,
710 )),
711 layer: Some(Value::variable("layer")),
712 version: Some(Value::literal("ES")),
713 fw_ids: Some(Vec::from([
714 FirmwareId {
715 hash_algorithm: HashAlgorithm::Sha256,
716 digest: Value::variable("rom_ext_hash"),
717 },
718 FirmwareId {
719 hash_algorithm: HashAlgorithm::Sha256,
720 digest: Value::variable("ownership_manifest_hash"),
721 },
722 ])),
723 flags: Some(DiceTcbInfoFlags {
724 not_configured: Value::Literal(true),
725 not_secure: Value::Literal(false),
726 recovery: Value::Literal(true),
727 debug: Value::Literal(false),
728 }),
729 })],
730 signature: Signature::EcdsaWithSha256 {
731 value: Some(EcdsaSignature {
732 r: Value::variable("cert_signature_r"),
733 s: Value::variable("cert_signature_s"),
734 }),
735 },
736 };
737
738 let expected = Template {
740 name: "cdi_owner".to_string(),
741 variables,
742 certificate,
743 };
744 let actual = Template::from_hjson_str(input).expect("failed to parse template");
745 assert_eq!(
747 expected, actual,
748 "certificate mismatch: expected {expected:#?} but got {actual:#?}"
749 );
750 }
751}