1use 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 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 let time_str = time.as_ptr() as *mut openssl_sys::ASN1_STRING;
89 let time_type = unsafe { openssl_sys::ASN1_STRING_type(time_str) };
91 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 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 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
168pub fn generate_certificate_from_tbs(tbs: Vec<u8>, signature: &Signature) -> Result<Vec<u8>> {
170 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
177pub fn generate_certificate(tmpl: &template::Template) -> Result<Vec<u8>> {
181 let tbs =
183 der::Der::generate(|builder| x509::X509::push_tbs_certificate(builder, &tmpl.certificate))?;
184 let tbs = Value::Literal(tbs);
185 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 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
211pub 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 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 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 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}