structured_cms/timestamp/
authority.rs1use super::TokenInfo;
4use crate::{Error, ErrorCode};
5use structured_asn1::{Reader, Tag, memory::Work, universal};
6use structured_pkix::{
7 Certificate,
8 general_name::{self, GeneralName},
9 oid,
10 usage::ExtendedKeyUsage,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum AuthoritySource {
16 Token,
18 Certificate,
20}
21
22#[derive(Debug)]
24pub struct AuthorityError {
25 pub source: AuthoritySource,
27 pub cause: Error,
29}
30
31#[derive(Debug)]
33pub enum TsaUsage {
34 TimeStampingOnly,
36 Missing,
38 NotCritical,
40 OtherPurposes,
42 Unreadable(Error),
44}
45
46#[derive(Debug)]
48pub enum TsaIdentity {
49 Absent,
51 Matches,
53 Differs,
55 Unreadable,
57 CertificateUnreadable(Error),
59}
60
61#[derive(Debug)]
63pub struct AuthorityFacts {
64 pub usage: TsaUsage,
66 pub identity: TsaIdentity,
68}
69
70impl TokenInfo<'_> {
71 pub fn authority(
80 &self,
81 certificate: &Certificate<'_>,
82 token_parent: &Reader<'_>,
83 certificate_parent: &Reader<'_>,
84 ) -> Result<AuthorityFacts, AuthorityError> {
85 self.imprint()
86 .require_session(token_parent)
87 .map_err(token_error)?;
88 if !certificate
89 .tbs()
90 .public_key()
91 .algorithm()
92 .same_session(certificate_parent)
93 {
94 return Err(cert_error(Error::pkix(structured_pkix::Error::new(
95 structured_pkix::ErrorCode::ForeignReader,
96 "certificate",
97 certificate.span().start,
98 ))));
99 }
100 Ok(AuthorityFacts {
101 usage: usage(certificate, token_parent)?,
102 identity: identity(self, certificate, token_parent)?,
103 })
104 }
105}
106
107fn token_error(cause: Error) -> AuthorityError {
108 AuthorityError {
109 source: AuthoritySource::Token,
110 cause,
111 }
112}
113fn cert_error(cause: Error) -> AuthorityError {
114 AuthorityError {
115 source: AuthoritySource::Certificate,
116 cause,
117 }
118}
119
120fn usage(certificate: &Certificate<'_>, query: &Reader<'_>) -> Result<TsaUsage, AuthorityError> {
121 let Some(extension) = certificate
122 .tbs()
123 .extensions()
124 .get(oid::EXT_KEY_USAGE, &query.execution())
125 .map_err(Error::pkix)
126 .map_err(cert_error)?
127 else {
128 return Ok(TsaUsage::Missing);
129 };
130 if !extension.is_critical() {
131 return Ok(TsaUsage::NotCritical);
132 }
133 let purposes = match ExtendedKeyUsage::read_in(extension, query).map_err(Error::pkix) {
134 Ok(purposes) => purposes,
135 Err(error) if error.is_terminal() => return Err(cert_error(error)),
136 Err(error) => return Ok(TsaUsage::Unreadable(error)),
137 };
138 if purposes.purposes().len() != 1 {
139 return Ok(TsaUsage::OtherPurposes);
140 }
141 query
142 .execution()
143 .charge_work(1 + purposes.purposes()[0].as_bytes().len() as u64)
144 .map_err(|reason| {
145 cert_error(Error::resource(
146 reason,
147 "extensions.extKeyUsage",
148 extension.span().start,
149 ))
150 })?;
151 Ok(if purposes.purposes()[0] == oid::TIME_STAMPING {
152 TsaUsage::TimeStampingOnly
153 } else {
154 TsaUsage::OtherPurposes
155 })
156}
157
158fn identity(
159 info: &TokenInfo<'_>,
160 certificate: &Certificate<'_>,
161 query: &Reader<'_>,
162) -> Result<TsaIdentity, AuthorityError> {
163 let Some(tsa) = info.tsa() else {
164 return Ok(TsaIdentity::Absent);
165 };
166 let Some(name) = tsa.name() else {
167 return Ok(TsaIdentity::Unreadable);
168 };
169 if let GeneralName::Directory(name) = name
170 && name
171 .matches_in(certificate.tbs().subject(), query)
172 .map_err(|cause| {
173 Error::pkix(structured_pkix::Error::new(
174 cause.code(),
175 "tstInfo.tsa",
176 tsa.span().start,
177 ))
178 })
179 .map_err(token_error)?
180 {
181 return Ok(TsaIdentity::Matches);
182 }
183 let Some(extension) = certificate
184 .tbs()
185 .extensions()
186 .get(oid::SUBJECT_ALT_NAME, &query.execution())
187 .map_err(Error::pkix)
188 .map_err(cert_error)?
189 else {
190 return Ok(TsaIdentity::Differs);
191 };
192 match san_matches(extension, name, tsa.span().start, query) {
193 Ok(true) => Ok(TsaIdentity::Matches),
194 Ok(false) => Ok(TsaIdentity::Differs),
195 Err(error) if error.field() == "tstInfo.tsa" => Err(token_error(error)),
198 Err(error) if error.is_terminal() => Err(cert_error(error)),
199 Err(error) => Ok(TsaIdentity::CertificateUnreadable(error)),
200 }
201}
202
203fn san_matches(
204 extension: &structured_pkix::Extension<'_>,
205 name: &GeneralName<'_>,
206 at: u64,
207 query: &Reader<'_>,
208) -> crate::Result<bool> {
209 const FIELD: &str = "extensions.subjectAltName";
210 let mut reader = extension
211 .reader_in(query)
212 .map_err(|source| Error::encoding(source, FIELD))?;
213 let sequence = reader
214 .expect(Tag::constructed(universal::SEQUENCE))
215 .map_err(|source| Error::encoding(source, FIELD))?;
216 if !reader.is_empty() {
217 return Err(Error::schema(
218 ErrorCode::TrailingData,
219 FIELD,
220 reader.offset(),
221 ));
222 }
223 let mut inside = reader
224 .enter(&sequence)
225 .map_err(|source| Error::encoding(source, FIELD))?;
226 let mut matched = false;
227 while let Some(element) = inside
228 .next_element()
229 .map_err(|source| Error::encoding(source, FIELD))?
230 {
231 let alternative = general_name::read(&inside, &element, FIELD).map_err(Error::pkix)?;
232 if !matched
233 && general_name::matches(query, name, &alternative, at, "tstInfo.tsa")
234 .map_err(Error::pkix)?
235 {
236 matched = true;
237 }
238 }
239 Ok(matched)
240}