synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
//! Builder for CRMF (RFC 4211) certificate request messages.
//!
//! [`CertReqMsgBuilder`] assembles a single `CertReqMsg` DER from pre-encoded
//! Name and SubjectPublicKeyInfo bytes.  [`CertReqMessagesBuilder`] wraps a
//! list of pre-encoded `CertReqMsg` DER blobs in the outer `SEQUENCE OF`
//! envelope.
//!
//! Both builders follow the same consume-and-return (fluent) style as
//! [`CertificateBuilder`](crate::CertificateBuilder): each setter takes
//! `self` by value, mutates it, and returns it so calls can be chained.
//!
//! # Example
//!
//! ```rust,ignore
//! use synta_certificate::{CertReqMsgBuilder, CertReqMessagesBuilder};
//!
//! let msg_der = CertReqMsgBuilder::new()
//!     .cert_req_id(0)
//!     .subject_name(&subject_name_der)
//!     .popo_ra_verified()
//!     .build()?;
//!
//! let msgs_der = CertReqMessagesBuilder::new()
//!     .add_request_der(&msg_der)
//!     .build()?;
//! ```

use synta::tag::TAG_SEQUENCE;
use synta::Tag;
use synta::{Decoder, Encoding, Integer, Null, ObjectIdentifier, RawDer};

use crate::builder::BuilderError;
use crate::crmf_types::{
    AttributeTypeAndValue, CertReqMsg, CertRequest, CertTemplate, PKIPublicationInfo,
    ProofOfPossession, SinglePubInfo,
};
use crate::{GeneralNameSpec, Name, SubjectPublicKeyInfo};

// ── pub_method integer constants (RFC 4211 §11) ───────────────────────────────

/// `pubMethod` value: `dontCare(0)` — no publication preference.
pub const PUB_METHOD_DONT_CARE: i64 = 0;
/// `pubMethod` value: `x500(1)` — publish in an X.500 directory.
pub const PUB_METHOD_X500: i64 = 1;
/// `pubMethod` value: `web(2)` — publish on the web (HTTP/HTTPS URI).
pub const PUB_METHOD_WEB: i64 = 2;
/// `pubMethod` value: `ldap(3)` — publish in an LDAP directory.
pub const PUB_METHOD_LDAP: i64 = 3;

// ── CertReqMsgBuilder ─────────────────────────────────────────────────────────

/// Builder for a single `CertReqMsg` DER (RFC 4211 §4).
///
/// Store pre-encoded Name / SubjectPublicKeyInfo DER bytes and call
/// [`build`](Self::build) to produce the DER-encoded `CertReqMsg` SEQUENCE.
///
/// The builder also supports setting a `PKIPublicationInfo` control via
/// [`add_pub_info`](Self::add_pub_info) and the convenience methods
/// [`pub_location_uri`](Self::pub_location_uri),
/// [`pub_location_rfc822`](Self::pub_location_rfc822),
/// [`pub_location_dns`](Self::pub_location_dns), and
/// [`pub_location_directory_name`](Self::pub_location_directory_name).
///
/// ```rust,ignore
/// use synta_certificate::{CertReqMsgBuilder, PUB_METHOD_WEB};
///
/// let msg_der = CertReqMsgBuilder::new()
///     .cert_req_id(0)
///     .subject_name(&name_der)
///     .pub_location_uri(PUB_METHOD_WEB, "http://certs.example.com/")
///     .popo_ra_verified()
///     .build()?;
/// ```
pub struct CertReqMsgBuilder {
    cert_req_id: i64,
    /// Pre-encoded DER of the subject `Name` SEQUENCE TLV.
    subject_der: Option<Vec<u8>>,
    /// Pre-encoded DER of the issuer `Name` SEQUENCE TLV.
    issuer_der: Option<Vec<u8>>,
    /// Pre-encoded DER of the `SubjectPublicKeyInfo` SEQUENCE TLV.
    spki_der: Option<Vec<u8>>,
    popo_ra_verified: bool,
    /// `PKIPublicationInfo` action integer (0 = dontPublish, 1 = pleasePublish).
    pub_info_action: i64,
    /// `SinglePubInfo` entries: `(pub_method, Option<GeneralNameSpec>)`.
    pub_infos: Vec<(i64, Option<GeneralNameSpec>)>,
}

impl Default for CertReqMsgBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl CertReqMsgBuilder {
    /// Create a new, empty builder with `certReqId` = 0.
    pub fn new() -> Self {
        Self {
            cert_req_id: 0,
            subject_der: None,
            issuer_der: None,
            spki_der: None,
            popo_ra_verified: false,
            pub_info_action: 1, // pleasePublish
            pub_infos: Vec::new(),
        }
    }

    /// Set the `certReqId` integer identifying this request within the batch.
    pub fn cert_req_id(mut self, id: i64) -> Self {
        self.cert_req_id = id;
        self
    }

    /// Set the subject `Name` from a pre-encoded DER SEQUENCE TLV.
    ///
    /// The bytes are decoded at [`build`](Self::build) time to verify they
    /// form a valid `Name`.  Pass the output of `NameBuilder::build()` or
    /// `Certificate::subject_raw_der()` directly.
    pub fn subject_name(mut self, name_der: &[u8]) -> Self {
        self.subject_der = Some(name_der.to_vec());
        self
    }

    /// Set the issuer `Name` from a pre-encoded DER SEQUENCE TLV.
    pub fn issuer_name(mut self, name_der: &[u8]) -> Self {
        self.issuer_der = Some(name_der.to_vec());
        self
    }

    /// Set the `SubjectPublicKeyInfo` from a pre-encoded DER SEQUENCE TLV.
    ///
    /// Pass the output of `CertificationRequest::subject_public_key_info_der()`
    /// or `Certificate::subject_public_key_info_der()` directly.
    pub fn public_key_der(mut self, spki_der: &[u8]) -> Self {
        self.spki_der = Some(spki_der.to_vec());
        self
    }

    /// Set the proof-of-possession to `raVerified` (`[0] IMPLICIT NULL`).
    ///
    /// This is the simplest POPO type — it asserts that the RA has already
    /// verified possession of the private key.  No signing is required.
    pub fn popo_ra_verified(mut self) -> Self {
        self.popo_ra_verified = true;
        self
    }

    /// Set the `PKIPublicationInfo` `action` integer.
    ///
    /// `0` = `dontPublish`; `1` = `pleasePublish` (default).
    pub fn publication_action(mut self, action: i64) -> Self {
        self.pub_info_action = action;
        self
    }

    /// Add a `SinglePubInfo` entry with no `pubLocation`.
    ///
    /// Use one of the `pubMethod` constants from this module:
    /// [`PUB_METHOD_DONT_CARE`], [`PUB_METHOD_X500`], [`PUB_METHOD_WEB`],
    /// or [`PUB_METHOD_LDAP`].
    pub fn add_pub_info(mut self, pub_method: i64) -> Self {
        self.pub_infos.push((pub_method, None));
        self
    }

    /// Add a `SinglePubInfo` with a `uniformResourceIdentifier` `pubLocation`.
    ///
    /// Convenience wrapper for the common case of a web (`pubMethod = 2`) or
    /// LDAP (`pubMethod = 3`) URL.
    pub fn pub_location_uri(mut self, pub_method: i64, uri: &str) -> Self {
        self.pub_infos
            .push((pub_method, Some(GeneralNameSpec::uri(uri))));
        self
    }

    /// Add a `SinglePubInfo` with an `rfc822Name` `pubLocation`.
    pub fn pub_location_rfc822(mut self, pub_method: i64, email: &str) -> Self {
        self.pub_infos
            .push((pub_method, Some(GeneralNameSpec::rfc822(email))));
        self
    }

    /// Add a `SinglePubInfo` with a `dNSName` `pubLocation`.
    pub fn pub_location_dns(mut self, pub_method: i64, host: &str) -> Self {
        self.pub_infos
            .push((pub_method, Some(GeneralNameSpec::dns(host))));
        self
    }

    /// Add a `SinglePubInfo` with a `directoryName` `pubLocation` from a
    /// pre-encoded `Name` DER SEQUENCE TLV.
    pub fn pub_location_directory_name(mut self, pub_method: i64, name_der: &[u8]) -> Self {
        self.pub_infos
            .push((pub_method, Some(GeneralNameSpec::directory_name(name_der))));
        self
    }

    /// Build and return the DER-encoded `CertReqMsg` SEQUENCE.
    ///
    /// # Errors
    ///
    /// Returns [`BuilderError::EncodeError`] if any stored Name or SPKI bytes
    /// cannot be decoded, if any `GeneralNameSpec` is invalid, or if ASN.1
    /// encoding fails.
    pub fn build(self) -> Result<Vec<u8>, BuilderError> {
        // Move owned byte buffers out of self so we can borrow from them.
        let subject_bytes = self.subject_der;
        let issuer_bytes = self.issuer_der;
        let spki_bytes = self.spki_der;

        let subject = match subject_bytes {
            Some(ref bytes) => Some(
                Decoder::new(bytes, Encoding::Der)
                    .decode::<Name<'_>>()
                    .map_err(|e| BuilderError::EncodeError(e.to_string()))?,
            ),
            None => None,
        };

        let issuer = match issuer_bytes {
            Some(ref bytes) => Some(
                Decoder::new(bytes, Encoding::Der)
                    .decode::<Name<'_>>()
                    .map_err(|e| BuilderError::EncodeError(e.to_string()))?,
            ),
            None => None,
        };

        let public_key = match spki_bytes {
            Some(ref bytes) => Some(
                Decoder::new(bytes, Encoding::Der)
                    .decode::<SubjectPublicKeyInfo<'_>>()
                    .map_err(|e| BuilderError::EncodeError(e.to_string()))?,
            ),
            None => None,
        };

        let cert_template = CertTemplate {
            subject,
            issuer,
            public_key,
            ..Default::default()
        };

        let popo = if self.popo_ra_verified {
            Some(ProofOfPossession::RaVerified(Null))
        } else {
            None
        };

        // Build the optional PKIPublicationInfo control.
        // Keep all intermediate DER buffers alive until msg is encoded.
        let pub_info_der_opt = if !self.pub_infos.is_empty() {
            Some(build_publication_info_der(
                self.pub_info_action,
                &self.pub_infos,
            )?)
        } else {
            None
        };
        // Encode PKIPublicationInfo as AttributeTypeAndValue DER.
        let attr_der_opt: Option<Vec<u8>> = match pub_info_der_opt {
            Some(ref pi_der) => {
                let oid =
                    ObjectIdentifier::new(crate::crmf_types::ID_REG_CTRL_PKI_PUBLICATION_INFO)
                        .map_err(|e| BuilderError::EncodeError(e.to_string()))?;
                let attr = AttributeTypeAndValue {
                    r#type: oid,
                    value: RawDer(pi_der),
                };
                Some(
                    attr.to_der()
                        .map_err(|e| BuilderError::EncodeError(e.to_string()))?,
                )
            }
            None => None,
        };
        // Decode attr_der back to AttributeTypeAndValue borrowing from attr_der_opt.
        // Both locals live until the end of build(), past the enc.encode(&msg) call.
        let controls: Option<Vec<AttributeTypeAndValue<'_>>> = match attr_der_opt {
            Some(ref a_der) => {
                let attr_decoded = Decoder::new(a_der, Encoding::Der)
                    .decode::<AttributeTypeAndValue<'_>>()
                    .map_err(|e| BuilderError::EncodeError(e.to_string()))?;
                Some(vec![attr_decoded])
            }
            None => None,
        };

        let msg = CertReqMsg {
            cert_req: CertRequest {
                cert_req_id: Integer::from_i64(self.cert_req_id),
                cert_template,
                controls,
            },
            popo,
            reg_info: None,
        };

        msg.to_der()
            .map_err(|e| BuilderError::EncodeError(e.to_string()))
    }
}

/// Encode a `PKIPublicationInfo` SEQUENCE from the given action integer and
/// list of `(pub_method, Option<GeneralNameSpec>)` entries.
///
/// Returns the raw DER TLV bytes.
fn build_publication_info_der(
    action: i64,
    pub_infos: &[(i64, Option<GeneralNameSpec>)],
) -> Result<Vec<u8>, BuilderError> {
    // First pass: resolve each GeneralNameSpec → encoded GeneralName DER bytes.
    let mut gn_bufs: Vec<Option<Vec<u8>>> = Vec::with_capacity(pub_infos.len());
    for (_, spec_opt) in pub_infos {
        let buf = match spec_opt {
            None => None,
            Some(spec) => {
                let gn = spec
                    .to_general_name()
                    .map_err(|e| BuilderError::EncodeError(e.to_string()))?;
                Some(
                    gn.to_der()
                        .map_err(|e| BuilderError::EncodeError(e.to_string()))?,
                )
            }
        };
        gn_bufs.push(buf);
    }

    // Second pass: decode each GeneralName DER buffer and build SinglePubInfo.
    // gn_bufs lives for the entirety of this function, outliving single_infos.
    let mut single_infos: Vec<SinglePubInfo<'_>> = Vec::with_capacity(pub_infos.len());
    for (i, (method, _)) in pub_infos.iter().enumerate() {
        let pub_location = match gn_bufs[i] {
            None => None,
            Some(ref bytes) => Some(
                Decoder::new(bytes, Encoding::Der)
                    .decode::<crate::GeneralName<'_>>()
                    .map_err(|e| BuilderError::EncodeError(e.to_string()))?,
            ),
        };
        single_infos.push(SinglePubInfo {
            pub_method: Integer::from_i64(*method),
            pub_location,
        });
    }

    let pub_info = PKIPublicationInfo {
        action: Integer::from_i64(action),
        pub_infos: Some(single_infos),
    };

    pub_info
        .to_der()
        .map_err(|e| BuilderError::EncodeError(e.to_string()))
}

// ── CertReqMessagesBuilder ────────────────────────────────────────────────────

/// Builder for a `CertReqMessages` `SEQUENCE OF CertReqMsg` (RFC 4211 §3).
///
/// Add pre-encoded `CertReqMsg` DER blobs (from [`CertReqMsgBuilder::build`])
/// and call [`build`](Self::build) to produce the outer SEQUENCE envelope.
///
/// ```rust,ignore
/// use synta_certificate::{CertReqMsgBuilder, CertReqMessagesBuilder};
///
/// let msgs_der = CertReqMessagesBuilder::new()
///     .add_request_der(&msg1_der)
///     .add_request_der(&msg2_der)
///     .build()?;
/// ```
pub struct CertReqMessagesBuilder {
    /// Pre-encoded `CertReqMsg` DER blobs (each is a complete SEQUENCE TLV).
    requests: Vec<Vec<u8>>,
}

impl Default for CertReqMessagesBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl CertReqMessagesBuilder {
    /// Create a new, empty builder.
    pub fn new() -> Self {
        Self {
            requests: Vec::new(),
        }
    }

    /// Append a pre-encoded `CertReqMsg` DER blob to the batch.
    ///
    /// The bytes must be a valid DER-encoded `CertReqMsg` SEQUENCE TLV, as
    /// returned by [`CertReqMsgBuilder::build`].
    pub fn add_request_der(mut self, cert_req_msg_der: &[u8]) -> Self {
        self.requests.push(cert_req_msg_der.to_vec());
        self
    }

    /// Build and return the DER-encoded `CertReqMessages` `SEQUENCE OF`.
    ///
    /// # Errors
    ///
    /// Returns [`BuilderError::EncodeError`] if ASN.1 encoding fails.
    pub fn build(self) -> Result<Vec<u8>, BuilderError> {
        let total: usize = self.requests.iter().map(|r| r.len()).sum();
        let mut enc = synta::Encoder::with_capacity(Encoding::Der, total + 4);

        enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))
            .map_err(|e| BuilderError::EncodeError(e.to_string()))?;

        for req_der in &self.requests {
            enc.encode(&RawDer(req_der))
                .map_err(|e| BuilderError::EncodeError(e.to_string()))?;
        }

        enc.end_constructed()
            .map_err(|e| BuilderError::EncodeError(e.to_string()))?;

        enc.finish()
            .map_err(|e| BuilderError::EncodeError(e.to_string()))
    }
}