1#![forbid(unsafe_code)]
18
19use crate::certificate::MatterCertificate;
20use crate::error::{Error, Result};
21use crate::extensions::Extensions;
22use crate::name::DistinguishedName;
23use crate::public_key::PublicKey;
24use crate::signature::Signature;
25use crate::time::MatterTime;
26
27#[derive(Debug, Default)]
30pub struct Builder {
31 serial: Option<Vec<u8>>,
32 issuer: Option<DistinguishedName>,
33 not_before: Option<MatterTime>,
34 not_after: Option<MatterTime>,
35 subject: Option<DistinguishedName>,
36 public_key: Option<PublicKey>,
37 extensions: Option<Extensions>,
38}
39
40#[derive(Debug, Clone)]
45pub struct UnsignedCertificate {
46 serial: Vec<u8>,
47 issuer: DistinguishedName,
48 not_before: MatterTime,
49 not_after: MatterTime,
50 subject: DistinguishedName,
51 public_key: PublicKey,
52 extensions: Extensions,
53}
54
55impl Builder {
56 #[must_use]
58 pub fn serial(mut self, serial: Vec<u8>) -> Self {
59 self.serial = Some(serial);
60 self
61 }
62
63 #[must_use]
65 pub fn issuer(mut self, dn: DistinguishedName) -> Self {
66 self.issuer = Some(dn);
67 self
68 }
69
70 #[must_use]
72 pub fn subject(mut self, dn: DistinguishedName) -> Self {
73 self.subject = Some(dn);
74 self
75 }
76
77 #[must_use]
79 pub fn validity(mut self, not_before: MatterTime, not_after: MatterTime) -> Self {
80 self.not_before = Some(not_before);
81 self.not_after = Some(not_after);
82 self
83 }
84
85 #[must_use]
87 pub fn public_key(mut self, pk: PublicKey) -> Self {
88 self.public_key = Some(pk);
89 self
90 }
91
92 #[must_use]
94 pub fn extensions(mut self, ext: Extensions) -> Self {
95 self.extensions = Some(ext);
96 self
97 }
98
99 pub fn build_unsigned(self) -> Result<UnsignedCertificate> {
108 let serial = self.serial.ok_or(Error::MissingBuilderField("serial"))?;
109 if serial.is_empty() || serial.len() > 20 {
110 return Err(Error::FieldValueOutOfRange {
111 tag: crate::tlv_tags::CERT_SERIAL_NUMBER,
112 });
113 }
114 Ok(UnsignedCertificate {
115 serial,
116 issuer: self.issuer.ok_or(Error::MissingBuilderField("issuer"))?,
117 not_before: self
118 .not_before
119 .ok_or(Error::MissingBuilderField("not_before"))?,
120 not_after: self
121 .not_after
122 .ok_or(Error::MissingBuilderField("not_after"))?,
123 subject: self.subject.ok_or(Error::MissingBuilderField("subject"))?,
124 public_key: self
125 .public_key
126 .ok_or(Error::MissingBuilderField("public_key"))?,
127 extensions: self
128 .extensions
129 .ok_or(Error::MissingBuilderField("extensions"))?,
130 })
131 }
132}
133
134impl UnsignedCertificate {
135 pub fn tbs_der(&self) -> Result<Vec<u8>> {
144 let placeholder = MatterCertificate::from_fields(
148 self.serial.clone(),
149 self.issuer.clone(),
150 self.not_before,
151 self.not_after,
152 self.subject.clone(),
153 self.public_key.clone(),
154 self.extensions.clone(),
155 Signature::new([0u8; 64]),
156 );
157 placeholder.to_x509_tbs_der()
158 }
159
160 #[must_use]
164 pub fn assemble(self, signature: [u8; 64]) -> MatterCertificate {
165 MatterCertificate::from_fields(
166 self.serial,
167 self.issuer,
168 self.not_before,
169 self.not_after,
170 self.subject,
171 self.public_key,
172 self.extensions,
173 Signature::new(signature),
174 )
175 }
176}
177
178impl MatterCertificate {
179 #[must_use]
181 pub fn builder() -> Builder {
182 Builder::default()
183 }
184}
185
186#[cfg(test)]
187#[allow(clippy::unwrap_used, clippy::cast_possible_truncation)] mod tests {
189 use super::*;
190 use crate::extensions::{BasicConstraints, Extensions};
191 use crate::name::DnAttribute;
192
193 fn sample_public_key() -> PublicKey {
194 let mut key_bytes = [0u8; 65];
195 key_bytes[0] = 0x04;
196 for (i, b) in key_bytes.iter_mut().enumerate().skip(1) {
198 *b = i as u8;
199 }
200 PublicKey::new(key_bytes).unwrap()
201 }
202
203 #[test]
204 fn build_unsigned_then_assemble_roundtrips() {
205 let pk = sample_public_key();
206 let unsigned = MatterCertificate::builder()
207 .serial(vec![1, 2, 3])
208 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
209 .subject(DistinguishedName::new(vec![
210 DnAttribute::FabricId(7),
211 DnAttribute::NodeId(42),
212 ]))
213 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
214 .public_key(pk.clone())
215 .extensions(Extensions {
216 basic_constraints: Some(BasicConstraints {
217 is_ca: false,
218 path_len_constraint: None,
219 }),
220 ..Default::default()
221 })
222 .build_unsigned()
223 .unwrap();
224
225 let tbs = unsigned.tbs_der().unwrap();
227 assert!(!tbs.is_empty(), "TBS DER must be non-empty");
228
229 let cert = unsigned.assemble([0xAB; 64]);
230 let tlv = cert.to_tlv().unwrap();
232 let parsed = MatterCertificate::from_tlv(&tlv).unwrap();
233 assert_eq!(parsed, cert);
234 assert_eq!(
237 tbs,
238 cert.to_x509_tbs_der().unwrap(),
239 "TBS from unsigned must match TBS from assembled cert"
240 );
241 }
242
243 #[test]
244 fn build_unsigned_fails_on_missing_serial() {
245 let err = MatterCertificate::builder()
246 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
247 .subject(DistinguishedName::new(vec![DnAttribute::NodeId(42)]))
248 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
249 .public_key(sample_public_key())
250 .extensions(Extensions::default())
251 .build_unsigned()
252 .unwrap_err();
253 assert!(
254 matches!(err, Error::MissingBuilderField("serial")),
255 "got: {err:?}"
256 );
257 }
258
259 #[test]
260 fn build_unsigned_fails_on_missing_subject() {
261 let err = MatterCertificate::builder()
262 .serial(vec![1])
263 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
264 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
265 .public_key(sample_public_key())
266 .extensions(Extensions::default())
267 .build_unsigned()
268 .unwrap_err();
269 assert!(
270 matches!(err, Error::MissingBuilderField("subject")),
271 "got: {err:?}"
272 );
273 }
274
275 #[test]
276 fn build_unsigned_rejects_oversized_serial() {
277 let err = MatterCertificate::builder()
278 .serial(vec![0u8; 21])
279 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
280 .subject(DistinguishedName::new(vec![DnAttribute::NodeId(42)]))
281 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
282 .public_key(sample_public_key())
283 .extensions(Extensions::default())
284 .build_unsigned()
285 .unwrap_err();
286 assert!(
287 matches!(err, Error::FieldValueOutOfRange { .. }),
288 "got: {err:?}"
289 );
290 }
291
292 #[test]
293 fn build_unsigned_rejects_empty_serial() {
294 let err = MatterCertificate::builder()
295 .serial(vec![])
296 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
297 .subject(DistinguishedName::new(vec![DnAttribute::NodeId(42)]))
298 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
299 .public_key(sample_public_key())
300 .extensions(Extensions::default())
301 .build_unsigned()
302 .unwrap_err();
303 assert!(
304 matches!(err, Error::FieldValueOutOfRange { .. }),
305 "got: {err:?}"
306 );
307 }
308}