structured_xades/timestamp/
profile.rs1use structured_asn1::{
4 Reader,
5 memory::{MemoryError, Work},
6};
7use structured_cms::{
8 ess::Version,
9 provider::DigestAlgorithm,
10 timestamp::{HashAlgorithmReading, ProfileFailure, ProfileInput, ProfileSource},
11};
12use structured_pkix::AlgorithmIdentifier;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum DigestUse {
17 Signer,
19 Certificate {
21 version: Version,
23 index: usize,
25 },
26 Imprint,
28}
29
30#[derive(Debug)]
32pub enum DigestError {
33 Prohibited {
35 usage: DigestUse,
37 offset: u64,
39 },
40 Resource {
42 usage: DigestUse,
44 offset: u64,
46 cause: MemoryError,
48 },
49}
50
51pub(crate) fn check(
52 input: &ProfileInput<'_, '_, '_, '_>,
53) -> Result<(), ProfileFailure<DigestError>> {
54 written(
60 input
61 .token
62 .signer()
63 .digest_algorithm()
64 .identifier()
65 .expect("timestamp profile runs only after signer faults were refused"),
66 input.parent,
67 ProfileSource::Envelope,
68 DigestUse::Signer,
69 )?;
70 for attribute in [input.ess.v1(), input.ess.v2()].into_iter().flatten() {
71 let mut index = 0;
72 while index < attribute.identifiers().len() {
73 let usage = DigestUse::Certificate {
74 version: attribute.version(),
75 index,
76 };
77 admit(
78 input.parent,
79 ProfileSource::Envelope,
80 usage,
81 attribute.element().span().start,
82 1,
83 )?;
84 let identifier = &attribute.identifiers()[index];
85 if let DigestAlgorithm::Written(algorithm) = identifier.digest_algorithm() {
86 written(algorithm, input.parent, ProfileSource::Envelope, usage)?;
87 }
88 index += 1;
90 }
91 }
92 if let HashAlgorithmReading::Identifier(algorithm) = input.info.imprint().algorithm() {
93 written(
94 *algorithm,
95 input.reader,
96 ProfileSource::Information,
97 DigestUse::Imprint,
98 )?;
99 }
100 Ok(())
101}
102
103fn written(
104 algorithm: AlgorithmIdentifier<'_>,
105 parent: &Reader<'_>,
106 source: ProfileSource,
107 usage: DigestUse,
108) -> Result<(), ProfileFailure<DigestError>> {
109 const MD5: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 2, 5];
112 let offset = algorithm.offset();
113 admit(parent, source, usage, offset, 1)?;
114 let bytes = algorithm.algorithm().as_bytes();
115 if bytes.len() != MD5.len() {
116 return Ok(());
117 }
118 admit(parent, source, usage, offset, MD5.len() as u64)?;
119 if bytes == MD5 {
120 return Err(ProfileFailure {
121 source,
122 cause: DigestError::Prohibited { usage, offset },
123 });
124 }
125 Ok(())
126}
127
128fn admit(
129 parent: &Reader<'_>,
130 source: ProfileSource,
131 usage: DigestUse,
132 offset: u64,
133 cost: u64,
134) -> Result<(), ProfileFailure<DigestError>> {
135 parent
136 .execution()
137 .charge_work(cost)
138 .map_err(|cause| ProfileFailure {
139 source,
140 cause: DigestError::Resource {
141 usage,
142 offset,
143 cause,
144 },
145 })
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use alloc::sync::Arc;
152 use structured_asn1::{
153 Encoding, Limits, Parser,
154 memory::{Memory, MemoryAccount},
155 };
156
157 #[test]
158 fn prohibited_digest_scan_keeps_current_local_and_aggregate_refusals_distinct() {
159 let bytes = [0x30, 10, 6, 8, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 2, 5];
163 let original = Memory::new(Arc::new(MemoryAccount::new(65536, 65536)));
164 let parser = Parser::der(original.clone());
165 let mut parent = parser.read(&bytes, 400).unwrap();
166 let element = parent.next_element().unwrap().unwrap();
167 let algorithm = structured_pkix::algorithm::read(&parent, &element, "digest").unwrap();
168 let parsed_work = original.work_used();
169 for (local, aggregate) in [(1, 100), (9, 100), (100, 1), (100, 9), (10, 10)] {
171 let memory = Memory::new(Arc::new(MemoryAccount::new(0, aggregate)));
172 let operation = Parser::new(
173 Encoding::Der,
174 Limits {
175 max_work: local,
176 ..Limits::default()
177 },
178 memory.clone(),
179 );
180 let query = operation.read(&[], 900).unwrap();
181 let scoped = parser
182 .read_query_in(&query, &[], 400, Encoding::Der, 0)
183 .unwrap();
184 let refusal = written(
185 algorithm,
186 &scoped,
187 ProfileSource::Envelope,
188 DigestUse::Signer,
189 )
190 .unwrap_err();
191 assert_eq!(refusal.source, ProfileSource::Envelope);
192 if local == 10 && aggregate == 10 {
193 assert!(matches!(
194 refusal.cause,
195 DigestError::Prohibited {
196 usage: DigestUse::Signer,
197 offset: 400
198 }
199 ));
200 assert_eq!(memory.work_used(), 10);
201 } else {
202 assert!(matches!(
203 refusal.cause,
204 DigestError::Resource {
205 usage: DigestUse::Signer,
206 offset: 400,
207 ..
208 }
209 ));
210 assert!(memory.work_used() <= 2);
211 }
212 assert_eq!(memory.used(), 0);
213 }
214 assert_eq!(original.work_used(), parsed_work);
215 }
216}