ot_certs/asn1/x509.rs
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use num_bigint_dig::BigUint;
7use num_traits::FromPrimitive;
8
9use crate::asn1::builder::{Builder, concat_suffix};
10use crate::asn1::{Oid, Tag};
11use crate::template::{
12 AttributeType, BasicConstraints, Certificate, CertificateExtension, EcCurve, EcPublicKeyInfo,
13 EcdsaSignature, HashAlgorithm, KeyUsage, Name, Signature, SubjectPublicKeyInfo, Value,
14};
15
16impl HashAlgorithm {
17 // Return the object identifier of this algorithm.
18 pub fn oid(&self) -> Oid {
19 match self {
20 HashAlgorithm::Sha256 => Oid::Sha256,
21 }
22 }
23}
24
25impl EcCurve {
26 pub fn oid(&self) -> Oid {
27 match self {
28 EcCurve::Prime256v1 => Oid::Prime256v1,
29 }
30 }
31}
32
33// Those attribute types are described in X.520. For UnboundedDirectoryString,
34// we have chosen the UTF-8 type.
35impl AttributeType {
36 // Return the object identifier of this attribute type.
37 pub fn oid(&self) -> Oid {
38 match self {
39 Self::Country => Oid::Country,
40 Self::Organization => Oid::Organization,
41 Self::OrganizationalUnit => Oid::OrganizationalUnit,
42 Self::State => Oid::State,
43 Self::CommonName => Oid::CommonName,
44 Self::SerialNumber => Oid::SerialNumber,
45 Self::TpmModel => Oid::SalTpmModel,
46 Self::TpmVendor => Oid::SalTpmVendor,
47 Self::TpmVersion => Oid::SalTpmVersion,
48 }
49 }
50
51 // Return the tag of the value associated with this attribute type.
52 pub fn data_type(&self) -> Tag {
53 match self {
54 Self::Country => Tag::PrintableString,
55 Self::Organization => Tag::Utf8String,
56 Self::OrganizationalUnit => Tag::Utf8String,
57 Self::State => Tag::Utf8String,
58 Self::CommonName => Tag::Utf8String,
59 Self::SerialNumber => Tag::PrintableString,
60 Self::TpmModel => Tag::Utf8String,
61 Self::TpmVendor => Tag::Utf8String,
62 Self::TpmVersion => Tag::Utf8String,
63 }
64 }
65}
66
67pub struct X509;
68
69impl X509 {
70 /// Push an X509 certificate into the builder. The TBS certificate is represented by
71 /// a byte array (see push_x509_tbs_certificate).
72 pub fn push_certificate<B: Builder>(
73 builder: &mut B,
74 tbs_cert_var: &Value<Vec<u8>>,
75 sig: &Signature,
76 ) -> Result<()> {
77 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.1:
78 // Certificate ::= SEQUENCE {
79 // tbsCertificate TBSCertificate,
80 // signatureAlgorithm AlgorithmIdentifier,
81 // signatureValue BIT STRING }
82 builder.push_seq(Some("cert".into()), |builder| {
83 builder.push_byte_array(Some("tbs".into()), tbs_cert_var)?;
84 Self::push_sig_alg_id(builder, sig)?;
85 builder.push_as_bit_string(Some("cert_sig".into()), &Tag::BitString, 0, |builder| {
86 Self::push_signature(builder, sig)
87 })
88 })
89 }
90
91 /// Push an X509 TBS certificate into the builder.
92 pub fn push_tbs_certificate<B: Builder>(builder: &mut B, cert: &Certificate) -> Result<()> {
93 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.1:
94 // TBSCertificate ::= SEQUENCE {
95 // version [0] EXPLICIT Version DEFAULT v1,
96 // serialNumber CertificateSerialNumber,
97 // signature AlgorithmIdentifier,
98 // issuer Name,
99 // validity Validity,
100 // subject Name,
101 // subjectPublicKeyInfo SubjectPublicKeyInfo,
102 // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
103 // -- If present, version MUST be v2 or v3
104 // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
105 // -- If present, version MUST be v2 or v3
106 // extensions [3] EXPLICIT Extensions OPTIONAL
107 // -- If present, version MUST be v3
108 // }
109 //
110 // // Version ::= INTEGER { v1(0), v2(1), v3(2) }
111 //
112 // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
113 builder.push_seq(Some("cert".into()), |builder| {
114 builder.push_tag(
115 Some("tbs_version_tag".into()),
116 &Tag::Context {
117 constructed: true,
118 value: 0,
119 },
120 |builder| {
121 builder.push_integer(
122 Some("tbs_version".into()),
123 &Tag::Integer,
124 &Value::Literal(
125 BigUint::from_u32(2).expect("cannot make biguint from u32"),
126 ),
127 )
128 },
129 )?;
130 // CertificateSerialNumber ::= INTEGER
131 builder.push_integer(
132 Some("tbs_serial_number".into()),
133 &Tag::Integer,
134 &cert.serial_number,
135 )?;
136 Self::push_sig_alg_id(builder, &cert.signature)?;
137 Self::push_name(builder, Some("issuer".into()), &cert.issuer)?;
138 Self::push_validity(builder, &cert.not_before, &cert.not_after)?;
139 Self::push_name(builder, Some("subject".into()), &cert.subject)?;
140 Self::push_public_key_info(builder, &cert.subject_public_key_info)?;
141 builder.push_tag(
142 Some("tbs_extensions_tag".into()),
143 &Tag::Context {
144 constructed: true,
145 value: 3,
146 },
147 |builder| {
148 builder.push_seq(Some("tbs_extensions".into()), |builder| {
149 if let Some(constraints) = &cert.basic_constraints {
150 Self::push_basic_constraints_ext(builder, constraints)?;
151 }
152 Self::push_subject_alt_name_ext(builder, &cert.subject_alt_name)?;
153 if let Some(key_usage) = &cert.key_usage {
154 Self::push_key_usage_ext(builder, key_usage)?;
155 }
156 if let Some(auth_key_id) = &cert.authority_key_identifier {
157 Self::push_auth_key_id_ext(builder, auth_key_id)?;
158 }
159 if let Some(subj_key_id) = &cert.subject_key_identifier {
160 Self::push_subject_key_id_ext(builder, subj_key_id)?;
161 }
162 for ext in &cert.private_extensions {
163 Self::push_cert_extension(builder, ext)?
164 }
165 Ok(())
166 })
167 },
168 )
169 })
170 }
171
172 pub fn push_cert_extension<B: Builder>(
173 builder: &mut B,
174 ext: &CertificateExtension,
175 ) -> Result<()> {
176 match ext {
177 CertificateExtension::DiceTcbInfo(dice_ext) => dice_ext.push_extension(builder),
178 }
179 }
180
181 pub fn push_name<B: Builder>(
182 builder: &mut B,
183 name_hint: Option<String>,
184 name: &Name,
185 ) -> Result<()> {
186 // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
187 // Name ::= CHOICE { -- only one possibility for now --
188 // rdnSequence RDNSequence }
189 //
190 // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
191 //
192 // RelativeDistinguishedName ::=
193 // SET SIZE (1..MAX) OF AttributeTypeAndValue
194 //
195 // AttributeTypeAndValue ::= SEQUENCE {
196 // type AttributeType,
197 // value AttributeValue }
198 //
199 // AttributeType ::= OBJECT IDENTIFIER
200 //
201 // AttributeValue ::= ANY -- DEFINED BY AttributeType
202 builder.push_seq(name_hint.clone(), |builder| {
203 for attributes in name {
204 builder.push_set(concat_suffix(&name_hint, "set"), |builder| {
205 for (attr_type, val) in attributes {
206 builder.push_seq(
207 concat_suffix(&name_hint, &attr_type.to_string()),
208 |builder| {
209 builder.push_oid(&attr_type.oid())?;
210 builder.push_string(
211 concat_suffix(&name_hint, &attr_type.to_string()),
212 &attr_type.data_type(),
213 val,
214 )
215 },
216 )?;
217 }
218 Ok(())
219 })?;
220 }
221 Ok(())
222 })
223 }
224
225 pub fn push_basic_constraints_ext<B: Builder>(
226 builder: &mut B,
227 constraints: &BasicConstraints,
228 ) -> Result<()> {
229 // https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9
230 // BasicConstraints ::= SEQUENCE {
231 // cA BOOLEAN DEFAULT FALSE,
232 // pathLenConstraint INTEGER (0..MAX) OPTIONAL }
233
234 // Retrieve the configured value of the extension.
235 Self::push_extension(builder, &Oid::BasicConstraints, true, |builder| {
236 builder.push_seq(Some("basic_constraints".into()), |builder| {
237 builder.push_boolean(&Tag::Boolean, &constraints.ca)
238 })
239 })
240 }
241
242 pub fn push_subject_alt_name_ext<B: Builder>(
243 builder: &mut B,
244 subject_alt_name: &Name,
245 ) -> Result<()> {
246 // SubjectAltName ::= GeneralNames
247 //
248 // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
249 //
250 // GeneralName ::= CHOICE {
251 // otherName [0] OtherName,
252 // rfc822Name [1] IA5String,
253 // dNSName [2] IA5String,
254 // x400Address [3] ORAddress,
255 // directoryName [4] Name,
256 // ediPartyName [5] EDIPartyName,
257 // uniformResourceIdentifier [6] IA5String,
258 // iPAddress [7] OCTET STRING,
259 // registeredID [8] OBJECT IDENTIFIER }
260 //
261 // We only support `directoryName` at the moment.
262 if subject_alt_name.is_empty() {
263 // Subject Alternative Name is an optional extension, it's ok not to be present.
264 return Ok(());
265 }
266 Self::push_extension(builder, &Oid::SubjectAltName, false, |builder| {
267 builder.push_seq(Some("subject_alt_name".into()), |builder| -> Result<()> {
268 builder.push_tag(
269 None,
270 &Tag::Context {
271 constructed: true,
272 value: 4,
273 },
274 |builder| -> Result<()> { Self::push_name(builder, None, subject_alt_name) },
275 )
276 })
277 })
278 }
279
280 pub fn push_key_usage_ext<B: Builder>(builder: &mut B, key_usage: &KeyUsage) -> Result<()> {
281 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3
282 // KeyUsage ::= BIT STRING {
283 // digitalSignature (0),
284 // nonRepudiation (1), -- recent editions of X.509 have
285 // -- renamed this bit to contentCommitment
286 // keyEncipherment (2),
287 // dataEncipherment (3),
288 // keyAgreement (4),
289 // keyCertSign (5),
290 // cRLSign (6),
291 // encipherOnly (7),
292 // decipherOnly (8) }
293 Self::push_extension(builder, &Oid::KeyUsage, true, |builder| {
294 builder.push_bitstring(
295 Some("key_usage".into()),
296 &Tag::BitString,
297 &[
298 key_usage
299 .digital_signature
300 .clone()
301 .unwrap_or(Value::literal(false)),
302 /* nonRepudiation */ Value::Literal(false),
303 /* keyEncipherment */ Value::Literal(false),
304 /* dataEncipherment */ Value::Literal(false),
305 key_usage
306 .key_agreement
307 .clone()
308 .unwrap_or(Value::literal(false)),
309 key_usage.cert_sign.clone().unwrap_or(Value::literal(false)),
310 /* cRLSign */ Value::Literal(false),
311 /* encipherOnly */ Value::Literal(false),
312 /* decipherOnly */ Value::Literal(false),
313 ],
314 )
315 })
316 }
317
318 pub fn push_auth_key_id_ext<B: Builder>(
319 builder: &mut B,
320 auth_key_id: &Value<Vec<u8>>,
321 ) -> Result<()> {
322 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
323 // AuthorityKeyIdentifier ::= SEQUENCE {
324 // keyIdentifier [0] KeyIdentifier OPTIONAL,
325 // authorityCertIssuer [1] GeneralNames OPTIONAL,
326 // authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
327 //
328 // KeyIdentifier ::= OCTET STRING
329 //
330 // Note: this is part of the implicit tagged modules:
331 // https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.2
332 Self::push_extension(builder, &Oid::AuthorityKeyIdentifier, false, |builder| {
333 builder.push_seq(Some("auth_key_id".into()), |builder| {
334 builder.push_tag(
335 Some("key_id".into()),
336 &Tag::Context {
337 constructed: false,
338 value: 0,
339 },
340 |builder| builder.push_byte_array(Some("authority_key_id".into()), auth_key_id),
341 )
342 })
343 })
344 }
345
346 pub fn push_subject_key_id_ext<B: Builder>(
347 builder: &mut B,
348 auth_key_id: &Value<Vec<u8>>,
349 ) -> Result<()> {
350 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2
351 // KeyIdentifier ::= OCTET STRING
352 Self::push_extension(builder, &Oid::SubjectKeyIdentifier, false, |builder| {
353 builder.push_octet_string(Some("subject_key_id".into()), |builder| {
354 builder.push_byte_array(Some("subject_key_id".into()), auth_key_id)
355 })
356 })
357 }
358
359 pub fn push_extension<B: Builder>(
360 builder: &mut B,
361 oid: &Oid,
362 critical: bool,
363 build: impl FnOnce(&mut B) -> Result<()>,
364 ) -> Result<()> {
365 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.1:
366 // Extension ::= SEQUENCE {
367 // extnID OBJECT IDENTIFIER,
368 // critical BOOLEAN DEFAULT FALSE,
369 // extnValue OCTET STRING
370 // -- contains the DER encoding of an ASN.1 value
371 // -- corresponding to the extension type identified
372 // -- by extnID
373 // }
374 builder.push_seq(concat_suffix(&Some(oid.to_string()), "ext"), |builder| {
375 builder.push_oid(oid)?;
376 builder.push_boolean(&Tag::Boolean, &Value::Literal(critical))?;
377 builder.push_octet_string(concat_suffix(&Some(oid.to_string()), "ext_value"), build)
378 })
379 }
380
381 pub fn push_public_key_info<B: Builder>(
382 builder: &mut B,
383 pubkey_info: &SubjectPublicKeyInfo,
384 ) -> Result<()> {
385 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.1:
386 // SubjectPublicKeyInfo ::= SEQUENCE {
387 // algorithm AlgorithmIdentifier,
388 // subjectPublicKey BIT STRING }
389 builder.push_seq(Some("subject_public_key_info".into()), |builder| {
390 Self::push_pubkey_alg_id(builder, pubkey_info)?;
391 builder.push_as_bit_string(
392 Some("subject_public_key".into()),
393 &Tag::BitString,
394 0,
395 |builder| Self::push_public_key(builder, pubkey_info),
396 )
397 })
398 }
399
400 pub fn push_pubkey_alg_id<B: Builder>(
401 builder: &mut B,
402 pubkey_info: &SubjectPublicKeyInfo,
403 ) -> Result<()> {
404 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.1.2
405 // AlgorithmIdentifier ::= SEQUENCE {
406 // algorithm OBJECT IDENTIFIER,
407 // parameters ANY DEFINED BY algorithm OPTIONAL }
408 builder.push_seq(
409 Some("subject_public_key_alg".into()),
410 |builder| match pubkey_info {
411 SubjectPublicKeyInfo::EcPublicKey(ec_pubkey) => {
412 builder.push_oid(&Oid::EcPublicKey)?;
413 Self::push_ec_public_key_params(builder, ec_pubkey)
414 }
415 },
416 )
417 }
418
419 pub fn push_public_key<B: Builder>(
420 builder: &mut B,
421 pubkey_info: &SubjectPublicKeyInfo,
422 ) -> Result<()> {
423 match pubkey_info {
424 SubjectPublicKeyInfo::EcPublicKey(ec_pubkey) => {
425 Self::push_ec_public_key(builder, ec_pubkey)
426 }
427 }
428 }
429
430 pub fn push_ec_public_key_params<B: Builder>(
431 builder: &mut B,
432 pubkey: &EcPublicKeyInfo,
433 ) -> Result<()> {
434 // From the X9.62 spec: section 6.4
435 // Parameters ::= CHOICE {
436 // ecParameters ECParameters,
437 // namedCurve CURVES.&id({CurveNames}),
438 // implicitlyCA NULL
439 // }
440 //
441 // CURVES ::= CLASS {
442 // &id OBJECT IDENTIFIER UNIQUE
443 // }
444 // WITH SYNTAX { ID &id }
445 //
446 // CurveNames CURVES ::= {
447 // { ID prime256v1 } | ...
448 // }
449 //
450 builder.push_oid(&pubkey.curve.oid())?;
451 Ok(())
452 }
453
454 pub fn push_ec_public_key<B: Builder>(builder: &mut B, pubkey: &EcPublicKeyInfo) -> Result<()> {
455 // From the X9.62 spec: section 6.2
456 // ECPoint ::= OCTET STRING
457 //
458 // From the X9.62 spec: 4.3.6
459 // If P = (x,y), If the uncompressed form is used, then do the following:
460 // - Convert the field element x p to an octet string X1.
461 // - Convert the field element y to an octet string Y1.
462 // - Assign the value 04 to the single octet PC.
463 // - The result is the octet string PO = PC || X1 ||Y1.
464 // Section 4.3.3 explains how to convert a field element: this is a big-endian
465 // integer padded to ceil(log q) bytes.
466 //
467 // From the X9.62 spec: section 6.4
468 // The elliptic curve public key (a value of type ECPoint which is an OCTET STRING) is mapped to a
469 // subjectPublicKey (a value of type BIT STRING) as follows: the most significant bit of the OCTET STRING
470 // value becomes the most significant bit of the BIT STRING value, etc.; the least significant bit of the OCTET
471 // STRING becomes the least significant bit of the BIT STRING.
472 let size = match pubkey.curve {
473 EcCurve::Prime256v1 => 32,
474 };
475
476 builder.push_byte(4)?;
477 builder.push_integer_pad(Some("pubkey_ec_x".into()), &pubkey.public_key.x, size)?;
478 builder.push_integer_pad(Some("pubkey_ec_y".into()), &pubkey.public_key.y, size)?;
479 Ok(())
480 }
481
482 pub fn push_validity<B: Builder>(
483 builder: &mut B,
484 not_before: &Value<String>,
485 not_after: &Value<String>,
486 ) -> Result<()> {
487 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.1:
488 // Validity ::= SEQUENCE {
489 // notBefore Time,
490 // notAfter Time }
491 //
492 // Time ::= CHOICE {
493 // utcTime UTCTime,
494 // generalTime GeneralizedTime }
495 //
496 // From the X680 spec: section 46
497 // GeneralizedTime ::= [UNIVERSAL 24] IMPLICIT VisibleString
498 builder.push_seq(Some("validity".into()), |builder| {
499 builder.push_string(Some("not_before".into()), &Tag::GeneralizedTime, not_before)?;
500 builder.push_string(Some("not_after".into()), &Tag::GeneralizedTime, not_after)
501 })
502 }
503
504 pub fn push_sig_alg_id<B: Builder>(builder: &mut B, sig: &Signature) -> Result<()> {
505 // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.1.2
506 // AlgorithmIdentifier ::= SEQUENCE {
507 // algorithm OBJECT IDENTIFIER,
508 // parameters ANY DEFINED BY algorithm OPTIONAL }
509 //
510 // Per the X509 specification, the signature parameters must not contain any parameters
511 builder.push_seq(Some("algorithm_identifier".into()), |builder| match sig {
512 Signature::EcdsaWithSha256 { .. } => builder.push_oid(&Oid::EcdsaWithSha256),
513 })
514 }
515
516 pub fn push_signature<B: Builder>(builder: &mut B, sig: &Signature) -> Result<()> {
517 match sig {
518 Signature::EcdsaWithSha256 { value } => {
519 let zero = BigUint::from_u32(0).expect("cannot build BigUint from u32");
520 let empty_ecdsa = EcdsaSignature {
521 r: Value::Literal(zero.clone()),
522 s: Value::Literal(zero.clone()),
523 };
524 Self::push_ecdsa_sig(builder, value.as_ref().unwrap_or(&empty_ecdsa))
525 }
526 }
527 }
528
529 pub fn push_ecdsa_sig<B: Builder>(builder: &mut B, sig: &EcdsaSignature) -> Result<()> {
530 // From https://datatracker.ietf.org/doc/html/rfc3279#section-2.2.3:
531 //
532 // Ecdsa-Sig-Value ::= SEQUENCE {
533 // r INTEGER,
534 // s INTEGER }
535 builder.push_seq(Some("sig_ecdsa_value".into()), |builder| {
536 builder.push_integer(Some("sig_ecdsa_r".into()), &Tag::Integer, &sig.r)?;
537 builder.push_integer(Some("sig_ecdsa_s".into()), &Tag::Integer, &sig.s)
538 })
539 }
540}