1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
//! Operation-bound lookups for signature-extension formats.
use super::*;
/// Outcome of resolving a same-document bare-name URI (`#id`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BareNameTarget {
/// The fragment names exactly this element under the operation's ID rules.
Element(NodeIdentity),
/// The URI has another form: a non-empty base, no fragment, or an XPointer.
NotBareName,
/// No element, or more than one, carries this ID.
Unresolved,
}
/// An original `DigestMethod` interpreted under the compiled digest policy.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DigestMethodResolution {
/// A digest this crate implements and the operation policy permits.
Supported(DigestAlgorithm),
/// The Algorithm URI names no digest implemented by this crate.
Unsupported,
/// The required Algorithm attribute is absent.
Malformed,
}
/// One incremental digest of a verified operation's provider.
pub struct SessionDigest<'p> {
algorithm: DigestAlgorithm,
state: Box<dyn crate::provider::DigestState + 'p>,
}
impl SessionDigest<'_> {
/// Required digest output width, independent of provider output.
pub fn output_len(&self) -> usize {
self.algorithm.output_len()
}
/// Absorb `data`, charging its length to `work` first.
pub fn update<Q: Work>(&mut self, data: &[u8], work: &Q) -> Result<(), DsigError> {
work.charge_work(u64::try_from(data.len()).map_err(|_| MemoryError::WorkExceeded)?)?;
self.state.update(data).map_err(DsigError::from)
}
/// Write the digest into an exactly sized caller buffer. Admit finalization
/// work and the provider's temporary output before invoking it; the caller
/// can keep an inline output without another heap allocation.
pub fn finish<Q: Work>(self, output: &mut [u8], work: &Q) -> Result<(), DsigError> {
let width = self.algorithm.output_len();
if output.len() != width {
return Err(DsigError::InvalidRequest {
reason: "digest output buffer has the wrong width",
});
}
work.charge_work(width as u64)?;
let _temporary = work.memory().reserve(width)?;
let digest = super::super::super::digest::finish_digest(self.algorithm, self.state)
.map_err(DsigError::from)?;
output.copy_from_slice(&digest);
Ok(())
}
}
impl<W: Work> VerifiedSession<'_, '_, '_, W> {
/// Resolve `#id` through this operation's ID registrations, fragment
/// unescaping and same-document ID semantics, charging `work`. XMLDSig 1.1
/// §4.4.3.3 defines the bare-name form; XPointer and non-fragment forms are
/// reported, never interpreted.
/// <https://www.w3.org/TR/2013/REC-xmldsig-core1-20130411/#sec-Same-Document>
pub fn resolve_bare_name_in<Q: Work>(
&self,
uri: &str,
work: &Q,
) -> Result<BareNameTarget, DsigError> {
work.charge_work(1)?;
let target = self
.resolver
.bare_name_node_in(
uri,
Some(self.budgets.transforms.node_set_materialization()),
&crate::c14n::scratch::Accounted(work),
)
.map_err(|error| {
DsigError::Reference(ReferenceProcessingError::UriDereference(error))
})?;
Ok(match target {
None => BareNameTarget::NotBareName,
Some(Some(node)) if node.is_element() => {
BareNameTarget::Element(self.view.node_identity(node))
}
Some(_) => BareNameTarget::Unresolved,
})
}
/// Interpret an original `DigestMethod` element. A recognized algorithm
/// that the operation policy forbids is a policy error, not `Unsupported`.
pub fn resolve_digest_method_in<Q: Work>(
&self,
method: NodeIdentity,
work: &Q,
) -> Result<DigestMethodResolution, DsigError> {
work.charge_work(1)?;
let node = self.view.resolve_node(method)?;
if !node.has_tag_name((XMLDSIG_NS, "DigestMethod")) {
return Err(DsigError::InvalidRequest {
reason: "digest method identity is not a ds:DigestMethod",
});
}
let mut algorithm = None;
for attribute in node.attributes() {
work.charge_work(
u64::try_from(attribute.name().len() + 1).map_err(|_| MemoryError::WorkExceeded)?,
)?;
if attribute.namespace().is_none() && attribute.name() == "Algorithm" {
algorithm = Some(attribute.value());
break;
}
}
let Some(uri) = algorithm else {
return Ok(DigestMethodResolution::Malformed);
};
work.charge_work(u64::try_from(uri.len()).map_err(|_| MemoryError::WorkExceeded)?)?;
// XMLDSig 1.1 §4.4.3.5 types Algorithm as anyURI, whose whiteSpace is
// collapse (XSD 1.0 §4.3.6). No algorithm URI has interior whitespace,
// so trimming the four XML whitespace characters decides the same.
// https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#rf-whiteSpace
let uri = uri.trim_matches([' ', '\t', '\r', '\n']);
let Some(algorithm) = DigestAlgorithm::from_uri(uri) else {
return Ok(DigestMethodResolution::Unsupported);
};
self.context.policy.check_digest_algorithm(algorithm)?;
Ok(DigestMethodResolution::Supported(algorithm))
}
/// Start an incremental digest with the selected provider under the
/// compiled digest policy.
pub fn begin_digest(&self, algorithm: DigestAlgorithm) -> Result<SessionDigest<'_>, DsigError> {
self.context.policy.check_digest_algorithm(algorithm)?;
let state = super::super::super::digest::begin_digest(self.context.provider, algorithm)
.map_err(DsigError::from)?;
Ok(SessionDigest { algorithm, state })
}
/// This verified request's own execution. A continuation that funds its
/// work from the request, rather than from a separately supplied query
/// account, keeps the caller's local and aggregate ceilings.
pub fn execution(&self) -> &W {
self.work
}
/// A DER certificate parser bounded by this operation's X.509 resource
/// policy (`max_x509_work`, `max_x509_workspace_bytes`, under the hard
/// ceilings), on a fresh account of its own, as verification's
/// certificate processing is. Path validation and certificate storage
/// that use its account obey the same limits.
pub fn certificate_parser(&self) -> structured_asn1::Parser {
super::super::super::pkix::CertificateSession::new(&self.context.policy.resources)
.into_parser()
}
/// Shared path-processor bounds from this operation's key-trust policy,
/// the same bounds verification applied to its own paths.
pub fn certificate_path_bounds(&self) -> structured_pkix::path::ValidationBounds {
super::super::super::pkix::path_bounds(self.context.policy.key_trust.max_x509_chain_depth)
}
/// Certificate-path signature checks for `chain` and `anchor` through this
/// operation's provider and certificate trust policy.
pub fn certificate_path_verifier<'a, 'p>(
&'p self,
chain: &'p [&'a structured_pkix::Certificate<'a>],
anchor: &'a structured_pkix::Certificate<'a>,
) -> super::super::super::pkix::CertificatePathVerifier<'a, 'p> {
super::super::super::pkix::CertificatePathVerifier::new(
self.context.provider,
&self.context.policy.key_trust,
chain,
anchor,
)
}
}
#[cfg(test)]
mod digest_admission_tests {
use super::*;
use std::cell::Cell;
struct Observed<'a>(&'a Cell<bool>);
impl crate::provider::DigestState for Observed<'_> {
fn update(&mut self, _: &[u8]) -> Result<(), crate::provider::ProviderError> {
Ok(())
}
fn finalize(self: Box<Self>) -> Result<Vec<u8>, crate::provider::ProviderError> {
self.0.set(true);
Ok(vec![0; 32])
}
}
#[test]
fn digest_refusals_never_enter_provider_finalize() {
// Wrong output shape and both admission axes fail before the backend,
// and a temporary output reservation is released on every path.
for (capacity, work, width) in [(31, 100, 32), (32, 31, 32), (32, 100, 31), (32, 100, 32)] {
let memory = structured_asn1::memory::Memory::new(std::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(capacity, work),
));
let called = Cell::new(false);
let state = SessionDigest {
algorithm: DigestAlgorithm::Sha256,
state: Box::new(Observed(&called)),
};
let mut output = [0; 32];
let result = state.finish(&mut output[..width], &memory);
let admitted = capacity == 32 && work == 100 && width == 32;
assert_eq!(result.is_ok(), admitted, "{result:?}");
assert_eq!(called.get(), admitted);
assert_eq!(memory.used(), 0);
}
}
}