1use crate::error::{Error, ErrorCode, Located, Result, gate};
9use crate::extension::{self, Extension};
10use structured_asn1::memory::{Memory, OwnedVec};
11use structured_asn1::{Oid, Parser, Tag, universal};
12
13#[derive(Debug)]
15pub struct PolicyInformation<'a> {
16 policy: Oid<'a>,
17 qualifiers: OwnedVec<PolicyQualifier<'a>>,
18 at: u64,
19}
20
21impl<'a> PolicyInformation<'a> {
22 #[must_use]
24 pub fn policy(&self) -> Oid<'a> {
25 self.policy
26 }
27
28 #[must_use]
30 pub fn qualifiers(&self) -> &[PolicyQualifier<'a>] {
31 &self.qualifiers
32 }
33}
34
35#[derive(Debug, Clone, Copy)]
37pub struct PolicyQualifier<'a> {
38 kind: Oid<'a>,
39 value: &'a [u8],
40 at: u64,
44 outer: u32,
46}
47
48impl<'a> PolicyQualifier<'a> {
49 #[must_use]
51 pub fn kind(self) -> Oid<'a> {
52 self.kind
53 }
54
55 #[must_use]
58 pub fn value(self) -> &'a [u8] {
59 self.value
60 }
61}
62
63#[derive(Debug)]
65pub struct CertificatePolicies<'a> {
66 policies: OwnedVec<PolicyInformation<'a>>,
67}
68
69impl<'a> CertificatePolicies<'a> {
70 #[must_use]
72 pub fn policies(&self) -> &[PolicyInformation<'a>] {
73 &self.policies
74 }
75
76 pub fn has(&self, policy: Oid<'_>, execution: &Memory) -> Result<bool> {
82 for information in &self.policies {
83 let work = 1 + information
84 .policy
85 .as_bytes()
86 .len()
87 .max(policy.as_bytes().len()) as u64;
88 execution.charge_work(work).map_err(|reason| {
89 Error::resource(reason, "extensions.certificatePolicies", information.at)
90 })?;
91 if information.policy == policy {
92 return Ok(true);
93 }
94 }
95 Ok(false)
96 }
97
98 pub fn read(extension: &Extension<'a>, execution: &Memory) -> Result<Self> {
100 const FIELD: &str = "extensions.certificatePolicies";
101 let fail = |code| Error::new(code, FIELD, extension.span().start);
102
103 let mut inside = extension::sequence(extension, FIELD, execution)?;
104 let mut policies = inside.memory().vector();
105 let mut identifiers = inside.memory().map::<Oid<'a>, ()>();
106
107 while let Some(entry) = inside.next_element().field(FIELD)? {
108 let at = |code| Error::new(code, FIELD, entry.span().start);
109 if entry.tag() != Tag::constructed(universal::SEQUENCE) {
110 return Err(at(ErrorCode::Encoding(
111 structured_asn1::ErrorCode::UnexpectedTag,
112 )));
113 }
114
115 let mut information = inside.enter(&entry).field(FIELD)?;
116 let policy = information.read_oid().field(FIELD)?;
117 let mut qualifiers = inside.memory().vector();
118
119 if let Some(list) = information.next_element().field(FIELD)? {
120 if list.tag() != Tag::constructed(universal::SEQUENCE) {
121 return Err(at(ErrorCode::Encoding(
122 structured_asn1::ErrorCode::UnexpectedTag,
123 )));
124 }
125 let mut each = information.enter(&list).field(FIELD)?;
126 while let Some(qualifier) = each.next_element().field(FIELD)? {
127 if qualifier.tag() != Tag::constructed(universal::SEQUENCE) {
128 return Err(at(ErrorCode::Encoding(
129 structured_asn1::ErrorCode::UnexpectedTag,
130 )));
131 }
132 let mut pair = each.enter(&qualifier).field(FIELD)?;
133 let kind = pair.read_oid().field(FIELD)?;
134 let value = pair
135 .next_element()
136 .field(FIELD)?
137 .ok_or_else(|| at(ErrorCode::MissingField))?;
138 if !pair.is_empty() {
139 return Err(at(ErrorCode::TrailingData));
140 }
141 crate::name::walk_der(&pair, &value, FIELD, crate::name::Strings::Checked)?;
149 qualifiers
150 .try_push(PolicyQualifier {
151 kind,
152 value: value.as_bytes(),
153 at: value.span().start,
154 outer: each.depth(),
155 })
156 .map_err(|reason| Error::resource(reason, FIELD, qualifier.span().start))?;
157 }
158 if qualifiers.is_empty() {
159 return Err(at(ErrorCode::EmptySequence));
160 }
161 }
162
163 if !information.is_empty() {
164 return Err(at(ErrorCode::TrailingData));
165 }
166
167 if identifiers
171 .try_insert(policy, ())
172 .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
173 .is_some()
174 {
175 return Err(at(ErrorCode::MalformedValue));
176 }
177
178 policies
179 .try_push(PolicyInformation {
180 policy,
181 qualifiers,
182 at: entry.span().start,
183 })
184 .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
185 }
186
187 if policies.is_empty() {
188 return Err(fail(ErrorCode::EmptySequence));
189 }
190 Ok(Self { policies })
191 }
192}
193
194#[derive(Debug, Clone, Copy)]
196pub struct PolicyMapping<'a> {
197 issuer_domain: Oid<'a>,
198 subject_domain: Oid<'a>,
199}
200
201impl<'a> PolicyMapping<'a> {
202 #[must_use]
204 pub fn issuer_domain(self) -> Oid<'a> {
205 self.issuer_domain
206 }
207
208 #[must_use]
210 pub fn subject_domain(self) -> Oid<'a> {
211 self.subject_domain
212 }
213}
214
215#[derive(Debug)]
217pub struct PolicyMappings<'a> {
218 mappings: OwnedVec<PolicyMapping<'a>>,
219}
220
221impl<'a> PolicyMappings<'a> {
222 #[must_use]
224 pub fn mappings(&self) -> &[PolicyMapping<'a>] {
225 &self.mappings
226 }
227
228 pub fn read(extension: &Extension<'a>, execution: &Memory) -> Result<Self> {
230 const FIELD: &str = "extensions.policyMappings";
231 let fail = |code| Error::new(code, FIELD, extension.span().start);
232
233 let mut inside = extension::sequence(extension, FIELD, execution)?;
234 let mut mappings = inside.memory().vector();
235 let mut seen = inside.memory().map::<(Oid<'a>, Oid<'a>), ()>();
236
237 while let Some(entry) = inside.next_element().field(FIELD)? {
238 let at = |code| Error::new(code, FIELD, entry.span().start);
239 if entry.tag() != Tag::constructed(universal::SEQUENCE) {
240 return Err(at(ErrorCode::Encoding(
241 structured_asn1::ErrorCode::UnexpectedTag,
242 )));
243 }
244
245 let mut pair = inside.enter(&entry).field(FIELD)?;
246 let issuer_domain = pair.read_oid().field(FIELD)?;
247 let subject_domain = pair.read_oid().field(FIELD)?;
248 if !pair.is_empty() {
249 return Err(at(ErrorCode::TrailingData));
250 }
251
252 if issuer_domain == crate::oid::ANY_POLICY || subject_domain == crate::oid::ANY_POLICY {
255 return Err(at(ErrorCode::MalformedValue));
256 }
257
258 if seen
266 .try_insert((issuer_domain, subject_domain), ())
267 .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
268 .is_none()
269 {
270 mappings
271 .try_push(PolicyMapping {
272 issuer_domain,
273 subject_domain,
274 })
275 .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
276 }
277 }
278
279 if mappings.is_empty() {
280 return Err(fail(ErrorCode::EmptySequence));
281 }
282 Ok(Self { mappings })
283 }
284}
285
286impl CertificatePolicies<'_> {
287 pub(crate) fn understood(&self, parser: &Parser, execution: &Memory) -> Result<bool> {
295 for qualifier in self.policies.iter().flat_map(PolicyInformation::qualifiers) {
296 execution
297 .charge_work(1)
298 .map_err(|reason| Error::resource(reason, QUALIFIER_FIELD, qualifier.at))?;
299 if !retained_qualifier_understood(parser, *qualifier, execution)? {
300 return Ok(false);
301 }
302 }
303 Ok(true)
304 }
305}
306
307const QUALIFIER_FIELD: &str = "extensions.certificatePolicies";
309
310fn retained_qualifier_understood(
311 parser: &Parser,
312 qualifier: PolicyQualifier<'_>,
313 execution: &Memory,
314) -> Result<bool> {
315 let Some(mut reader) = gate(
319 parser
320 .read_nested_in(
321 qualifier.value,
322 qualifier.at,
323 structured_asn1::Encoding::Der,
324 qualifier.outer,
325 execution,
326 )
327 .field(QUALIFIER_FIELD),
328 )?
329 else {
330 return Ok(false);
331 };
332 let Some(Some(value)) = gate(reader.next_element().field(QUALIFIER_FIELD))? else {
333 return Ok(false);
334 };
335 if !reader.is_empty() {
336 return Ok(false);
337 }
338
339 qualifier_understood(&reader, qualifier.kind, &value)
340}
341
342#[derive(Clone, Copy)]
343enum QualifierKind {
344 Cps,
345 Notice,
346}
347
348pub fn qualifier_understood<'a>(
360 parent: &structured_asn1::Reader<'a>,
361 kind: Oid<'_>,
362 value: &structured_asn1::Element<'a>,
363) -> Result<bool> {
364 let work = (kind
365 .as_bytes()
366 .len()
367 .max(crate::oid::CPS_QUALIFIER.as_bytes().len()) as u64)
368 .checked_mul(2)
369 .and_then(|cost| cost.checked_add(1))
370 .ok_or_else(|| {
371 Error::resource(
372 structured_asn1::memory::MemoryError::WorkExceeded,
373 QUALIFIER_FIELD,
374 value.span().start,
375 )
376 })?;
377 qualifier_charge(parent, value, work)?;
378 let kind = if kind == crate::oid::CPS_QUALIFIER {
379 QualifierKind::Cps
380 } else if kind == crate::oid::USER_NOTICE_QUALIFIER {
381 QualifierKind::Notice
382 } else {
383 return Ok(false);
384 };
385 if parent.encoding().is_der() {
386 return qualifier_schema(parent, kind, value);
387 }
388 let Some(mut reader) = gate(
389 parent
390 .reread(value, structured_asn1::Encoding::Der)
391 .field(QUALIFIER_FIELD),
392 )?
393 else {
394 return Ok(false);
395 };
396 let Some(Some(value)) = gate(reader.next_element().field(QUALIFIER_FIELD))? else {
397 return Ok(false);
398 };
399 if !reader.is_empty() {
400 return Ok(false);
401 }
402 qualifier_schema(&reader, kind, &value)
403}
404
405fn qualifier_charge(
406 reader: &structured_asn1::Reader<'_>,
407 value: &structured_asn1::Element<'_>,
408 work: u64,
409) -> Result<()> {
410 reader.charge(work).map_err(|error| {
411 Error::new(
412 ErrorCode::Encoding(error.code()),
413 QUALIFIER_FIELD,
414 value.span().start,
415 )
416 })
417}
418
419fn qualifier_schema(
420 reader: &structured_asn1::Reader<'_>,
421 kind: QualifierKind,
422 value: &structured_asn1::Element<'_>,
423) -> Result<bool> {
424 Ok(match kind {
425 QualifierKind::Cps => {
426 if value.tag() != Tag::primitive(universal::IA5_STRING) {
432 return Ok(false);
433 }
434 let work = (value.contents().len() as u64)
437 .checked_mul(12)
438 .and_then(|cost| cost.checked_add(1))
439 .ok_or_else(|| {
440 Error::resource(
441 structured_asn1::memory::MemoryError::WorkExceeded,
442 QUALIFIER_FIELD,
443 value.span().start,
444 )
445 })?;
446 qualifier_charge(reader, value, work)?;
447 crate::name::is_ia5(value.contents())
448 && core::str::from_utf8(value.contents()).is_ok_and(crate::general_name::is_cps_uri)
449 }
450 QualifierKind::Notice => user_notice_understood(reader, value)?,
451 })
452}
453
454fn user_notice_understood(
459 parent: &structured_asn1::Reader<'_>,
460 value: &structured_asn1::Element<'_>,
461) -> Result<bool> {
462 if value.tag() != Tag::constructed(universal::SEQUENCE) {
463 return Ok(false);
464 }
465 let Some(mut inside) = gate(parent.enter(value).field(QUALIFIER_FIELD))? else {
466 return Ok(false);
467 };
468 let Some(mut next) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
469 return Ok(false);
470 };
471 if let Some(reference) = next
472 && reference.tag() == Tag::constructed(universal::SEQUENCE)
473 {
474 if !notice_reference_understood(&inside, &reference)? {
475 return Ok(false);
476 }
477 let Some(following) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
478 return Ok(false);
479 };
480 next = following;
481 }
482 if let Some(text) = next
483 && !is_display_text(&inside, &text)?
484 {
485 return Ok(false);
486 }
487 Ok(inside.is_empty())
488}
489
490fn notice_reference_understood(
493 parent: &structured_asn1::Reader<'_>,
494 reference: &structured_asn1::Element<'_>,
495) -> Result<bool> {
496 let Some(mut inside) = gate(parent.enter(reference).field(QUALIFIER_FIELD))? else {
497 return Ok(false);
498 };
499 let Some(Some(organization)) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
500 return Ok(false);
501 };
502 if !is_display_text(&inside, &organization)? {
503 return Ok(false);
504 }
505 let Some(Some(numbers)) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
506 return Ok(false);
507 };
508 if numbers.tag() != Tag::constructed(universal::SEQUENCE) || !inside.is_empty() {
509 return Ok(false);
510 }
511 let Some(mut each) = gate(inside.enter(&numbers).field(QUALIFIER_FIELD))? else {
512 return Ok(false);
513 };
514 loop {
515 match gate(each.next_element().field(QUALIFIER_FIELD))? {
516 Some(Some(number)) => {
517 if number.tag() != Tag::primitive(universal::INTEGER)
521 || gate(each.integer(&number).field(QUALIFIER_FIELD))?.is_none()
522 {
523 return Ok(false);
524 }
525 }
526 Some(None) => return Ok(true),
530 None => return Ok(false),
531 }
532 }
533}
534
535fn is_display_text(
540 reader: &structured_asn1::Reader<'_>,
541 element: &structured_asn1::Element<'_>,
542) -> Result<bool> {
543 let tag = element.tag();
544 let contents = element.contents();
545 if contents.len() > 200 * 4 {
550 return Ok(false);
551 }
552 qualifier_charge(reader, element, (contents.len() as u64) * 2 + 1)?;
558 let characters = if tag == Tag::primitive(universal::IA5_STRING) {
559 contents.iter().all(u8::is_ascii).then_some(contents.len())
561 } else if tag == Tag::primitive(universal::UTF8_STRING) {
562 core::str::from_utf8(contents)
563 .ok()
564 .map(|text| text.chars().count())
565 } else if tag == Tag::primitive(26) {
566 contents
569 .iter()
570 .all(|byte| matches!(byte, 0x20..=0x7e))
571 .then_some(contents.len())
572 } else if tag == Tag::primitive(30) {
573 crate::name::wide_char_count(contents, 2)
578 } else {
579 return Ok(false);
580 };
581 Ok(characters.is_some_and(|count| (1..=200).contains(&count)))
582}
583
584#[cfg(test)]
585mod tests;