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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
//! Independent generation/imprint facts, not a signature or trust proof.
use super::{Accuracy, HashAlgorithmReading, TokenInfo};
use crate::{Error, ErrorCode, Result};
use structured_asn1::{
Oid, Reader, Span,
memory::{OwnedVec, Work},
value::OwnedOid,
};
use structured_pkix::temporal::Reading;
/// Independently retained written hash algorithm and logical digest bytes.
/// Parameter absence stays distinct from a written NULL; no re-encoding or
/// generic normalization substitutes for the selected provider's semantics.
#[derive(Debug)]
pub struct RetainedImprint {
algorithm: OwnedOid,
parameters: Option<OwnedVec<u8>>,
hashed: OwnedVec<u8>,
}
impl RetainedImprint {
/// Original validated identifier arcs, now independent of the source parser.
#[must_use]
pub fn algorithm(&self) -> Oid<'_> {
self.algorithm.as_oid()
}
/// Whole original parameter TLV, without a fabricated absent/default value.
#[must_use]
pub fn parameters(&self) -> Option<&[u8]> {
self.parameters.as_deref()
}
/// Complete logical hashedMessage octets, with no fragment headers.
#[must_use]
pub fn hashed(&self) -> &[u8] {
&self.hashed
}
}
/// A conforming token's declared generation/imprint projection.
///
/// This deliberately carries no successful signature/trust verdict. Callers
/// inspect and verify the token independently; only the complete timestamp
/// checker may turn positive checks into evidence. Every retained allocation
/// holds its charge after parser/token drop; no infallible deep Clone exists.
#[derive(Debug)]
pub struct RetainedFacts {
imprint: RetainedImprint,
policy: OwnedOid,
serial: OwnedVec<u8>,
nonce: Option<OwnedVec<u8>>,
gen_time: structured_pkix::temporal::ExactReading,
accuracy: Option<Accuracy>,
ordering: bool,
source_span: Span,
}
impl TokenInfo<'_> {
/// Retains this generation/imprint projection before the scoped source ends.
///
/// A retained malformed token is not silently turned into conforming data.
/// This projection refuses inspection faults; the original model and all
/// diagnostics remain available on refusal. It copies only declared facts,
/// not a certificate, source payload, provider state or a trust assertion.
pub fn retain_facts(&self, parent: &Reader<'_>) -> Result<RetainedFacts> {
self.imprint().require_session(parent)?;
if self.has_faults() {
return Err(Error::schema(
ErrorCode::MalformedValue,
"tstInfo",
self.span().start,
));
}
let HashAlgorithmReading::Identifier(algorithm) = self.imprint().algorithm() else {
return Err(Error::schema(
ErrorCode::MalformedValue,
"tstInfo.messageImprint",
self.imprint().span().start,
));
};
let accuracy = self
.accuracy()
.map(super::AccuracyReading::deviation)
.transpose()
.map_err(|error| {
Error::schema(
ErrorCode::MalformedValue,
"tstInfo.accuracy",
error.span.start,
)
})?;
let work = parent.execution();
work.charge_work(8)
.map_err(|reason| Error::resource(reason, "tstInfo", self.span().start))?;
let hash_algorithm =
OwnedOid::from_oid_in(algorithm.algorithm(), &work).map_err(|reason| {
Error::resource(
reason,
"tstInfo.messageImprint.hashAlgorithm",
algorithm.offset(),
)
})?;
let parameters = algorithm
.parameters()
.map(|bytes| {
retain(
bytes,
&work,
"tstInfo.messageImprint.hashAlgorithm.parameters",
algorithm.offset(),
)
})
.transpose()?;
let hashed = self
.imprint()
.hashed()
.copy_in(
parent,
"tstInfo.messageImprint.hashedMessage",
self.imprint().hashed().span().start,
)?
.into_owned();
let policy = OwnedOid::from_oid_in(self.policy(), &work)
.map_err(|reason| Error::resource(reason, "tstInfo.policy", self.span().start))?;
let serial = retain(
self.serial().element().contents(),
&work,
"tstInfo.serialNumber",
self.serial().element().span().start,
)?;
let nonce = self
.nonce()
.map(|nonce| {
retain(
nonce.element().contents(),
&work,
"tstInfo.nonce",
nonce.element().span().start,
)
})
.transpose()?;
let gen_time = self
.precise_gen_time()
.try_copy_in(&work, "tstInfo.genTime", self.span().start)
.map_err(Error::pkix)?;
Ok(RetainedFacts {
imprint: RetainedImprint {
algorithm: hash_algorithm,
parameters,
hashed,
},
policy,
serial,
nonce,
gen_time,
accuracy,
ordering: self.ordering(),
source_span: self.span(),
})
}
}
fn retain<W: Work>(bytes: &[u8], work: &W, field: &'static str, at: u64) -> Result<OwnedVec<u8>> {
let mut owned = work.vector();
owned
.try_extend_from_slice(bytes)
.map_err(|reason| Error::resource(reason, field, at))?;
Ok(owned.into_owned())
}
impl RetainedFacts {
/// Copies declared facts into independently charged current storage.
/// Moving the owner needs no copy; a genuine deep copy admits new work and
/// capacity before execution. Partial refusal leaves this source unchanged.
pub fn try_copy_in<W: Work>(&self, work: &W) -> Result<Self> {
work.charge_work(8)
.map_err(|reason| Error::resource(reason, "tstInfo", self.source_span.start))?;
let algorithm =
OwnedOid::from_oid_in(self.imprint.algorithm(), work).map_err(|reason| {
Error::resource(
reason,
"tstInfo.messageImprint.hashAlgorithm",
self.source_span.start,
)
})?;
let parameters = self
.imprint
.parameters()
.map(|bytes| {
retain(
bytes,
work,
"tstInfo.messageImprint.hashAlgorithm.parameters",
self.source_span.start,
)
})
.transpose()?;
let hashed = retain(
self.imprint.hashed(),
work,
"tstInfo.messageImprint.hashedMessage",
self.source_span.start,
)?;
let policy = OwnedOid::from_oid_in(self.policy(), work)
.map_err(|reason| Error::resource(reason, "tstInfo.policy", self.source_span.start))?;
let serial = retain(
self.serial(),
work,
"tstInfo.serialNumber",
self.source_span.start,
)?;
let nonce = self
.nonce()
.map(|bytes| retain(bytes, work, "tstInfo.nonce", self.source_span.start))
.transpose()?;
let gen_time = self
.gen_time
.try_copy_in(work, "tstInfo.genTime", self.source_span.start)
.map_err(Error::pkix)?;
Ok(Self {
imprint: RetainedImprint {
algorithm,
parameters,
hashed,
},
policy,
serial,
nonce,
gen_time,
accuracy: self.accuracy,
ordering: self.ordering,
source_span: self.source_span,
})
}
/// Declared complete imprint, requiring selected-provider resolution to use.
#[must_use]
pub fn imprint(&self) -> &RetainedImprint {
&self.imprint
}
/// Original policy arcs; no product policy acceptance is implied.
#[must_use]
pub fn policy(&self) -> Oid<'_> {
self.policy.as_oid()
}
/// Original serial INTEGER contents, never a narrowing numeric conversion.
#[must_use]
pub fn serial(&self) -> &[u8] {
&self.serial
}
/// Original nonce INTEGER contents when one was written.
#[must_use]
pub fn nonce(&self) -> Option<&[u8]> {
self.nonce.as_deref()
}
/// Original common UTC reading, not a fabricated exact instant.
#[must_use]
pub fn gen_time(&self) -> Reading {
self.gen_time.reading()
}
/// Exact generation-time order, retaining arbitrary subnanosecond digits.
/// This compares authenticated facts only when called on actual evidence;
/// a same-TSA ordering assertion or accuracy rule is a separate precondition.
/// Both UTC labels and complete comparison work are admitted before lookup.
pub fn compare_generation<W: Work>(
&self,
other: &Self,
utc: &super::LeapTable<'_>,
work: &W,
) -> core::result::Result<core::cmp::Ordering, super::GenerationOrderError> {
self.gen_time.compare(&other.gen_time, utc, work)
}
/// Complete Accuracy absence remains distinct from a present zero value.
#[must_use]
pub fn accuracy(&self) -> Option<Accuracy> {
self.accuracy
}
/// Original ordering assertion, which does not establish chronology itself.
#[must_use]
pub fn ordering(&self) -> bool {
self.ordering
}
/// Range in the original callback reader's coordinates. When logical, keep
/// its supplied `SourceMap` separately; this is not a physical document span.
#[must_use]
pub fn source_span(&self) -> Span {
self.source_span
}
}