ot_certs/x509/
extension.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::{Context, Result, bail, ensure};
6use num_bigint_dig::BigUint;
7
8use foreign_types::{ForeignType, ForeignTypeRef};
9use openssl::asn1::{Asn1Object, Asn1ObjectRef, Asn1OctetStringRef};
10use openssl::x509::X509;
11
12use crate::asn1::Oid;
13use crate::template::{
14    BasicConstraints, CertificateExtension, DiceTcbInfoExtension, DiceTcbInfoFlags, FirmwareId,
15    HashAlgorithm, KeyUsage, Value,
16};
17
18/// X509 extension reference.
19pub struct X509ExtensionRef<'a> {
20    // Extension type.
21    pub object: &'a Asn1ObjectRef,
22    // Critical marker.
23    pub critical: bool,
24    // Extension data.
25    pub data: &'a Asn1OctetStringRef,
26}
27
28/// Return the list of extensions of an X509 cerificate.
29pub fn x509_get_extensions(x509: &X509) -> Result<Vec<X509ExtensionRef<'_>>> {
30    let mut exts = Vec::new();
31    // SAFETY: the rust openssl binding guarantees that x509 is a valid object.
32    let ext_count = unsafe { openssl_sys::X509_get_ext_count(x509.as_ptr()) };
33
34    for index in 0..ext_count {
35        // SAFETY: the rust openssl binding guarantees that x509 is a valid object
36        // and `index` is a valid index.
37        // From the documentation of X509_get_ext:
38        // The returned extension is an internal pointer which must not be freed
39        // up by the application. Therefore this pointer is valid as long as the X509
40        // object lives.
41        let ext = unsafe { openssl_sys::X509_get_ext(x509.as_ptr(), index) };
42
43        // SAFETY: `ext` is a valid object.
44        let critical = unsafe { openssl_sys::X509_EXTENSION_get_critical(ext) };
45        // In the ASN1, the critical marker is a boolean so it's actually impossible for
46        // openssl to return anything but 0 and 1, so throw in error in case we see anything else.
47        let critical = match critical {
48            0 => false,
49            1 => true,
50            _ => bail!("openssl returned non-boolean critical marker for extension {index}"),
51        };
52
53        // SAFETY: `ext` is a valid object and the returned pointer is marked with the lifetime
54        // of the X509 object that owns the memory.
55        let object = unsafe {
56            // From the documentation of X509_EXTENSION_get_data:
57            // The returned pointer is an internal value which must not be freed up.
58            let data = openssl_sys::X509_EXTENSION_get_object(ext);
59            Asn1ObjectRef::from_ptr(data)
60        };
61
62        // SAFETY: `ext` is a valid object and the returned pointer is marked with the lifetime
63        // of the X509 object that owns the memory.
64        let data = unsafe {
65            // From the documentation of X509_EXTENSION_get_data:
66            // The returned pointer is an internal value which must not be freed up.
67            let data = openssl_sys::X509_EXTENSION_get_data(ext);
68            Asn1OctetStringRef::from_ptr(data)
69        };
70
71        exts.push(X509ExtensionRef {
72            object,
73            critical,
74            data,
75        })
76    }
77
78    Ok(exts)
79}
80
81// From the DICE specification:
82// https://trustedcomputinggroup.org/wp-content/uploads/DICE-Attestation-Architecture-r23-final.pdf
83//
84// tcg OBJECT IDENTIFIER ::= {2 23 133}
85// tcg-dice OBJECT IDENTIFIER ::= { tcg platformClass(5) 4 }
86// tcg-dice-TcbInfo OBJECT IDENTIFIER ::= {tcg-dice 1}
87// DiceTcbInfo ::== SEQUENCE {
88//     vendor [0] IMPLICIT UTF8String OPTIONAL,
89//     model [1] IMPLICIT UTF8String OPTIONAL,
90//     version [2] IMPLICIT UTF8String OPTIONAL,
91//     svn [3] IMPLICIT INTEGER OPTIONAL,
92//     layer [4] IMPLICIT INTEGER OPTIONAL,
93//     index [5] IMPLICIT INTEGER OPTIONAL,
94//     fwids [6] IMPLICIT FWIDLIST OPTIONAL,
95//     flags [7] IMPLICIT OperationalFlags OPTIONAL,
96//     vendorInfo [8] IMPLICIT OCTET STRING OPTIONAL,
97//     type [9] IMPLICIT OCTET STRING OPTIONAL
98// }
99// FWIDLIST ::== SEQUENCE SIZE (1..MAX) OF FWID
100//     FWID ::== SEQUENCE {
101//     hashAlg OBJECT IDENTIFIER,
102//     digest OCTET STRING
103// }
104// OperationalFlags ::= BIT STRING {
105//     notConfigured (0),
106//     notSecure (1),
107//     recovery (2),
108//     debug (3)
109// }
110
111// See DiceTcbInfo.
112#[derive(asn1::Asn1Read)]
113struct Fwid<'a> {
114    pub hash_alg: asn1::ObjectIdentifier,
115    pub digest: &'a [u8],
116}
117
118// This is an internal structure used to parse a DiceTcbInfo extension using the `asn1`
119// crate. We cannot use the `DiceTcbInfoExtension` in `template` since we
120// need to use specific annotations and types so that the `asn` library can
121// derive an ASN1 parser.
122#[derive(asn1::Asn1Read)]
123struct DiceTcbInfo<'a> {
124    #[implicit(0)]
125    pub vendor: Option<asn1::Utf8String<'a>>,
126    #[implicit(1)]
127    pub model: Option<asn1::Utf8String<'a>>,
128    #[implicit(2)]
129    pub version: Option<asn1::Utf8String<'a>>,
130    #[implicit(3)]
131    pub svn: Option<asn1::BigInt<'a>>,
132    #[implicit(4)]
133    pub layer: Option<asn1::BigInt<'a>>,
134    #[implicit(5)]
135    pub index: Option<asn1::BigInt<'a>>,
136    #[implicit(6)]
137    pub fwids: Option<asn1::SequenceOf<'a, Fwid<'a>>>,
138    #[implicit(7)]
139    pub flags: Option<DiceTcbInfoFlags>,
140    #[implicit(8)]
141    pub vendor_info: Option<&'a [u8]>,
142    #[implicit(9)]
143    pub tcb_type: Option<&'a [u8]>,
144}
145
146fn convert_hash_algorithm(objid: &asn1::ObjectIdentifier) -> Result<HashAlgorithm> {
147    for (oid, hashalg) in [(Oid::Sha256, HashAlgorithm::Sha256)] {
148        if *objid
149            == asn1::ObjectIdentifier::from_string(oid.oid())
150                .expect("Cannot convert Oid to asn1::ObjectIdentifier")
151        {
152            return Ok(hashalg);
153        }
154    }
155    bail!("unsupported hash algorithm {}", objid);
156}
157
158fn asn1utf8_to_str(s: &asn1::Utf8String) -> Value<String> {
159    Value::literal(s.as_str().to_string())
160}
161
162fn asn1bigint_to_bn(bn: &asn1::BigInt) -> Value<BigUint> {
163    Value::literal(BigUint::from_bytes_be(bn.as_bytes()))
164}
165
166impl DiceTcbInfo<'_> {
167    fn to_dice_extension(&self) -> Result<DiceTcbInfoExtension> {
168        let fw_ids = self
169            .fwids
170            .as_ref()
171            .map(|fwids| {
172                fwids
173                    .clone()
174                    .map(|fwid| {
175                        Ok(FirmwareId {
176                            hash_algorithm: convert_hash_algorithm(&fwid.hash_alg)
177                                .context("unknown hash algorithm")?,
178                            digest: Value::literal(fwid.digest.to_vec()),
179                        })
180                    })
181                    .collect::<Result<Vec<_>>>()
182            })
183            .transpose()
184            .context("cannot parse DICE TCB firmware IDs")?;
185
186        // Vendor info is not supported.
187        ensure!(
188            self.index.is_none(),
189            "the parser does not support DICE TCB index"
190        );
191        ensure!(
192            self.vendor_info.is_none(),
193            "the parser does not support DICE TCB vendor info"
194        );
195        ensure!(
196            self.tcb_type.is_none(),
197            "the parser does not support DICE TCB type"
198        );
199
200        Ok(DiceTcbInfoExtension {
201            model: self.model.as_ref().map(asn1utf8_to_str),
202            vendor: self.vendor.as_ref().map(asn1utf8_to_str),
203            version: self.version.as_ref().map(asn1utf8_to_str),
204            svn: self.svn.as_ref().map(asn1bigint_to_bn),
205            layer: self.layer.as_ref().map(asn1bigint_to_bn),
206            fw_ids,
207            flags: self.flags.clone(),
208        })
209    }
210}
211
212impl<'a> asn1::SimpleAsn1Readable<'a> for DiceTcbInfoFlags {
213    const TAG: asn1::Tag = asn1::OwnedBitString::TAG;
214    fn parse_data(_data: &'a [u8]) -> asn1::ParseResult<Self> {
215        let result = asn1::OwnedBitString::parse_data(_data)?;
216        let bs = result.as_bitstring();
217        if bs.as_bytes().len() != 1 || bs.padding_bits() != 4 {
218            // We expect 4 bits.
219            asn1::ParseResult::Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidLength))
220        } else {
221            Ok(DiceTcbInfoFlags {
222                not_configured: Value::Literal(bs.has_bit_set(0)),
223                not_secure: Value::Literal(bs.has_bit_set(1)),
224                recovery: Value::Literal(bs.has_bit_set(2)),
225                debug: Value::Literal(bs.has_bit_set(3)),
226            })
227        }
228    }
229}
230
231// From https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3
232// KeyUsage ::= BIT STRING {
233//    digitalSignature        (0),
234//    nonRepudiation          (1), -- recent editions of X.509 have
235//                         -- renamed this bit to contentCommitment
236//    keyEncipherment         (2),
237//    dataEncipherment        (3),
238//    keyAgreement            (4),
239//    keyCertSign             (5),
240//    cRLSign                 (6),
241//    encipherOnly            (7),
242//    decipherOnly            (8) }
243impl<'a> asn1::SimpleAsn1Readable<'a> for KeyUsage {
244    const TAG: asn1::Tag = asn1::OwnedBitString::TAG;
245    fn parse_data(_data: &'a [u8]) -> asn1::ParseResult<Self> {
246        let result = asn1::OwnedBitString::parse_data(_data)?;
247        let bs = result.as_bitstring();
248        // List of bits that this parser supports. Any bit set outside of
249        // these will be an error because we cannot record it.
250        const PARSED_BITS: &[usize] = &[0, 4, 5];
251        let len = bs.as_bytes().len() * 8 - bs.padding_bits() as usize;
252        for i in 0..len {
253            if bs.has_bit_set(i) && !PARSED_BITS.contains(&i) {
254                // FIXME This will not return a very readable error message but the asn1
255                // does not support arbitrary string errors.
256                return asn1::ParseResult::Err(asn1::ParseError::new(
257                    asn1::ParseErrorKind::ExtraData,
258                ));
259            }
260        }
261        Ok(KeyUsage {
262            digital_signature: Some(Value::Literal(bs.has_bit_set(0))),
263            key_agreement: Some(Value::Literal(bs.has_bit_set(4))),
264            cert_sign: Some(Value::Literal(bs.has_bit_set(5))),
265        })
266    }
267}
268
269// From https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
270// AuthorityKeyIdentifier ::= SEQUENCE {
271//       keyIdentifier             [0] KeyIdentifier           OPTIONAL,
272//       authorityCertIssuer       [1] GeneralNames            OPTIONAL,
273//       authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }
274//
275// KeyIdentifier ::= OCTET STRING
276#[derive(asn1::Asn1Read)]
277struct AuthorityKeyIdentifier<'a> {
278    #[implicit(0)]
279    pub key_id: Option<&'a [u8]>,
280}
281
282/// Try to parse an X509 extension as a DICE TCB info extension.
283pub fn parse_dice_tcb_info_extension(ext: &[u8]) -> Result<DiceTcbInfoExtension> {
284    asn1::parse_single::<DiceTcbInfo>(ext)
285        .context("cannot parse DICE extension")?
286        .to_dice_extension()
287}
288
289// This is an internal structure used to parse a Basic Constraints extension using the `asn1`
290// crate. We cannot use the `BasicConstraints` in `template` since we
291// need to use specific annotations and types so that the `asn` library can
292// derive an ASN1 parser.
293#[derive(asn1::Asn1Read)]
294struct BasicConstraintsInternal {
295    ca: bool,
296}
297
298impl BasicConstraintsInternal {
299    fn to_basic_constraints(&self) -> Result<BasicConstraints> {
300        Ok(BasicConstraints {
301            ca: Value::Literal(self.ca),
302        })
303    }
304}
305
306pub fn parse_key_usage(ext: &X509ExtensionRef) -> Result<KeyUsage> {
307    Ok(asn1::parse_single::<KeyUsage>(ext.data.as_slice())?)
308}
309
310pub fn parse_basic_constraints(ext: &X509ExtensionRef) -> Result<BasicConstraints> {
311    asn1::parse_single::<BasicConstraintsInternal>(ext.data.as_slice())
312        .context("cannot parse DICE extension")?
313        .to_basic_constraints()
314}
315
316pub fn parse_authority_key_id(ext: &X509ExtensionRef) -> Result<Vec<u8>> {
317    let auth = asn1::parse_single::<AuthorityKeyIdentifier>(ext.data.as_slice())?;
318    Ok(auth
319        .key_id
320        .context("authority key identifier extension is empty")?
321        .to_vec())
322}
323
324pub fn parse_subject_key_id(ext: &X509ExtensionRef) -> Result<Vec<u8>> {
325    // From https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2
326    // SubjectKeyIdentifier ::= KeyIdentifier
327    //
328    //  KeyIdentifier ::= OCTET STRING
329    let subj = asn1::parse_single::<&[u8]>(ext.data.as_slice())?;
330    Ok(subj.to_vec())
331}
332
333/// Try to parse an X509 extension.
334pub fn parse_extension(ext: &X509ExtensionRef) -> Result<CertificateExtension> {
335    let dice_oid =
336        Asn1Object::from_str(Oid::DiceTcbInfo.oid()).expect("cannot create object ID from string");
337    // The openssl library does not provide a way to compare between two Asn1Object so compare the raw DER.
338    Ok(match ext.object.to_owned().as_slice() {
339        obj if obj == dice_oid.as_slice() => {
340            CertificateExtension::DiceTcbInfo(parse_dice_tcb_info_extension(ext.data.as_slice())?)
341        }
342        _ => bail!("unknown extension type {}", ext.object,),
343    })
344}