1use crate::{Error, Result};
5use structured_asn1::memory::{OwnedVec, Work};
6use structured_asn1::{Element, Oid, Reader, Span, Tag, universal};
7use structured_cms::{ErrorCode, Octets, attribute::Attribute, oid, signer::SignerInfo};
8use structured_pkix::algorithm::{self, AlgorithmIdentifier};
9
10pub(crate) mod legacy;
11pub mod values;
12
13#[derive(Debug)]
16pub struct Fault {
17 pub attribute: Oid<'static>,
19 pub field: &'static str,
21 pub cause: Error,
23}
24
25#[derive(Debug, Clone, Copy)]
28pub struct OpenValue<'a> {
29 pub role: OpenRole,
31 pub kind: Option<Oid<'a>>,
33 pub enclosure: Element<'a>,
35 pub value: Option<crate::source::Value<'a>>,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum OpenRole {
43 Claimed,
45 AttributeCertificate,
47 OtherAttributeCertificate,
49 SignedAssertion,
51 CommitmentQualifier,
53 PolicyQualifier,
55}
56
57pub struct Policy<'a> {
61 pub identifier: Oid<'a>,
63 pub algorithm: AlgorithmIdentifier<'a>,
65 pub hash: Octets<'a>,
67 pub hash_unknown: bool,
69}
70
71pub struct Store<'a> {
73 pub specification: Element<'a>,
75 pub document: crate::source::Value<'a>,
77}
78
79pub struct Properties<'a> {
82 faults: OwnedVec<Fault>,
83 open: OwnedVec<OpenValue<'a>>,
84 issuer_hints: OwnedVec<Span>,
85 policy: Option<Policy<'a>>,
86 store: Option<Store<'a>>,
87 values: Option<values::Values<'a>>,
88}
89
90impl<'a> Properties<'a> {
91 pub fn read(signer: &SignerInfo<'a>, parent: &Reader<'a>) -> Result<Self> {
100 signer.require_session(parent, "cades.properties")?;
101 let work = parent.execution();
102 let mut policy = None;
103 let mut store = None;
104 let mut values = None;
105 let mut faults = work.vector();
106 let mut open = work.vector();
107 let mut issuer_hints = work.vector();
108 for set in [signer.signed_attributes(), signer.unsigned_attributes()]
109 .into_iter()
110 .flatten()
111 {
112 for attribute in set.items() {
113 parent
116 .charge(11 * (1 + attribute.kind().as_bytes().len().min(12) as u64))
117 .map_err(|cause| Error::encoding(cause, "cades.properties"))?;
118 let Some((kind, field)) = known(attribute.kind()) else {
119 legacy::inspect(
120 attribute,
121 parent,
122 &mut faults,
123 &mut issuer_hints,
124 &mut values,
125 &work,
126 )?;
127 continue;
128 };
129 let reading = read_value(
130 attribute,
131 kind,
132 field,
133 parent,
134 &mut open,
135 &mut policy,
136 &mut store,
137 );
138 if let Err(cause) = reading {
139 if cause.is_terminal() {
140 return Err(cause);
141 }
142 let at = cause.offset();
143 faults
144 .try_push(Fault {
145 attribute: kind,
146 field,
147 cause,
148 })
149 .map_err(|cause| Error::resource(cause, field, at))?;
150 }
151 }
152 }
153 Ok(Self {
154 faults: faults.into_owned(),
155 open: open.into_owned(),
156 issuer_hints: issuer_hints.into_owned(),
157 policy,
158 store,
159 values: values.map(values::Values::into_owned),
160 })
161 }
162 #[must_use]
164 pub fn faults(&self) -> &[Fault] {
165 &self.faults
166 }
167 pub(crate) fn issuer_hints(&self) -> &[Span] {
168 &self.issuer_hints
169 }
170 #[must_use]
172 pub fn open_values(&self) -> &[OpenValue<'a>] {
173 &self.open
174 }
175 #[must_use]
177 pub fn policy(&self) -> Option<&Policy<'a>> {
178 self.policy.as_ref()
179 }
180 #[must_use]
182 pub fn store(&self) -> Option<&Store<'a>> {
183 self.store.as_ref()
184 }
185 pub fn legacy_values(&self) -> Option<&values::Values<'a>> {
188 self.values.as_ref()
189 }
190
191 pub(crate) fn has_legacy_values(signer: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
192 for set in [signer.signed_attributes(), signer.unsigned_attributes()]
193 .into_iter()
194 .flatten()
195 {
196 for attribute in set.items() {
197 parent
198 .charge(2 * (1 + attribute.kind().as_bytes().len().min(11) as u64))
199 .map_err(|cause| Error::encoding(cause, "cades.legacy"))?;
200 if attribute.kind() == legacy::LEGACY[2] || attribute.kind() == legacy::LEGACY[3] {
201 return Ok(true);
202 }
203 }
204 }
205 Ok(false)
206 }
207}
208
209pub fn defines(
213 kind: Oid<'_>,
214 work: &impl Work,
215) -> core::result::Result<bool, structured_asn1::memory::MemoryError> {
216 work.charge_work(11 * (1 + kind.as_bytes().len().min(12) as u64))?;
217 if known(kind).is_some() {
218 return Ok(true);
219 }
220 for expected in legacy::LEGACY.into_iter().chain([
221 oid::CONTENT_TYPE,
222 oid::MESSAGE_DIGEST,
223 oid::SIGNING_TIME,
224 oid::SIGNING_CERTIFICATE,
225 oid::SIGNING_CERTIFICATE_V2,
226 oid::CONTENT_TIMESTAMP,
227 oid::ARCHIVE_TIMESTAMP_V3,
228 oid::ATS_HASH_INDEX_V3,
229 oid::CLAIMED_SAML_ASSERTION,
230 ]) {
231 work.charge_work(1 + kind.as_bytes().len().min(expected.as_bytes().len()) as u64)?;
232 if kind == expected {
233 return Ok(true);
234 }
235 }
236 work.charge_work(1 + kind.as_bytes().len().min(11) as u64)?;
237 Ok(crate::preservation::Kind::from_oid(kind).is_some())
238}
239
240fn known(kind: Oid<'_>) -> Option<(Oid<'static>, &'static str)> {
241 for (expected, field) in [
242 (oid::COMMITMENT_TYPE_INDICATION, "commitmentTypeIndication"),
243 (oid::MIME_TYPE, "mimeType"),
244 (oid::CONTENT_HINTS, "contentHints"),
245 (oid::CONTENT_IDENTIFIER, "contentIdentifier"),
246 (oid::CONTENT_REFERENCE, "contentReference"),
247 (oid::SIGNER_LOCATION, "signerLocation"),
248 (oid::SIGNER_ATTRIBUTES_V2, "signerAttributesV2"),
249 (
250 oid::SIGNATURE_POLICY_IDENTIFIER,
251 "signaturePolicyIdentifier",
252 ),
253 (oid::SIGNATURE_POLICY_STORE, "signaturePolicyStore"),
254 (oid::SIGNATURE_TIMESTAMP, "signatureTimeStamp"),
255 (oid::COUNTERSIGNATURE, "countersignature"),
256 ] {
257 if kind == expected {
258 return Some((expected, field));
259 }
260 }
261 None
262}
263
264fn read_value<'a, W: Work>(
265 attribute: &Attribute<'a>,
266 kind: Oid<'static>,
267 field: &'static str,
268 parent: &Reader<'a>,
269 open: &mut OwnedVec<OpenValue<'a>, W>,
270 policy: &mut Option<Policy<'a>>,
271 store: &mut Option<Store<'a>>,
272) -> Result<()> {
273 if kind == oid::COUNTERSIGNATURE {
274 return if attribute.values().is_empty() {
275 Err(syntax(field, attribute.span().start))
276 } else {
277 Ok(())
278 };
279 }
280 if kind == oid::SIGNATURE_TIMESTAMP {
281 let mut der = parent
287 .reread(attribute.element(), structured_asn1::Encoding::Der)
288 .map_err(|cause| Error::encoding(cause, field))?;
289 let element = der
290 .expect(Tag::constructed(universal::SEQUENCE))
291 .map_err(|cause| Error::encoding(cause, field))?;
292 return structured_pkix::der::conforms(&der, &element, field).map_err(Error::Pkix);
293 }
294 if attribute.values().len() != 1 {
295 return Err(syntax(field, attribute.span().start));
296 }
297 let mut reader = attribute
298 .value_reader_in(0, parent, field)?
299 .ok_or_else(|| syntax(field, attribute.span().start))?;
300 let value = reader
301 .next_element()
302 .map_err(|cause| Error::encoding(cause, field))?
303 .ok_or_else(|| syntax(field, reader.offset()))?;
304 if !reader.is_empty() {
305 return Err(syntax(field, reader.offset()));
306 }
307 if kind == oid::MIME_TYPE {
308 require_tag(&value, Tag::primitive(universal::UTF8_STRING), field)?;
309 structured_pkix::der::conforms(&reader, &value, field).map_err(Error::Pkix)?;
310 let valid =
311 structured_cms::mime::media_type_or_headers(value.contents(), &reader.execution())
312 .map_err(|cause| Error::resource(cause, field, value.span().start))?;
313 return if valid {
314 Ok(())
315 } else {
316 Err(syntax(field, value.span().start))
317 };
318 }
319 if kind == oid::CONTENT_IDENTIFIER {
320 Octets::read(&reader, &value, field)?;
321 return Ok(());
322 }
323 let inside = sequence(&reader, &value, field)?;
324 read_sequence(inside, kind, field, open, policy, store)
325}
326
327fn read_sequence<'a, W: Work>(
328 mut inside: Reader<'a>,
329 kind: Oid<'static>,
330 field: &'static str,
331 open: &mut OwnedVec<OpenValue<'a>, W>,
332 policy: &mut Option<Policy<'a>>,
333 store: &mut Option<Store<'a>>,
334) -> Result<()> {
335 if kind == oid::CONTENT_HINTS {
336 let first = next(&mut inside, field)?;
337 if first.tag() == Tag::primitive(universal::UTF8_STRING) {
338 string(&inside, &first, field)?;
339 inside
340 .read_oid()
341 .map_err(|cause| Error::encoding(cause, field))?;
342 } else {
343 inside
344 .oid(&first)
345 .map_err(|cause| Error::encoding(cause, field))?;
346 }
347 } else if kind == oid::CONTENT_REFERENCE {
348 inside
349 .read_oid()
350 .map_err(|cause| Error::encoding(cause, field))?;
351 for _ in 0..2 {
352 let part = next(&mut inside, field)?;
353 Octets::read(&inside, &part, field)?;
354 }
355 } else if kind == oid::COMMITMENT_TYPE_INDICATION {
356 inside
357 .read_oid()
358 .map_err(|cause| Error::encoding(cause, field))?;
359 if let Some(qualifiers) = inside
360 .next_element()
361 .map_err(|cause| Error::encoding(cause, field))?
362 {
363 let mut values = sequence(&inside, &qualifiers, field)?;
364 if values.is_empty() {
365 return Err(syntax(field, qualifiers.span().start));
366 }
367 while let Some(value) = values
368 .next_element()
369 .map_err(|cause| Error::encoding(cause, field))?
370 {
371 open_pair(&values, &value, OpenRole::CommitmentQualifier, field, open)?;
372 }
373 }
374 } else if kind == oid::SIGNER_LOCATION {
375 location(&mut inside, field)?;
376 } else if kind == oid::SIGNER_ATTRIBUTES_V2 {
377 attributes(&mut inside, field, open)?;
378 } else if kind == oid::SIGNATURE_POLICY_IDENTIFIER {
379 let identifier = inside
380 .read_oid()
381 .map_err(|cause| Error::encoding(cause, field))?;
382 let hash_value = next(&mut inside, field)?;
383 let mut hash_fields = sequence(&inside, &hash_value, field)?;
384 let id = next(&mut hash_fields, field)?;
385 let algorithm = algorithm::read(&hash_fields, &id, field).map_err(Error::Pkix)?;
386 let hash_value = next(&mut hash_fields, field)?;
387 let hash = Octets::read(&hash_fields, &hash_value, field)?;
388 end(&hash_fields, field)?;
389 let mut hash_unknown = true;
390 for piece in hash.pieces() {
391 inside
392 .charge(1 + piece.as_bytes().len() as u64)
393 .map_err(|cause| Error::encoding(cause, field))?;
394 if piece.as_bytes().iter().any(|byte| *byte != 0) {
395 hash_unknown = false;
396 }
397 }
398 if let Some(qualifiers) = inside
399 .next_element()
400 .map_err(|cause| Error::encoding(cause, field))?
401 {
402 let mut values = sequence(&inside, &qualifiers, field)?;
403 if values.is_empty() {
404 return Err(syntax(field, qualifiers.span().start));
405 }
406 while let Some(value) = values
407 .next_element()
408 .map_err(|cause| Error::encoding(cause, field))?
409 {
410 open_pair(&values, &value, OpenRole::PolicyQualifier, field, open)?;
411 }
412 }
413 *policy = Some(Policy {
414 identifier,
415 algorithm,
416 hash,
417 hash_unknown,
418 });
419 } else if kind == oid::SIGNATURE_POLICY_STORE {
420 let specification = next(&mut inside, field)?;
421 spec(&inside, &specification, field)?;
422 let document = next(&mut inside, field)?;
423 if document.tag().number() == Some(universal::OCTET_STRING) {
424 Octets::read(&inside, &document, field)?;
425 } else {
426 uri_value(&inside, &document, field)?;
430 }
431 *store = Some(Store {
432 specification,
433 document: crate::source::Value::of(&inside, document, field),
434 });
435 }
436 end(&inside, field)
437}
438
439fn location(reader: &mut Reader<'_>, field: &'static str) -> Result<()> {
440 let mut previous = None;
443 while let Some(value) = reader
444 .next_element()
445 .map_err(|cause| Error::encoding(cause, field))?
446 {
447 let Some(number) = value.tag().number() else {
448 return Err(syntax(field, value.span().start));
449 };
450 if number > 2
451 || value.tag() != Tag::context(true, number)
452 || previous.is_some_and(|p| number <= p)
453 {
454 return Err(syntax(field, value.span().start));
455 }
456 previous = Some(number);
457 let mut explicit = reader
458 .enter(&value)
459 .map_err(|cause| Error::encoding(cause, field))?;
460 let part = next(&mut explicit, field)?;
461 end(&explicit, field)?;
462 if number < 2 {
463 directory_string(&explicit, &part, field)?;
464 } else {
465 let mut lines = sequence(&explicit, &part, field)?;
466 let mut count = 0;
467 while let Some(line) = lines
468 .next_element()
469 .map_err(|cause| Error::encoding(cause, field))?
470 {
471 count += 1;
472 if count > 6 {
473 return Err(syntax(field, line.span().start));
474 }
475 directory_string(&lines, &line, field)?;
476 }
477 if count == 0 {
478 return Err(syntax(field, part.span().start));
479 }
480 }
481 }
482 if previous.is_none() {
483 return Err(syntax(field, reader.offset()));
484 }
485 Ok(())
486}
487
488fn claimed_value(claimed: &Attribute<'_>, parent: &Reader<'_>, field: &'static str) -> Result<()> {
489 parent
490 .charge(
491 1 + claimed
492 .kind()
493 .as_bytes()
494 .len()
495 .min(oid::CLAIMED_SAML_ASSERTION.as_bytes().len()) as u64,
496 )
497 .map_err(|cause| Error::encoding(cause, field))?;
498 if claimed.kind() != oid::CLAIMED_SAML_ASSERTION {
499 return Ok(());
500 }
501 claimed.single(field)?;
505 let mut reader = claimed
506 .value_reader_in(0, parent, field)?
507 .ok_or_else(|| syntax(field, claimed.span().start))?;
508 let value = next(&mut reader, field)?;
509 Octets::read(&reader, &value, field)?;
510 Ok(())
511}
512
513fn attributes<'a, W: Work>(
514 reader: &mut Reader<'a>,
515 field: &'static str,
516 open: &mut OwnedVec<OpenValue<'a>, W>,
517) -> Result<()> {
518 let mut previous = None;
519 let mut populated = false;
520 while let Some(value) = reader
521 .next_element()
522 .map_err(|cause| Error::encoding(cause, field))?
523 {
524 let Some(number) = value.tag().number() else {
525 return Err(syntax(field, value.span().start));
526 };
527 if number > 2
528 || value.tag() != Tag::context(true, number)
529 || previous.is_some_and(|p| number <= p)
530 {
531 return Err(syntax(field, value.span().start));
532 }
533 previous = Some(number);
534 let mut explicit = reader
535 .enter(&value)
536 .map_err(|cause| Error::encoding(cause, field))?;
537 let part = next(&mut explicit, field)?;
538 end(&explicit, field)?;
539 let mut entries = sequence(&explicit, &part, field)?;
540 if number != 0 && entries.is_empty() {
542 return Err(syntax(field, part.span().start));
543 }
544 while let Some(entry) = entries
545 .next_element()
546 .map_err(|cause| Error::encoding(cause, field))?
547 {
548 populated = true;
549 if number == 0 {
550 let claimed = Attribute::read(&entries, &entry, field)?;
551 claimed_value(&claimed, &entries, field)?;
552 push(
553 open,
554 OpenValue {
555 role: OpenRole::Claimed,
556 kind: Some(claimed.kind()),
557 enclosure: entry,
558 value: Some(crate::source::Value::of(&entries, entry, field)),
559 },
560 field,
561 )?;
562 } else if number == 1 {
563 let choice = entry.tag();
564 if choice != Tag::context(true, 0) && choice != Tag::context(true, 1) {
565 return Err(syntax(field, entry.span().start));
566 }
567 let mut explicit = entries
568 .enter(&entry)
569 .map_err(|cause| Error::encoding(cause, field))?;
570 let certificate = next(&mut explicit, field)?;
571 end(&explicit, field)?;
572 if choice.number() == Some(0) {
573 require_tag(&certificate, Tag::constructed(universal::SEQUENCE), field)?;
574 push(
575 open,
576 OpenValue {
577 role: OpenRole::AttributeCertificate,
578 kind: None,
579 enclosure: certificate,
580 value: Some(crate::source::Value::of(&explicit, certificate, field)),
581 },
582 field,
583 )?;
584 } else {
585 open_pair(
586 &explicit,
587 &certificate,
588 OpenRole::OtherAttributeCertificate,
589 field,
590 open,
591 )?;
592 }
593 } else {
594 open_pair(&entries, &entry, OpenRole::SignedAssertion, field, open)?;
595 }
596 }
597 }
598 if !populated {
603 return Err(syntax(field, reader.offset()));
604 }
605 Ok(())
606}
607
608fn open_pair<'a, W: Work>(
609 parent: &Reader<'a>,
610 element: &Element<'a>,
611 role: OpenRole,
612 field: &'static str,
613 open: &mut OwnedVec<OpenValue<'a>, W>,
614) -> Result<()> {
615 let mut pair = sequence(parent, element, field)?;
616 let kind = pair
617 .read_oid()
618 .map_err(|cause| Error::encoding(cause, field))?;
619 let value = pair
620 .next_element()
621 .map_err(|cause| Error::encoding(cause, field))?;
622 end(&pair, field)?;
623 if role == OpenRole::SignedAssertion {
624 let saml = Oid::from_bytes(&[4, 0, 0x81, 0x95, 0x32, 3, 0]);
625 parent
626 .charge(1 + kind.as_bytes().len().min(saml.as_bytes().len()) as u64)
627 .map_err(|cause| Error::encoding(cause, field))?;
628 if kind == saml
632 && let Some(part) = value
633 {
634 Octets::read(&pair, &part, field)?;
635 }
636 }
637 if role == OpenRole::PolicyQualifier
638 && let Some(part) = value
639 {
640 let uri = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 5, 1]);
644 let notice = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 5, 2]);
645 let specification = Oid::from_bytes(&[4, 0, 0x81, 0x95, 0x32, 2, 1]);
646 parent
647 .charge(3 * (1 + kind.as_bytes().len() as u64))
648 .map_err(|cause| Error::encoding(cause, field))?;
649 if kind == uri || kind == notice || kind == specification {
650 if kind == uri {
651 uri_value(&pair, &part, field)?;
652 } else if kind == specification {
653 spec(&pair, &part, field)?;
654 } else if !structured_pkix::policy::signature_notice_understood(&pair, &part)
655 .map_err(Error::Pkix)?
656 {
657 return Err(syntax(field, part.span().start));
658 }
659 }
660 }
661 push(
662 open,
663 OpenValue {
664 role,
665 kind: Some(kind),
666 enclosure: *element,
667 value: value.map(|value| crate::source::Value::of(&pair, value, field)),
668 },
669 field,
670 )
671}
672
673fn push<'a, W: Work>(
674 out: &mut OwnedVec<OpenValue<'a>, W>,
675 value: OpenValue<'a>,
676 field: &'static str,
677) -> Result<()> {
678 let at = value.enclosure.span().start;
679 out.try_push(value)
680 .map_err(|cause| Error::resource(cause, field, at))
681}
682
683fn directory_string(parent: &Reader<'_>, value: &Element<'_>, field: &'static str) -> Result<()> {
684 if ![12, 19, 20, 28, 30]
685 .into_iter()
686 .any(|number| value.tag() == Tag::primitive(number))
687 {
688 return Err(syntax(field, value.span().start));
689 }
690 string(parent, value, field)
691}
692
693fn string<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
694 if value.contents().is_empty() {
695 return Err(syntax(field, value.span().start));
696 }
697 structured_pkix::der::conforms(parent, value, field).map_err(Error::Pkix)
698}
699
700fn spec<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
701 if value.tag() == Tag::primitive(universal::OID) {
702 parent
703 .oid(value)
704 .map_err(|cause| Error::encoding(cause, field))?;
705 } else {
706 uri_value(parent, value, field)?;
707 }
708 Ok(())
709}
710
711fn uri_value<'a>(parent: &Reader<'a>, value: &Element<'a>, field: &'static str) -> Result<()> {
712 let octets = Octets::read_ia5(parent, value, field)?;
713 let work = parent.execution();
714 let joined;
715 let bytes = if let Some(one) = octets.as_one() {
716 one
717 } else {
718 joined = octets.copy_in(parent, field, value.span().start)?;
719 &joined
720 };
721 work.charge_work(1 + bytes.len() as u64)
722 .map_err(|cause| Error::resource(cause, field, value.span().start))?;
723 let text = core::str::from_utf8(bytes).map_err(|_| syntax(field, value.span().start))?;
724 if !structured_pkix::general_name::uri_syntax(text, &work)
725 .map_err(|cause| Error::resource(cause, field, value.span().start))?
726 {
727 return Err(syntax(field, value.span().start));
728 }
729 Ok(())
730}
731
732fn sequence<'a>(
733 parent: &Reader<'a>,
734 value: &Element<'a>,
735 field: &'static str,
736) -> Result<Reader<'a>> {
737 require_tag(value, Tag::constructed(universal::SEQUENCE), field)?;
738 parent
739 .enter(value)
740 .map_err(|cause| Error::encoding(cause, field))
741}
742fn next<'a>(reader: &mut Reader<'a>, field: &'static str) -> Result<Element<'a>> {
743 reader
744 .next_element()
745 .map_err(|cause| Error::encoding(cause, field))?
746 .ok_or_else(|| syntax(field, reader.offset()))
747}
748fn end(reader: &Reader<'_>, field: &'static str) -> Result<()> {
749 if reader.is_empty() {
750 Ok(())
751 } else {
752 Err(syntax(field, reader.offset()))
753 }
754}
755fn require_tag(value: &Element<'_>, tag: Tag, field: &'static str) -> Result<()> {
756 if value.tag() == tag {
757 Ok(())
758 } else {
759 Err(syntax(field, value.span().start))
760 }
761}
762fn syntax(field: &'static str, at: u64) -> Error {
763 Error::Syntax {
764 code: ErrorCode::AttributeSyntax,
765 field,
766 at,
767 }
768}