Skip to content

Commit ea3e44f

Browse files
committed
Add visitor pattern for Certification Request Info object and attributes (see #206)
1 parent 4c4b831 commit ea3e44f

File tree

2 files changed

+345
-0
lines changed

2 files changed

+345
-0
lines changed

src/visitor/cri_visitor.rs

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
use asn1_rs::{Any, Input, Oid};
2+
3+
use crate::certification_request::*;
4+
use crate::cri_attributes::*;
5+
use crate::x509::{SubjectPublicKeyInfo, X509Name, X509Version};
6+
7+
/// Visitor pattern for [`X509CertificationRequestInfo`]
8+
///
9+
/// The trait lifetime is the lifetime of the Certification Request Info (CRI).
10+
/// It is required so the visitor object (the implementer) can declare that
11+
/// it will outlive the CRI, allowing it to keep references on attribute data.
12+
///
13+
/// To visit the attribute values, see the [`X509CriAttributeVisitor`] trait.
14+
///
15+
/// # Examples
16+
///
17+
/// The following visitor implementation will count the number of attributes from this
18+
/// Certification Request Info.
19+
///
20+
/// ```rust
21+
/// use x509_parser::prelude::*;
22+
/// use x509_parser::visitor::X509CertificationRequestInfoVisitor;
23+
///
24+
/// #[derive(Default)]
25+
/// struct CRIVisitor {
26+
/// num_attributes: usize,
27+
/// }
28+
///
29+
/// impl X509CertificationRequestInfoVisitor<'_> for CRIVisitor {
30+
/// fn visit_attribute(&mut self, attribute: &X509CriAttribute<'_>) {
31+
/// self.num_attributes += 1;
32+
/// }
33+
///
34+
/// fn visit_subject(&mut self, name: &X509Name<'_>) {
35+
/// eprintln!("{name:?}");
36+
/// }
37+
/// }
38+
/// ```
39+
#[allow(unused_variables)]
40+
pub trait X509CertificationRequestInfoVisitor<'cri> {
41+
/// Run the provided visitor (`self`) over the [`X509CertificationRequestInfo`] object
42+
fn walk(&mut self, cri: &'cri X509CertificationRequestInfo)
43+
where
44+
Self: Sized,
45+
{
46+
cri.walk(self);
47+
}
48+
49+
/// Invoked for the "version" field of the Certification Request Info
50+
fn visit_version(&mut self, version: &'cri X509Version) {}
51+
52+
/// Invoked for the "subject" field of the Certification Request Info
53+
fn visit_subject(&mut self, name: &'cri X509Name) {}
54+
55+
/// Invoked for the "subjectPublicKeyInfo" field of the Certification Request Info
56+
fn visit_subject_public_key_info(&mut self, subject_pki: &'cri SubjectPublicKeyInfo) {}
57+
58+
/// Invoked for attributes, before visiting children
59+
fn pre_visit_attributes(&mut self, attributes: &'cri [X509CriAttribute]) {}
60+
61+
/// Invoked for any attribute that appear in the X.509 Certification Request Info
62+
///
63+
/// To visit the attribute values, see the [`X509CriAttributeVisitor`] trait.
64+
///
65+
/// Note: this method may be redundant with any other attribute visitor method
66+
fn visit_attribute(&mut self, attribute: &'cri X509CriAttribute) {}
67+
68+
/// Invoked for attributes, after visiting children
69+
fn post_visit_attributes(&mut self, attributes: &'cri [X509CriAttribute]) {}
70+
}
71+
72+
impl X509CertificationRequestInfo<'_> {
73+
/// Run the provided [`X509CertificationRequestInfoVisitor`] over the X.509 Certification Request Info (`self`)
74+
pub fn walk<'cri, V: X509CertificationRequestInfoVisitor<'cri>>(&'cri self, visitor: &mut V) {
75+
let v = visitor;
76+
v.visit_version(&self.version);
77+
v.visit_subject(&self.subject);
78+
v.visit_subject_public_key_info(&self.subject_pki);
79+
80+
v.pre_visit_attributes(self.attributes());
81+
for attribute in self.attributes() {
82+
v.visit_attribute(attribute);
83+
}
84+
v.post_visit_attributes(self.attributes());
85+
}
86+
}
87+
88+
/// Visitor pattern for [`X509CriAttribute`]
89+
///
90+
/// An Attribute contains a `SET OF AttributeValue`. Different methods are provided:
91+
/// - `visit_raw_input`: inspects the raw `SET` contents (unparsed)
92+
/// - `visit_raw_value`: inspects each raw `AttributeValue` (parsed as `ANY`) from the SET
93+
/// - `visit_attribute_...`: inspect a parsed `AttributeValue` with a specific type
94+
///
95+
/// Note that some methods are (voluntarily) redundant, as they provide alternative methods
96+
/// to handle data. This is not a problem because default methods do nothing,
97+
/// but if a trait implementation provides methods for ex visiting both raw input and parsed attributes,
98+
/// it must be aware that it will visit the same attributes multiple times.
99+
///
100+
/// The trait lifetime is the lifetime of the CRI Attribute. It is required so the visitor object
101+
/// (the implementer) can declare that it will outlive the Attribute, allowing it to keep
102+
/// references on attribute data.
103+
///
104+
/// # Examples
105+
///
106+
/// This visitor implementation will count the number of values in the attribute, and display
107+
/// extension requests.
108+
///
109+
/// ```rust
110+
/// use asn1_rs::Any;
111+
/// use x509_parser::prelude::*;
112+
/// use x509_parser::visitor::X509CriAttributeVisitor;
113+
///
114+
/// #[derive(Default)]
115+
/// struct CRIAttributeVisitor {
116+
/// num_extensions: usize,
117+
/// }
118+
///
119+
/// impl X509CriAttributeVisitor<'_> for CRIAttributeVisitor {
120+
/// fn visit_raw_value(&mut self, _value: Any<'_>) {
121+
/// self.num_extensions += 1;
122+
/// }
123+
///
124+
/// fn visit_attribute_extension_request(&mut self, extension_request: &ExtensionRequest<'_>) {
125+
/// eprintln!("{extension_request:?}");
126+
/// }
127+
/// }
128+
/// ```
129+
#[allow(unused_variables)]
130+
pub trait X509CriAttributeVisitor<'a> {
131+
/// Run the provided visitor (`self`) over the [`X509CriAttribute`] object
132+
fn walk(&mut self, attribute: &'a X509CriAttribute)
133+
where
134+
Self: Sized,
135+
{
136+
attribute.walk_lft(self);
137+
}
138+
139+
/// Invoked for the "oid" field of the Certification Request Info Attribute
140+
fn visit_oid(&mut self, oid: &'a Oid) {}
141+
142+
/// Invoked for the raw input (unparsed) of the Certification Request Info Attribute
143+
///
144+
/// The raw value contains a SET (without header) of ASN.1 values
145+
///
146+
/// See also [X509CriAttributeVisitor::visit_raw_value] (called for each value from the raw input).
147+
fn visit_raw_input(&mut self, input: &'a Input) {}
148+
149+
/// Invoked for each raw value (unparsed) of the Certification Request Info Attribute
150+
///
151+
/// Note that if a particular value could not be parsed, this method will not be called.
152+
/// To iterate on the raw input of the attribute, use [X509CriAttributeVisitor::visit_raw_input].
153+
///
154+
/// Note: this method may be redundant with any other attribute visitor method
155+
fn visit_raw_value(&mut self, value: Any<'a>) {}
156+
157+
/// Invoked for each `ChallengePassword` value of the Certification Request Info Attribute
158+
fn visit_attribute_challenge_password(&mut self, challenge_password: &'a ChallengePassword) {}
159+
160+
/// Invoked for each `ExtensionRequest` value of the Certification Request Info Attribute
161+
fn visit_attribute_extension_request(&mut self, extension_request: &'a ExtensionRequest<'a>) {}
162+
163+
// NOTE: to be called when UnsupportedAttribute contains some data
164+
// /// Invoked for each Unsupported attribute of the Certification Request Info Attribute
165+
// fn visit_attribute_unsupported_attribute(&mut self, _unsupported_attribute: &UnsupportedAttribute) {}
166+
}
167+
168+
impl X509CriAttribute<'_> {
169+
/// Run the provided [`X509CriAttributeVisitor`] over the X.509 Certification Request Info Attribute (`self`)
170+
pub fn walk<'a, V: X509CriAttributeVisitor<'a>>(&'a self, visitor: &mut V) {
171+
let v = visitor;
172+
v.visit_oid(&self.oid);
173+
v.visit_raw_input(&self.value);
174+
for (_, value) in self.iter_raw_values().flatten() {
175+
v.visit_raw_value(value);
176+
}
177+
for parsed_attribute in self.parsed_attributes() {
178+
match parsed_attribute {
179+
ParsedCriAttribute::ChallengePassword(challenge_password) => {
180+
v.visit_attribute_challenge_password(challenge_password)
181+
}
182+
ParsedCriAttribute::ExtensionRequest(extension_request) => {
183+
v.visit_attribute_extension_request(extension_request)
184+
}
185+
ParsedCriAttribute::UnsupportedAttribute => (),
186+
}
187+
}
188+
}
189+
190+
/// Run the provided [`X509CriAttributeVisitor`] over the X.509 Certification Request Info Attribute (`self`)
191+
pub fn walk_lft<'a, V: X509CriAttributeVisitor<'a>>(&'a self, visitor: &mut V) {
192+
let v = visitor;
193+
v.visit_oid(&self.oid);
194+
v.visit_raw_input(&self.value);
195+
for (_, value) in self.iter_raw_values().flatten() {
196+
v.visit_raw_value(value);
197+
}
198+
for parsed_attribute in self.parsed_attributes() {
199+
match parsed_attribute {
200+
ParsedCriAttribute::ChallengePassword(challenge_password) => {
201+
v.visit_attribute_challenge_password(challenge_password)
202+
}
203+
ParsedCriAttribute::ExtensionRequest(extension_request) => {
204+
v.visit_attribute_extension_request(extension_request)
205+
}
206+
ParsedCriAttribute::UnsupportedAttribute => (),
207+
}
208+
}
209+
}
210+
}
211+
212+
#[cfg(test)]
213+
mod tests {
214+
use asn1_rs::{DerParser, Input};
215+
216+
use crate::{pem::Pem, prelude::X509Extension};
217+
218+
use super::*;
219+
220+
const CSR_TEST: &str = "assets/test.csr";
221+
222+
#[test]
223+
fn csr_visitors() {
224+
#[derive(Default)]
225+
struct CRIAttributeVisitor {
226+
num_extensions: usize,
227+
}
228+
229+
impl X509CriAttributeVisitor<'_> for CRIAttributeVisitor {
230+
fn visit_attribute_extension_request(&mut self, extension_request: &ExtensionRequest) {
231+
eprintln!("{extension_request:?}");
232+
233+
self.num_extensions += 1;
234+
}
235+
}
236+
237+
#[derive(Default)]
238+
struct CRIVisitor {
239+
num_attributes: usize,
240+
num_extensions: usize,
241+
}
242+
243+
impl X509CertificationRequestInfoVisitor<'_> for CRIVisitor {
244+
fn visit_attribute(&mut self, attribute: &X509CriAttribute) {
245+
let mut v = CRIAttributeVisitor::default();
246+
v.walk(attribute);
247+
248+
self.num_attributes += 1;
249+
self.num_extensions += v.num_extensions;
250+
}
251+
}
252+
253+
let data = std::fs::read(CSR_TEST).expect("Could not read CSR file");
254+
255+
let pem_iter = Pem::iter_from_buffer(&data);
256+
let mut v = CRIVisitor::default();
257+
258+
for entry in pem_iter {
259+
let entry = entry.expect("error in PEM data");
260+
let (_, csr) = X509CertificationRequest::parse_der(Input::from(&entry.contents))
261+
.expect("Parsing CSR failed");
262+
263+
v.walk(&csr.certification_request_info);
264+
}
265+
266+
assert_eq!(v.num_attributes, 1);
267+
assert_eq!(v.num_extensions, 1);
268+
}
269+
270+
/// This test checks the possibility to define a visitor storing references to parsed data
271+
#[test]
272+
fn csr_visitor_zero_copy() {
273+
#[derive(Default)]
274+
struct CRIAttributeVisitor<'a> {
275+
extensions: Vec<&'a X509Extension<'a>>,
276+
277+
raw_values: Vec<Any<'a>>,
278+
}
279+
280+
impl<'v, 'a> X509CriAttributeVisitor<'a> for CRIAttributeVisitor<'v>
281+
where
282+
'a: 'v,
283+
{
284+
fn visit_attribute_extension_request(
285+
&mut self,
286+
extension_request: &'a ExtensionRequest,
287+
) {
288+
// eprintln!("{extension_request:?}");
289+
290+
for ext in &extension_request.extensions {
291+
self.extensions.push(ext);
292+
}
293+
}
294+
295+
fn visit_raw_value(&mut self, value: Any<'a>) {
296+
self.raw_values.push(value);
297+
}
298+
}
299+
300+
#[derive(Default)]
301+
struct CRIVisitor {
302+
num_attributes: usize,
303+
num_raw_values: usize,
304+
}
305+
306+
impl X509CertificationRequestInfoVisitor<'_> for CRIVisitor {
307+
fn visit_attribute(&mut self, attribute: &X509CriAttribute) {
308+
let mut v = CRIAttributeVisitor::default();
309+
v.walk(attribute);
310+
311+
self.num_attributes += 1;
312+
self.num_raw_values += v.raw_values.len();
313+
314+
for &ext in &v.extensions {
315+
// eprintln!("{ext:?}");
316+
let _ = ext;
317+
}
318+
for raw_value in &v.raw_values {
319+
// eprintln!("{raw_value:?}");
320+
let _ = raw_value;
321+
}
322+
}
323+
}
324+
325+
let data = std::fs::read(CSR_TEST).expect("Could not read CSR file");
326+
327+
let pem_iter = Pem::iter_from_buffer(&data);
328+
let mut v = CRIVisitor::default();
329+
330+
for entry in pem_iter {
331+
let entry = entry.expect("error in PEM data");
332+
let (_, csr) = X509CertificationRequest::parse_der(Input::from(&entry.contents))
333+
.expect("Parsing CSR failed");
334+
335+
v.walk(&csr.certification_request_info);
336+
}
337+
338+
assert_eq!(v.num_attributes, 1);
339+
assert_eq!(v.num_raw_values, 1);
340+
}
341+
}

src/visitor/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
//! Visitor patterns for X.509 objects
2+
13
mod certificate_visitor;
4+
mod cri_visitor;
25
mod crl_visitor;
36

47
pub use certificate_visitor::*;
8+
pub use cri_visitor::*;
59
pub use crl_visitor::*;

0 commit comments

Comments
 (0)