Skip to main content

matc/
cert_x509.rs

1//! Handling of x509 certificate compatible with matter
2
3use byteorder::WriteBytesExt;
4use std::time::{Duration, SystemTime};
5
6use crate::tlv;
7use crate::util::asn1;
8use crate::util::cryptoutil;
9use anyhow::{Context, Result};
10
11fn add_ext(encoder: &mut asn1::Encoder, oid: &str, critical: bool, value: &[u8]) -> Result<()> {
12    encoder.start_seq(0x30)?;
13    encoder.write_oid(oid)?;
14    if critical {
15        encoder.write_bool(critical)?;
16    }
17    encoder.write_octet_string(value)?;
18    encoder.end_seq();
19    Ok(())
20}
21
22fn encode_nodeid(id: u64) -> String {
23    format!("{:0>16X}", id)
24}
25
26fn systemtime_to_x509_time(st: std::time::SystemTime) -> Result<String> {
27    let der_datetime = x509_cert::der::asn1::UtcTime::from_system_time(st)?;
28    let mut v = Vec::new();
29    x509_cert::der::EncodeValue::encode_value(&der_datetime, &mut v)?;
30    Ok(std::str::from_utf8(&v)?.to_owned())
31}
32
33const OID_MATTER_DN_NODE: &str = "1.3.6.1.4.1.37244.1.1";
34const OID_MATTER_DN_CA: &str = "1.3.6.1.4.1.37244.1.4";
35const OID_MATTER_DN_FABRIC: &str = "1.3.6.1.4.1.37244.1.5";
36
37const OID_SIG_ECDSA_WITH_SHA256: &str = "1.2.840.10045.4.3.2";
38
39pub(crate) const OID_CE_SUBJECT_KEY_IDENTIFIER: &str = "2.5.29.14";
40pub(crate) const OID_CE_KEY_USAGE: &str = "2.5.29.15";
41pub(crate) const OID_CE_BASIC_CONSTRAINTS: &str = "2.5.29.19";
42pub(crate) const OID_CE_EXT_KEU_USAGE: &str = "2.5.29.37";
43pub(crate) const OID_CE_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35";
44
45fn add_rdn(encoder: &mut asn1::Encoder, oid: &str, id: u64) -> Result<()> {
46    encoder.start_seq(0x31)?; //rdn
47    encoder.start_seq(0x30)?; //atv
48    encoder.write_oid(oid)?;
49    encoder.write_string(&encode_nodeid(id))?;
50    encoder.end_seq();
51    encoder.end_seq();
52    Ok(())
53}
54
55fn epoch2000_to_x509_time(secs: u32) -> Result<String> {
56    let st = SystemTime::UNIX_EPOCH
57        .checked_add(Duration::from_secs(946684800 + secs as u64))
58        .context("certificate time out of range")?;
59    systemtime_to_x509_time(st)
60}
61
62/// Reconstruct the X.509 TBSCertificate DER from a certificate in Matter TLV format.
63/// Matter certificate signatures are computed over the X.509 TBS, so this is needed to
64/// verify a Matter certificate against its issuer's public key.
65/// Only certificates produced by [encode_x509] (this library's own CA) round-trip exactly;
66/// certificates issued by other stacks may use encodings this reconstruction does not cover,
67/// in which case signature verification will fail.
68pub(crate) fn matter_cert_to_x509_tbs(matter_cert: &[u8]) -> Result<Vec<u8>> {
69    let cert = tlv::decode_tlv(matter_cert)?;
70    let serial = cert
71        .get_octet_string(&[1])
72        .context("matter cert: serial missing")?;
73    let issuer_ca_id = cert
74        .get_int(&[3, 20])
75        .context("matter cert: issuer ca id missing")?;
76    let not_before = cert.get_int(&[4]).context("matter cert: not_before missing")? as u32;
77    let not_after = cert.get_int(&[5]).context("matter cert: not_after missing")? as u32;
78    let public_key = cert
79        .get_octet_string(&[9])
80        .context("matter cert: public key missing")?;
81    let is_ca = cert.get_bool(&[10, 1, 1]).unwrap_or(false);
82    let subject_key_id = cert
83        .get_octet_string(&[10, 4])
84        .context("matter cert: subject key id missing")?;
85    let authority_key_id = cert
86        .get_octet_string(&[10, 5])
87        .context("matter cert: authority key id missing")?;
88
89    let mut encoder = asn1::Encoder::new();
90    encoder.start_seq(0x30)?;
91
92    encoder.start_seq(0xa0)?;
93    encoder.write_int(2)?; // version
94    encoder.end_seq();
95
96    encoder.write_octet_string_with_tag(0x2, serial)?; // serial INTEGER content bytes
97
98    encoder.start_seq(0x30)?; //signature algorithm
99    encoder.write_oid(OID_SIG_ECDSA_WITH_SHA256)?;
100    encoder.end_seq();
101
102    encoder.start_seq(0x30)?; //issuer
103    add_rdn(&mut encoder, OID_MATTER_DN_CA, issuer_ca_id)?;
104    encoder.end_seq();
105
106    encoder.start_seq(0x30)?; //validity
107    encoder.write_string_with_tag(0x17, &epoch2000_to_x509_time(not_before)?)?;
108    encoder.write_string_with_tag(0x17, &epoch2000_to_x509_time(not_after)?)?;
109    encoder.end_seq();
110
111    encoder.start_seq(0x30)?; //subject
112    if is_ca {
113        let subject_ca_id = cert
114            .get_int(&[6, 20])
115            .context("matter cert: subject ca id missing")?;
116        add_rdn(&mut encoder, OID_MATTER_DN_CA, subject_ca_id)?;
117    } else {
118        let node_id = cert
119            .get_int(&[6, 17])
120            .context("matter cert: subject node id missing")?;
121        let fabric_id = cert
122            .get_int(&[6, 21])
123            .context("matter cert: subject fabric id missing")?;
124        add_rdn(&mut encoder, OID_MATTER_DN_NODE, node_id)?;
125        add_rdn(&mut encoder, OID_MATTER_DN_FABRIC, fabric_id)?;
126    }
127    encoder.end_seq();
128
129    encoder.start_seq(0x30)?; //subject key info
130    encoder.start_seq(0x30)?; //algorithm
131    encoder.write_oid("1.2.840.10045.2.1")?;
132    encoder.write_oid("1.2.840.10045.3.1.7")?;
133    encoder.end_seq();
134    let mut pk2 = vec![0u8];
135    pk2.extend_from_slice(public_key);
136    encoder.write_octet_string_with_tag(0x3, &pk2)?;
137    encoder.end_seq();
138
139    let subjectkeyidasn = {
140        let mut encoder = asn1::Encoder::new();
141        encoder.write_octet_string(subject_key_id)?;
142        encoder.encode()
143    };
144
145    let authoritykey_sha1_asn = {
146        let mut encoder = asn1::Encoder::new();
147        encoder.start_seq(0x30)?;
148        encoder.write_octet_string_with_tag(0x80, authority_key_id)?;
149        encoder.encode()
150    };
151
152    encoder.start_seq(0xa3)?;
153    encoder.start_seq(0x30)?;
154    if is_ca {
155        add_ext(
156            &mut encoder,
157            OID_CE_BASIC_CONSTRAINTS,
158            true,
159            &[0x30, 0x03, 0x01, 0x01, 0xFF],
160        )?;
161        add_ext(
162            &mut encoder,
163            OID_CE_KEY_USAGE,
164            true,
165            &[0x03, 0x02, 0x01, 0x06],
166        )?;
167    } else {
168        add_ext(&mut encoder, OID_CE_BASIC_CONSTRAINTS, true, &[0x30, 0x00])?;
169        add_ext(
170            &mut encoder,
171            OID_CE_KEY_USAGE,
172            true,
173            &[0x03, 0x02, 0x07, 0x80],
174        )?;
175        let mut ext_ku_encoder = asn1::Encoder::new();
176        ext_ku_encoder.start_seq(0x30)?;
177        ext_ku_encoder.write_oid("1.3.6.1.5.5.7.3.2")?; // client-auth
178        ext_ku_encoder.write_oid("1.3.6.1.5.5.7.3.1")?; // server-auth
179        let ext_ku_bytes = ext_ku_encoder.encode();
180        add_ext(&mut encoder, OID_CE_EXT_KEU_USAGE, true, &ext_ku_bytes)?;
181    }
182    add_ext(
183        &mut encoder,
184        OID_CE_SUBJECT_KEY_IDENTIFIER,
185        false,
186        &subjectkeyidasn,
187    )?;
188    add_ext(
189        &mut encoder,
190        OID_CE_AUTHORITY_KEY_IDENTIFIER,
191        false,
192        &authoritykey_sha1_asn,
193    )?;
194    encoder.end_seq();
195    encoder.end_seq();
196    encoder.end_seq();
197
198    Ok(encoder.encode())
199}
200
201/// Create matter compatible certificate in x509 format.
202pub fn encode_x509(
203    node_public_key: &[u8],
204    node_id: u64,
205    fabric_id: u64,
206    ca_id: u64,
207    ca_private: &p256::SecretKey,
208    ca: bool,
209) -> Result<Vec<u8>> {
210    let mut encoder = asn1::Encoder::new();
211    encoder.start_seq(0x30)?;
212    encoder.start_seq(0x30)?;
213
214    encoder.start_seq(0xa0)?;
215    encoder.write_int(2)?; // version
216    encoder.end_seq();
217
218    encoder.write_int(10001)?; // serial
219
220    encoder.start_seq(0x30)?; //signature algorithm
221    encoder.write_oid(OID_SIG_ECDSA_WITH_SHA256)?;
222    encoder.end_seq();
223
224    encoder.start_seq(0x30)?; //issuer
225    add_rdn(&mut encoder, OID_MATTER_DN_CA, ca_id)?;
226    encoder.end_seq();
227
228    encoder.start_seq(0x30)?; //validity
229
230    let now = SystemTime::now();
231    encoder.write_string_with_tag(0x17, &systemtime_to_x509_time(now)?)?;
232    let not_after = now
233        .checked_add(Duration::from_secs(60 * 60 * 24 * 100))
234        .context("time continuity error")?;
235    encoder.write_string_with_tag(0x17, &systemtime_to_x509_time(not_after)?)?;
236    encoder.end_seq();
237
238    if ca {
239        encoder.start_seq(0x30)?; //subject
240        add_rdn(&mut encoder, OID_MATTER_DN_CA, node_id)?;
241        encoder.end_seq();
242    } else {
243        encoder.start_seq(0x30)?; //subject
244        add_rdn(&mut encoder, OID_MATTER_DN_NODE, node_id)?;
245        add_rdn(&mut encoder, OID_MATTER_DN_FABRIC, fabric_id)?;
246        encoder.end_seq();
247    }
248
249    encoder.start_seq(0x30)?; //subject key info
250    encoder.start_seq(0x30)?; //algorithm
251    encoder.write_oid("1.2.840.10045.2.1")?;
252    encoder.write_oid("1.2.840.10045.3.1.7")?;
253    encoder.end_seq();
254
255    let mut pk2 = Vec::new();
256    pk2.write_u8(0)?;
257
258    pk2.extend_from_slice(node_public_key);
259    encoder.write_octet_string_with_tag(0x3, &pk2)?;
260    encoder.end_seq();
261
262    let subjectkeyidasn = {
263        let mut encoder = asn1::Encoder::new();
264        encoder.write_octet_string(&cryptoutil::sha1_enc(node_public_key))?;
265        encoder.encode()
266    };
267
268    let authoritykey_sha1_asn = {
269        let mut encoder = asn1::Encoder::new();
270        encoder.start_seq(0x30)?;
271        let pubkey = ca_private.public_key().to_sec1_bytes();
272        encoder.write_octet_string_with_tag(0x80, &cryptoutil::sha1_enc(&pubkey))?;
273        encoder.encode()
274    };
275
276    encoder.start_seq(0xa3)?;
277    encoder.start_seq(0x30)?;
278    // basic constraints
279    if ca {
280        add_ext(
281            &mut encoder,
282            OID_CE_BASIC_CONSTRAINTS,
283            true,
284            &[0x30, 0x03, 0x01, 0x01, 0xFF],
285        )?
286    } else {
287        add_ext(&mut encoder, OID_CE_BASIC_CONSTRAINTS, true, &[0x30, 0x00])?
288    }
289    // key usage
290    if ca {
291        add_ext(
292            &mut encoder,
293            OID_CE_KEY_USAGE,
294            true,
295            &[0x03, 0x02, 0x01, 0x06],
296        )?;
297    } else {
298        add_ext(
299            &mut encoder,
300            OID_CE_KEY_USAGE,
301            true,
302            &[0x03, 0x02, 0x07, 0x80],
303        )?;
304    }
305    //ext key usage
306    if !ca {
307        let mut ext_ku_encoder = asn1::Encoder::new();
308        ext_ku_encoder.start_seq(0x30)?;
309        ext_ku_encoder.write_oid("1.3.6.1.5.5.7.3.2")?; // client-auth
310        ext_ku_encoder.write_oid("1.3.6.1.5.5.7.3.1")?; // server-auth
311        let ext_ku_bytes = ext_ku_encoder.encode();
312        add_ext(&mut encoder, OID_CE_EXT_KEU_USAGE, true, &ext_ku_bytes)?;
313    }
314    //subject key id
315    add_ext(
316        &mut encoder,
317        OID_CE_SUBJECT_KEY_IDENTIFIER,
318        false,
319        &subjectkeyidasn,
320    )?;
321
322    //authority key id
323    add_ext(
324        &mut encoder,
325        OID_CE_AUTHORITY_KEY_IDENTIFIER,
326        false,
327        &authoritykey_sha1_asn,
328    )?;
329
330    encoder.end_seq();
331    encoder.end_seq();
332    encoder.end_seq();
333
334    let to_sign = encoder.clone();
335    let to_sign_bytes = &to_sign.encode()[4..];
336    let key = ecdsa::SigningKey::from(ca_private);
337    let signed = key.sign_recoverable(to_sign_bytes)?.0;
338
339    encoder.start_seq(0x30)?; //alg
340    encoder.write_oid(OID_SIG_ECDSA_WITH_SHA256)?;
341    encoder.end_seq();
342    let mut signed_b = vec![0];
343    signed_b.extend_from_slice(signed.to_der().as_bytes());
344
345    encoder.write_octet_string_with_tag(0x3, &signed_b)?;
346
347    let res = encoder.encode();
348
349    Ok(res)
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    fn der_element(der: &[u8]) -> &[u8] {
357        match der[1] {
358            l if l < 0x80 => &der[..2 + l as usize],
359            0x81 => &der[..3 + der[2] as usize],
360            0x82 => &der[..4 + ((der[2] as usize) << 8) + der[3] as usize],
361            _ => panic!("unsupported DER length"),
362        }
363    }
364
365    #[test]
366    fn test_matter_cert_to_x509_tbs_roundtrip() -> Result<()> {
367        let ca_secret = p256::SecretKey::random(&mut rand::thread_rng());
368        let ca_public = ca_secret.public_key().to_sec1_bytes();
369        for is_ca in [false, true] {
370            let node_secret = p256::SecretKey::random(&mut rand::thread_rng());
371            let node_public = node_secret.public_key().to_sec1_bytes();
372            let x509 = encode_x509(&node_public, 1111, 1234, 5678, &ca_secret, is_ca)?;
373            let matter = crate::cert_matter::convert_x509_bytes_to_matter(&x509, &ca_public)?;
374            let tbs = matter_cert_to_x509_tbs(&matter)?;
375
376            let header = match x509[1] {
377                l if l < 0x80 => 2,
378                0x81 => 3,
379                0x82 => 4,
380                _ => panic!("unsupported DER length"),
381            };
382            let expected_tbs = der_element(&x509[header..]);
383            assert_eq!(tbs, expected_tbs, "reconstructed TBS must match original (is_ca={})", is_ca);
384
385            let cert_tlv = tlv::decode_tlv(&matter)?;
386            let sig = cert_tlv.get_octet_string(&[11]).unwrap();
387            let verifying_key =
388                ecdsa::VerifyingKey::from(p256::PublicKey::from_sec1_bytes(&ca_public)?);
389            let sig = ecdsa::Signature::<p256::NistP256>::from_slice(sig)?;
390            ecdsa::signature::Verifier::verify(&verifying_key, &tbs, &sig)?;
391        }
392        Ok(())
393    }
394}