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 #[must_use]
137 pub fn extensions(&self) -> &Extensions {
138 &self.extensions
139 }
140
141 #[must_use]
143 pub fn subject(&self) -> &DistinguishedName {
144 &self.subject
145 }
146
147 #[must_use]
149 pub fn issuer(&self) -> &DistinguishedName {
150 &self.issuer
151 }
152
153 pub fn tbs_der(&self) -> Result<Vec<u8>> {
162 let placeholder = MatterCertificate::from_fields(
166 self.serial.clone(),
167 self.issuer.clone(),
168 self.not_before,
169 self.not_after,
170 self.subject.clone(),
171 self.public_key.clone(),
172 self.extensions.clone(),
173 Signature::new([0u8; 64]),
174 );
175 placeholder.to_x509_tbs_der()
176 }
177
178 #[must_use]
182 pub fn assemble(self, signature: [u8; 64]) -> MatterCertificate {
183 MatterCertificate::from_fields(
184 self.serial,
185 self.issuer,
186 self.not_before,
187 self.not_after,
188 self.subject,
189 self.public_key,
190 self.extensions,
191 Signature::new(signature),
192 )
193 }
194}
195
196impl MatterCertificate {
197 #[must_use]
199 pub fn builder() -> Builder {
200 Builder::default()
201 }
202}
203
204#[cfg(test)]
205#[allow(clippy::unwrap_used, clippy::cast_possible_truncation)] mod tests {
207 use super::*;
208 use crate::extensions::{BasicConstraints, Extensions};
209 use crate::name::DnAttribute;
210
211 fn sample_public_key() -> PublicKey {
212 let mut key_bytes = [0u8; 65];
213 key_bytes[0] = 0x04;
214 for (i, b) in key_bytes.iter_mut().enumerate().skip(1) {
216 *b = i as u8;
217 }
218 PublicKey::new(key_bytes).unwrap()
219 }
220
221 #[test]
222 fn build_unsigned_then_assemble_roundtrips() {
223 let pk = sample_public_key();
224 let unsigned = MatterCertificate::builder()
225 .serial(vec![1, 2, 3])
226 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
227 .subject(DistinguishedName::new(vec![
228 DnAttribute::FabricId(7),
229 DnAttribute::NodeId(42),
230 ]))
231 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
232 .public_key(pk.clone())
233 .extensions(Extensions {
234 basic_constraints: Some(BasicConstraints {
235 is_ca: false,
236 path_len_constraint: None,
237 }),
238 ..Default::default()
239 })
240 .build_unsigned()
241 .unwrap();
242
243 let tbs = unsigned.tbs_der().unwrap();
245 assert!(!tbs.is_empty(), "TBS DER must be non-empty");
246
247 let cert = unsigned.assemble([0xAB; 64]);
248 let tlv = cert.to_tlv().unwrap();
250 let parsed = MatterCertificate::from_tlv(&tlv).unwrap();
251 assert_eq!(parsed, cert);
252 assert_eq!(
255 tbs,
256 cert.to_x509_tbs_der().unwrap(),
257 "TBS from unsigned must match TBS from assembled cert"
258 );
259 }
260
261 #[test]
262 fn build_unsigned_fails_on_missing_serial() {
263 let err = MatterCertificate::builder()
264 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
265 .subject(DistinguishedName::new(vec![DnAttribute::NodeId(42)]))
266 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
267 .public_key(sample_public_key())
268 .extensions(Extensions::default())
269 .build_unsigned()
270 .unwrap_err();
271 assert!(
272 matches!(err, Error::MissingBuilderField("serial")),
273 "got: {err:?}"
274 );
275 }
276
277 #[test]
278 fn build_unsigned_fails_on_missing_subject() {
279 let err = MatterCertificate::builder()
280 .serial(vec![1])
281 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
282 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
283 .public_key(sample_public_key())
284 .extensions(Extensions::default())
285 .build_unsigned()
286 .unwrap_err();
287 assert!(
288 matches!(err, Error::MissingBuilderField("subject")),
289 "got: {err:?}"
290 );
291 }
292
293 #[test]
294 fn build_unsigned_rejects_oversized_serial() {
295 let err = MatterCertificate::builder()
296 .serial(vec![0u8; 21])
297 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
298 .subject(DistinguishedName::new(vec![DnAttribute::NodeId(42)]))
299 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
300 .public_key(sample_public_key())
301 .extensions(Extensions::default())
302 .build_unsigned()
303 .unwrap_err();
304 assert!(
305 matches!(err, Error::FieldValueOutOfRange { .. }),
306 "got: {err:?}"
307 );
308 }
309
310 #[test]
311 fn build_unsigned_rejects_empty_serial() {
312 let err = MatterCertificate::builder()
313 .serial(vec![])
314 .issuer(DistinguishedName::new(vec![DnAttribute::RcacId(1)]))
315 .subject(DistinguishedName::new(vec![DnAttribute::NodeId(42)]))
316 .validity(MatterTime(1_000), MatterTime::NO_EXPIRY)
317 .public_key(sample_public_key())
318 .extensions(Extensions::default())
319 .build_unsigned()
320 .unwrap_err();
321 assert!(
322 matches!(err, Error::FieldValueOutOfRange { .. }),
323 "got: {err:?}"
324 );
325 }
326}