1use crate::error::{Error, ErrorCode, Located, Result, gate};
9use crate::extension::{self, Extension};
10use structured_asn1::memory::{OwnedVec, Work};
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<W: Work>(&self, policy: Oid<'_>, execution: &W) -> 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<W: Work>(extension: &Extension<'a>, execution: &W) -> 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 work = inside.execution_in(execution.memory());
105 let mut policies = work.vector();
106 let mut identifiers = work.map::<Oid<'a>, ()>();
107
108 while let Some(entry) = inside.next_element().field(FIELD)? {
109 let at = |code| Error::new(code, FIELD, entry.span().start);
110 if entry.tag() != Tag::constructed(universal::SEQUENCE) {
111 return Err(at(ErrorCode::Encoding(
112 structured_asn1::ErrorCode::UnexpectedTag,
113 )));
114 }
115
116 let mut information = inside.enter(&entry).field(FIELD)?;
117 let policy = information.read_oid().field(FIELD)?;
118 let mut qualifiers = work.vector();
119
120 if let Some(list) = information.next_element().field(FIELD)? {
121 if list.tag() != Tag::constructed(universal::SEQUENCE) {
122 return Err(at(ErrorCode::Encoding(
123 structured_asn1::ErrorCode::UnexpectedTag,
124 )));
125 }
126 let mut each = information.enter(&list).field(FIELD)?;
127 while let Some(qualifier) = each.next_element().field(FIELD)? {
128 if qualifier.tag() != Tag::constructed(universal::SEQUENCE) {
129 return Err(at(ErrorCode::Encoding(
130 structured_asn1::ErrorCode::UnexpectedTag,
131 )));
132 }
133 let mut pair = each.enter(&qualifier).field(FIELD)?;
134 let kind = pair.read_oid().field(FIELD)?;
135 let value = pair
136 .next_element()
137 .field(FIELD)?
138 .ok_or_else(|| at(ErrorCode::MissingField))?;
139 if !pair.is_empty() {
140 return Err(at(ErrorCode::TrailingData));
141 }
142 crate::name::walk_der(&pair, &value, FIELD, crate::name::Strings::Checked)?;
150 qualifiers
151 .try_push(PolicyQualifier {
152 kind,
153 value: value.as_bytes(),
154 at: value.span().start,
155 outer: each.depth(),
156 })
157 .map_err(|reason| Error::resource(reason, FIELD, qualifier.span().start))?;
158 }
159 if qualifiers.is_empty() {
160 return Err(at(ErrorCode::EmptySequence));
161 }
162 }
163
164 if !information.is_empty() {
165 return Err(at(ErrorCode::TrailingData));
166 }
167
168 if identifiers
172 .try_insert(policy, ())
173 .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
174 .is_some()
175 {
176 return Err(at(ErrorCode::MalformedValue));
177 }
178
179 policies
180 .try_push(PolicyInformation {
181 policy,
182 qualifiers: qualifiers.into_owned(),
183 at: entry.span().start,
184 })
185 .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
186 }
187
188 if policies.is_empty() {
189 return Err(fail(ErrorCode::EmptySequence));
190 }
191 Ok(Self {
192 policies: policies.into_owned(),
193 })
194 }
195}
196
197#[derive(Debug, Clone, Copy)]
199pub struct PolicyMapping<'a> {
200 issuer_domain: Oid<'a>,
201 subject_domain: Oid<'a>,
202}
203
204impl<'a> PolicyMapping<'a> {
205 #[must_use]
207 pub fn issuer_domain(self) -> Oid<'a> {
208 self.issuer_domain
209 }
210
211 #[must_use]
213 pub fn subject_domain(self) -> Oid<'a> {
214 self.subject_domain
215 }
216}
217
218#[derive(Debug)]
220pub struct PolicyMappings<'a> {
221 mappings: OwnedVec<PolicyMapping<'a>>,
222}
223
224impl<'a> PolicyMappings<'a> {
225 #[must_use]
227 pub fn mappings(&self) -> &[PolicyMapping<'a>] {
228 &self.mappings
229 }
230
231 pub fn read<W: Work>(extension: &Extension<'a>, execution: &W) -> Result<Self> {
233 const FIELD: &str = "extensions.policyMappings";
234 let fail = |code| Error::new(code, FIELD, extension.span().start);
235
236 let mut inside = extension::sequence(extension, FIELD, execution)?;
237 let work = inside.execution_in(execution.memory());
238 let mut mappings = work.vector();
239 let mut seen = work.map::<(Oid<'a>, Oid<'a>), ()>();
240
241 while let Some(entry) = inside.next_element().field(FIELD)? {
242 let at = |code| Error::new(code, FIELD, entry.span().start);
243 if entry.tag() != Tag::constructed(universal::SEQUENCE) {
244 return Err(at(ErrorCode::Encoding(
245 structured_asn1::ErrorCode::UnexpectedTag,
246 )));
247 }
248
249 let mut pair = inside.enter(&entry).field(FIELD)?;
250 let issuer_domain = pair.read_oid().field(FIELD)?;
251 let subject_domain = pair.read_oid().field(FIELD)?;
252 if !pair.is_empty() {
253 return Err(at(ErrorCode::TrailingData));
254 }
255
256 if issuer_domain == crate::oid::ANY_POLICY || subject_domain == crate::oid::ANY_POLICY {
259 return Err(at(ErrorCode::MalformedValue));
260 }
261
262 if seen
270 .try_insert((issuer_domain, subject_domain), ())
271 .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
272 .is_none()
273 {
274 mappings
275 .try_push(PolicyMapping {
276 issuer_domain,
277 subject_domain,
278 })
279 .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
280 }
281 }
282
283 if mappings.is_empty() {
284 return Err(fail(ErrorCode::EmptySequence));
285 }
286 Ok(Self {
287 mappings: mappings.into_owned(),
288 })
289 }
290}
291
292impl CertificatePolicies<'_> {
293 pub(crate) fn understood<W: Work>(&self, parser: &Parser, execution: &W) -> Result<bool> {
301 for qualifier in self.policies.iter().flat_map(PolicyInformation::qualifiers) {
302 execution
303 .charge_work(1)
304 .map_err(|reason| Error::resource(reason, QUALIFIER_FIELD, qualifier.at))?;
305 if !retained_qualifier_understood(parser, *qualifier, execution)? {
306 return Ok(false);
307 }
308 }
309 Ok(true)
310 }
311}
312
313const QUALIFIER_FIELD: &str = "extensions.certificatePolicies";
315
316fn retained_qualifier_understood<W: Work>(
317 parser: &Parser,
318 qualifier: PolicyQualifier<'_>,
319 execution: &W,
320) -> Result<bool> {
321 let Some(mut reader) = gate(
325 execution
326 .read_nested(
327 parser,
328 qualifier.value,
329 qualifier.at,
330 structured_asn1::Encoding::Der,
331 qualifier.outer,
332 )
333 .field(QUALIFIER_FIELD),
334 )?
335 else {
336 return Ok(false);
337 };
338 let Some(Some(value)) = gate(reader.next_element().field(QUALIFIER_FIELD))? else {
339 return Ok(false);
340 };
341 if !reader.is_empty() {
342 return Ok(false);
343 }
344
345 qualifier_understood(&reader, qualifier.kind, &value)
346}
347
348#[derive(Clone, Copy)]
349enum QualifierKind {
350 Cps,
351 Notice,
352}
353
354pub fn qualifier_understood<'a>(
366 parent: &structured_asn1::ScopedReader<'a, '_>,
367 kind: Oid<'_>,
368 value: &structured_asn1::Element<'a>,
369) -> Result<bool> {
370 let work = (kind
371 .as_bytes()
372 .len()
373 .max(crate::oid::CPS_QUALIFIER.as_bytes().len()) as u64)
374 .checked_mul(2)
375 .and_then(|cost| cost.checked_add(1))
376 .ok_or_else(|| {
377 Error::resource(
378 structured_asn1::memory::MemoryError::WorkExceeded,
379 QUALIFIER_FIELD,
380 value.span().start,
381 )
382 })?;
383 qualifier_charge(parent, value, work)?;
384 let kind = if kind == crate::oid::CPS_QUALIFIER {
385 QualifierKind::Cps
386 } else if kind == crate::oid::USER_NOTICE_QUALIFIER {
387 QualifierKind::Notice
388 } else {
389 return Ok(false);
390 };
391 if parent.encoding().is_der() {
392 return qualifier_schema(parent, kind, value);
393 }
394 let Some(mut reader) = gate(
395 parent
396 .reread(value, structured_asn1::Encoding::Der)
397 .field(QUALIFIER_FIELD),
398 )?
399 else {
400 return Ok(false);
401 };
402 let Some(Some(value)) = gate(reader.next_element().field(QUALIFIER_FIELD))? else {
403 return Ok(false);
404 };
405 if !reader.is_empty() {
406 return Ok(false);
407 }
408 qualifier_schema(&reader, kind, &value)
409}
410
411fn qualifier_charge(
412 reader: &structured_asn1::ScopedReader<'_, '_>,
413 value: &structured_asn1::Element<'_>,
414 work: u64,
415) -> Result<()> {
416 reader.charge(work).map_err(|error| {
417 Error::new(
418 ErrorCode::Encoding(error.code()),
419 QUALIFIER_FIELD,
420 value.span().start,
421 )
422 })
423}
424
425fn qualifier_schema(
426 reader: &structured_asn1::ScopedReader<'_, '_>,
427 kind: QualifierKind,
428 value: &structured_asn1::Element<'_>,
429) -> Result<bool> {
430 Ok(match kind {
431 QualifierKind::Cps => {
432 if value.tag() != Tag::primitive(universal::IA5_STRING) {
438 return Ok(false);
439 }
440 let work = (value.contents().len() as u64)
443 .checked_mul(12)
444 .and_then(|cost| cost.checked_add(1))
445 .ok_or_else(|| {
446 Error::resource(
447 structured_asn1::memory::MemoryError::WorkExceeded,
448 QUALIFIER_FIELD,
449 value.span().start,
450 )
451 })?;
452 qualifier_charge(reader, value, work)?;
453 crate::name::is_ia5(value.contents())
454 && core::str::from_utf8(value.contents()).is_ok_and(crate::general_name::is_cps_uri)
455 }
456 QualifierKind::Notice => user_notice_understood(reader, value)?,
457 })
458}
459
460fn user_notice_understood(
465 parent: &structured_asn1::ScopedReader<'_, '_>,
466 value: &structured_asn1::Element<'_>,
467) -> Result<bool> {
468 if value.tag() != Tag::constructed(universal::SEQUENCE) {
469 return Ok(false);
470 }
471 let Some(mut inside) = gate(parent.enter(value).field(QUALIFIER_FIELD))? else {
472 return Ok(false);
473 };
474 let Some(mut next) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
475 return Ok(false);
476 };
477 if let Some(reference) = next
478 && reference.tag() == Tag::constructed(universal::SEQUENCE)
479 {
480 if !notice_reference_understood(&inside, &reference)? {
481 return Ok(false);
482 }
483 let Some(following) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
484 return Ok(false);
485 };
486 next = following;
487 }
488 if let Some(text) = next
489 && !is_display_text(&inside, &text)?
490 {
491 return Ok(false);
492 }
493 Ok(inside.is_empty())
494}
495
496fn notice_reference_understood(
499 parent: &structured_asn1::ScopedReader<'_, '_>,
500 reference: &structured_asn1::Element<'_>,
501) -> Result<bool> {
502 let Some(mut inside) = gate(parent.enter(reference).field(QUALIFIER_FIELD))? else {
503 return Ok(false);
504 };
505 let Some(Some(organization)) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
506 return Ok(false);
507 };
508 if !is_display_text(&inside, &organization)? {
509 return Ok(false);
510 }
511 let Some(Some(numbers)) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
512 return Ok(false);
513 };
514 if numbers.tag() != Tag::constructed(universal::SEQUENCE) || !inside.is_empty() {
515 return Ok(false);
516 }
517 let Some(mut each) = gate(inside.enter(&numbers).field(QUALIFIER_FIELD))? else {
518 return Ok(false);
519 };
520 loop {
521 match gate(each.next_element().field(QUALIFIER_FIELD))? {
522 Some(Some(number)) => {
523 if number.tag() != Tag::primitive(universal::INTEGER)
527 || gate(each.integer(&number).field(QUALIFIER_FIELD))?.is_none()
528 {
529 return Ok(false);
530 }
531 }
532 Some(None) => return Ok(true),
536 None => return Ok(false),
537 }
538 }
539}
540
541fn is_display_text(
546 reader: &structured_asn1::ScopedReader<'_, '_>,
547 element: &structured_asn1::Element<'_>,
548) -> Result<bool> {
549 let tag = element.tag();
550 let contents = element.contents();
551 if contents.len() > 200 * 4 {
556 return Ok(false);
557 }
558 qualifier_charge(reader, element, (contents.len() as u64) * 2 + 1)?;
564 let characters = if tag == Tag::primitive(universal::IA5_STRING) {
565 contents.iter().all(u8::is_ascii).then_some(contents.len())
567 } else if tag == Tag::primitive(universal::UTF8_STRING) {
568 core::str::from_utf8(contents)
569 .ok()
570 .map(|text| text.chars().count())
571 } else if tag == Tag::primitive(26) {
572 contents
575 .iter()
576 .all(|byte| matches!(byte, 0x20..=0x7e))
577 .then_some(contents.len())
578 } else if tag == Tag::primitive(30) {
579 crate::name::wide_char_count(contents, 2)
584 } else {
585 return Ok(false);
586 };
587 Ok(characters.is_some_and(|count| (1..=200).contains(&count)))
588}
589
590#[cfg(test)]
591mod tests;