ot_certs/
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::{Context, Result, bail, ensure};
6use indexmap::IndexMap;
7use num_bigint_dig::BigUint;
8
9use foreign_types::ForeignTypeRef;
10use openssl::asn1::{Asn1IntegerRef, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef};
11use openssl::bn::{BigNum, BigNumContext, BigNumRef};
12use openssl::ec::{EcGroupRef, EcKey};
13use openssl::ecdsa::EcdsaSig;
14use openssl::nid::Nid;
15use openssl::pkey::PKey;
16use openssl::pkey::Public;
17use openssl::x509::{X509, X509NameRef};
18
19use crate::asn1::der;
20use crate::asn1::x509;
21
22use crate::template::{
23    self, AttributeType, EcCurve, EcPublicKeyInfo, EcdsaSignature, KeyUsage, Name, Signature,
24    SubjectPublicKeyInfo, Value,
25};
26
27pub mod extension;
28
29fn curve_from_ecgroup(group: &EcGroupRef) -> Result<EcCurve> {
30    let Some(name) = group.curve_name() else {
31        bail!("only curves with standard names are supported")
32    };
33    match name {
34        Nid::X9_62_PRIME256V1 => Ok(EcCurve::Prime256v1),
35        _ => bail!("curve {:?} is not supported", name),
36    }
37}
38
39impl TryFrom<&Asn1ObjectRef> for AttributeType {
40    type Error = anyhow::Error;
41
42    fn try_from(obj: &Asn1ObjectRef) -> Result<AttributeType, Self::Error> {
43        // Try to match attriutes that OpenSSL does not known about.
44        for attr in [
45            AttributeType::TpmVendor,
46            AttributeType::TpmModel,
47            AttributeType::TpmVersion,
48        ] {
49            if obj.to_owned().as_slice() == attr.oid().to_der().unwrap().as_slice() {
50                return Ok(attr);
51            }
52        }
53        Ok(match obj.nid() {
54            Nid::COUNTRYNAME => AttributeType::Country,
55            Nid::ORGANIZATIONNAME => AttributeType::Organization,
56            Nid::ORGANIZATIONALUNITNAME => AttributeType::OrganizationalUnit,
57            Nid::STATEORPROVINCENAME => AttributeType::State,
58            Nid::COMMONNAME => AttributeType::CommonName,
59            Nid::SERIALNUMBER => AttributeType::SerialNumber,
60            _ => bail!("unrecognized OID {:?}", obj),
61        })
62    }
63}
64
65fn asn1int_to_bn(field: &str, bn: &Asn1IntegerRef) -> Result<Value<BigUint>> {
66    Ok(Value::literal(BigUint::from_bytes_be(
67        &bn.to_bn()
68            .with_context(|| format!("could not extract {} from certificate", field))?
69            .to_vec(),
70    )))
71}
72
73fn asn1bignum_to_bn(bn: &BigNumRef) -> Value<BigUint> {
74    Value::literal(BigUint::from_bytes_be(&bn.to_vec()))
75}
76
77fn asn1str_to_str(field: &str, s: &Asn1StringRef) -> Result<Value<String>> {
78    Ok(Value::literal(
79        s.as_utf8()
80            .with_context(|| format!("could not extract {} from certificate", field))?
81            .to_string(),
82    ))
83}
84
85fn asn1time_to_string(time: &Asn1TimeRef) -> Result<Value<String>> {
86    // OpenSSL guarantees that an ASN1_TIME is in fact just a typedef for ASN1_STRING
87    // https://www.openssl.org/docs/man1.1.1/man3/ASN1_TIME_to_generalizedtime.html
88    let time_str = time.as_ptr() as *mut openssl_sys::ASN1_STRING;
89    // SAFETY: the above pointer is guaranteed to be valid since `time` is valid reference.
90    let time_type = unsafe { openssl_sys::ASN1_STRING_type(time_str) };
91    // FIXME: we only support GeneralizedTime and openssl_sys does not export the OpenSSL method to
92    // convert to a GeneralizedTime so we just error out.
93    if time_type == openssl_sys::V_ASN1_UTCTIME {
94        bail!("time uses UtcTime but only GeneralizedTime is supported")
95    }
96    if time_type != openssl_sys::V_ASN1_GENERALIZEDTIME {
97        bail!("time uses type {time_type} but only GeneralizedTime is supported")
98    }
99    // SAFETY: the above pointer is guaranteed to be valid since `time` is valid reference.
100    let time_str = unsafe { Asn1StringRef::from_ptr(time_str) }.as_slice();
101    Ok(Value::literal(
102        std::str::from_utf8(time_str)
103            .context("GeneralizedTime is not a valid UTF8 string")?
104            .to_string(),
105    ))
106}
107
108fn asn1name_to_name(field: &str, name: &X509NameRef) -> Result<Name> {
109    // FIXME The OpenSSL representation of names is a bit odd: it flattens
110    // the sequence of sets into a sequence but for each name entry remembers
111    // the index into the sequence. Unfortunately, we need to call X509_NAME_ENTRY_set
112    // to get the index but this is not exported by openssl-sys. For now, and since
113    // multi-valued RDNs are rare, simply assume that all sets have size 1.
114    let mut name_res = Name::new();
115    for entry in name.entries() {
116        let attr = AttributeType::try_from(entry.object())?;
117        let mut res = IndexMap::new();
118        res.insert(attr, asn1str_to_str(field, entry.data())?);
119        name_res.push(res)
120    }
121    Ok(name_res)
122}
123
124fn extract_ec_pubkey(eckey: &EcKey<Public>) -> Result<EcPublicKeyInfo> {
125    let mut ctx = BigNumContext::new().unwrap();
126    let mut x = BigNum::new().unwrap();
127    let mut y = BigNum::new().unwrap();
128    eckey
129        .public_key()
130        .affine_coordinates(eckey.group(), &mut x, &mut y, &mut ctx)
131        .unwrap();
132    Ok(template::EcPublicKeyInfo {
133        curve: curve_from_ecgroup(eckey.group())?,
134        public_key: template::EcPublicKey {
135            x: asn1bignum_to_bn(&x),
136            y: asn1bignum_to_bn(&y),
137        },
138    })
139}
140
141fn extract_pub_key(pubkey: &PKey<Public>) -> Result<SubjectPublicKeyInfo> {
142    match pubkey.id() {
143        openssl::pkey::Id::EC => Ok(SubjectPublicKeyInfo::EcPublicKey(extract_ec_pubkey(
144            &pubkey.ec_key().unwrap(),
145        )?)),
146        id => bail!("key type {:?} not supported by the parser", id),
147    }
148}
149
150fn extract_ecdsa_signature(x509: &X509) -> Result<EcdsaSignature> {
151    let ecdsa_sig = EcdsaSig::from_der(x509.signature().as_slice())
152        .context("cannot extract ECDSA signature from certificate")?;
153    Ok(EcdsaSignature {
154        r: asn1bignum_to_bn(ecdsa_sig.r()),
155        s: asn1bignum_to_bn(ecdsa_sig.s()),
156    })
157}
158
159fn extract_signature(x509: &X509) -> Result<Signature> {
160    match x509.signature_algorithm().object().nid() {
161        Nid::ECDSA_WITH_SHA256 => Ok(Signature::EcdsaWithSha256 {
162            value: Some(extract_ecdsa_signature(x509)?),
163        }),
164        alg => bail!("unsupported signature algorithm {:?}", alg),
165    }
166}
167
168/// Generate a X509 certificate from a pre-computed TBS and signature.
169pub fn generate_certificate_from_tbs(tbs: Vec<u8>, signature: &Signature) -> Result<Vec<u8>> {
170    // Generate certificate.
171    let tbs = Value::Literal(tbs);
172    let cert =
173        der::Der::generate(|builder| x509::X509::push_certificate(builder, &tbs, signature))?;
174    Ok(cert)
175}
176
177/// Generate a X509 certificate from a template that specifies all variables.
178/// If the template does not specify the values of the signature, a signature
179/// with "zero" values will be generated.
180pub fn generate_certificate(tmpl: &template::Template) -> Result<Vec<u8>> {
181    // Generate TBS.
182    let tbs =
183        der::Der::generate(|builder| x509::X509::push_tbs_certificate(builder, &tmpl.certificate))?;
184    let tbs = Value::Literal(tbs);
185    // Generate certificate.
186    let cert = der::Der::generate(|builder| {
187        x509::X509::push_certificate(builder, &tbs, &tmpl.certificate.signature)
188    })?;
189    Ok(cert)
190}
191
192fn get_subject_alt_name(x509: &X509) -> Result<Name> {
193    let Some(names) = x509.subject_alt_names() else {
194        return Ok(Name::default());
195    };
196    // We expect a single general name.
197    let mut iter = names.iter();
198    let Some(general_name) = iter.next() else {
199        return Ok(Name::default());
200    };
201    ensure!(
202        iter.next().is_none(),
203        "only one general name is supported for subject alt names"
204    );
205    let x509_name_ref = general_name
206        .directory_name()
207        .context("only directory names are supported for subject alt names")?;
208    asn1name_to_name("Subject Alternative Names", x509_name_ref)
209}
210
211/// Parse a X509 certificate
212pub fn parse_certificate(cert: &[u8]) -> Result<template::Certificate> {
213    let x509 = X509::from_der(cert).context("could not parse certificate with openssl")?;
214    let raw_extensions =
215        extension::x509_get_extensions(&x509).context("could not parse X509 extensions")?;
216    let mut private_extensions = Vec::new();
217    let mut basic_constraints = None;
218    let mut key_usage: Option<KeyUsage> = None;
219    let mut auth_key_id = None;
220    let mut subj_key_id = None;
221    for ext in raw_extensions {
222        match ext.object.nid() {
223            // Ignore extensions that are standard and handled by openssl.
224            Nid::BASIC_CONSTRAINTS => {
225                ensure!(
226                    basic_constraints.is_none(),
227                    "certificate contains several basic constraints extensions"
228                );
229                basic_constraints = Some(
230                    extension::parse_basic_constraints(&ext)
231                        .context("could not parse X509 basic constraints")?,
232                );
233            }
234            Nid::KEY_USAGE => {
235                key_usage = Some(
236                    extension::parse_key_usage(&ext).context("could not parse X509 key usage")?,
237                );
238            }
239            Nid::AUTHORITY_KEY_IDENTIFIER => {
240                // Although OpenSSL will parse the authority key identifier correctly, it will sometimes
241                // pretend that it doesn't exist if the certificate is unusual (i.e. all key usage bit
242                // set to false) without showing any error. Since this is very confusing, we may as well
243                // parse it ourselves.
244                auth_key_id = Some(Value::Literal(
245                    extension::parse_authority_key_id(&ext)
246                        .context("could not parse authority key identifier")?,
247                ));
248            }
249            Nid::SUBJECT_ALT_NAME => (),
250            Nid::SUBJECT_KEY_IDENTIFIER => {
251                // Same issue as with authority key identifier.
252                subj_key_id = Some(Value::Literal(
253                    extension::parse_subject_key_id(&ext)
254                        .context("could not parse subject key identifier")?,
255                ));
256            }
257            _ => private_extensions
258                .push(extension::parse_extension(&ext).context("could not parse X509 extension")?),
259        }
260    }
261
262    let subject_public_key_info = extract_pub_key(
263        &x509
264            .public_key()
265            .context("the X509 does not have a valid public key!")?,
266    )?;
267    Ok(template::Certificate {
268        serial_number: asn1int_to_bn("serial number", x509.serial_number())?,
269        issuer: asn1name_to_name("issuer", x509.issuer_name())?,
270        subject: asn1name_to_name("subject", x509.subject_name())?,
271        not_before: asn1time_to_string(x509.not_before())
272            .context("cannot parse not_before time")?,
273        not_after: asn1time_to_string(x509.not_after()).context("cannot parse not_after time")?,
274        subject_public_key_info,
275        authority_key_identifier: auth_key_id,
276        subject_key_identifier: subj_key_id,
277        basic_constraints,
278        key_usage,
279        subject_alt_name: get_subject_alt_name(&x509)?,
280        private_extensions,
281        signature: extract_signature(&x509)?,
282    })
283}