1use super::{
2 Check, DigestedObject, IssuerForm, Reason, charge, check_name, check_names, directory, proxy,
3 refused, target,
4};
5use crate::attribute_certificate::AttributeCertificate;
6use crate::{Error, Result};
7use structured_asn1::memory::{MemoryError, Work};
8use structured_asn1::{Oid, Tag, universal};
9use structured_pkix::access::{DistributionPoints, InfoAccess};
10use structured_pkix::general_name::GeneralName;
11use structured_pkix::identifier::AuthorityKeyIdentifier;
12use structured_pkix::{Extension, oid};
13
14pub const AUDIT_IDENTITY: Oid<'static> = Oid::from_bytes(&[43, 6, 1, 5, 5, 7, 1, 4]);
16pub const NO_REV_AVAILABLE: Oid<'static> = Oid::from_bytes(&[85, 29, 56]);
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Revocation {
22 NoRevAvailable,
24 Required,
26}
27
28#[derive(Debug)]
31pub struct Profile<'r, 'a> {
32 certificate: &'r AttributeCertificate<'a>,
33 audit: Option<&'a [u8]>,
34 targets: Option<target::TargetInformation<'a>>,
35 proxy: Option<proxy::ProxyInfo<'a>>,
36 authority_key: Option<AuthorityKeyIdentifier<'a>>,
37 access: Option<InfoAccess<'a>>,
38 distribution: Option<DistributionPoints<'a>>,
39 revocation: Revocation,
40}
41
42impl<'r, 'a> Profile<'r, 'a> {
43 fn new(certificate: &'r AttributeCertificate<'a>) -> Self {
44 Self {
45 certificate,
46 audit: None,
47 targets: None,
48 proxy: None,
49 authority_key: None,
50 access: None,
51 distribution: None,
52 revocation: Revocation::Required,
53 }
54 }
55 #[must_use]
57 pub fn certificate(&self) -> &AttributeCertificate<'a> {
58 self.certificate
59 }
60 #[must_use]
63 pub fn audit_identity(&self) -> Option<&'a [u8]> {
64 self.audit
65 }
66 #[must_use]
68 pub fn targeting(&self) -> Option<&target::TargetInformation<'a>> {
69 self.targets.as_ref()
70 }
71 #[must_use]
73 pub fn proxying(&self) -> Option<&proxy::ProxyInfo<'a>> {
74 self.proxy.as_ref()
75 }
76 #[must_use]
78 pub fn authority_key(&self) -> Option<&AuthorityKeyIdentifier<'a>> {
79 self.authority_key.as_ref()
80 }
81 #[must_use]
83 pub fn access(&self) -> Option<&InfoAccess<'a>> {
84 self.access.as_ref()
85 }
86 #[must_use]
88 pub fn distribution(&self) -> Option<&DistributionPoints<'a>> {
89 self.distribution.as_ref()
90 }
91 #[must_use]
93 pub fn revocation(&self) -> Revocation {
94 self.revocation
95 }
96 #[must_use]
99 pub fn holder_alternatives(&self) -> u8 {
100 let holder = &self.certificate.information().holder;
101 u8::from(holder.base_certificate.is_some())
102 + u8::from(holder.entity_names.is_some())
103 + u8::from(holder.object_digest.is_some())
104 }
105}
106
107fn url(text: &str, schemes: &[&[u8]]) -> bool {
108 let bytes = text.as_bytes();
114 for scheme in schemes {
115 if bytes.len() > scheme.len()
116 && bytes[..scheme.len()].eq_ignore_ascii_case(scheme)
117 && (*scheme == b"ldap://" || !matches!(bytes[scheme.len()], b'/' | b'?' | b'#'))
118 {
119 return true;
120 }
121 }
122 false
123}
124
125fn critical<T>(extension: &Extension<'_>, expected: bool) -> Option<Check<T>> {
126 (extension.is_critical() != expected).then(|| refused(Reason::Criticality, extension.span()))
127}
128
129fn check_fields<W: Work>(ac: &AttributeCertificate<'_>, work: &W) -> Result<Check<()>> {
130 let at = ac.signed_element().span();
131 let info = ac.information();
132 charge(work, info.serial.as_bytes().len() as u64 + 8, at.start)?;
133 if info.issuer.form != IssuerForm::V2
137 || info.issuer.base_certificate.is_some()
138 || info.issuer.object_digest.is_some()
139 || info.issuer.names.as_deref().and_then(directory).is_none()
140 {
141 return Ok(refused(Reason::IssuerForm, at));
142 }
143 if info.serial.is_negative()
144 || info.serial.as_bytes().len() > 20
145 || info.serial.as_bytes().iter().all(|b| *b == 0)
146 {
147 return Ok(refused(Reason::Serial, at));
148 }
149 if info.not_before > info.not_after {
150 return Ok(refused(Reason::Validity, at));
151 }
152 if let Some(base) = &info.holder.base_certificate
153 && directory(&base.issuer).is_none()
154 {
155 return Ok(refused(Reason::HolderIssuer, at));
156 }
157 if let Some(names) = &info.holder.entity_names
158 && let rejected @ Check::Rejected(_) = check_names(names, work, at)?
159 {
160 return Ok(rejected);
161 }
162 if let Some(digest) = &info.holder.object_digest
163 && (digest.object_type == DigestedObject::Other || digest.other_type.is_some())
164 {
165 return Ok(refused(Reason::ObjectDigest, at));
166 }
167 Ok(Check::Accepted(()))
168}
169
170fn read_audit<'a>(extension: &Extension<'a>, work: &impl Work) -> Result<Check<&'a [u8]>> {
171 let mut reader = extension
172 .reader_scoped(work)
173 .map_err(|e| Error::encoding(e, super::FIELD))?;
174 let value = reader
175 .expect(Tag::primitive(universal::OCTET_STRING))
176 .map_err(|e| Error::encoding(e, super::FIELD))?;
177 super::super::end(&reader, super::FIELD)?;
178 if value.contents().is_empty() || value.contents().len() > 20 {
179 return Ok(refused(Reason::AuditIdentity, value.span()));
180 }
181 Ok(Check::Accepted(value.contents()))
182}
183
184fn read_access<'a>(
185 extension: &Extension<'a>,
186 work: &impl Work,
187) -> Result<Check<(InfoAccess<'a>, bool)>> {
188 let at = extension.span();
189 let access = InfoAccess::read(extension, work).map_err(Error::pkix)?;
190 let mut pointer = false;
191 for entry in access.descriptions() {
192 charge(work, entry.method().as_bytes().len() as u64 + 1, at.start)?;
193 if let Check::Rejected(reason) = check_name(entry.location(), work, at)? {
194 return Ok(Check::Rejected(reason));
195 }
196 if entry.method() == oid::OCSP {
197 let GeneralName::Uri(uri) = entry.location() else {
198 return Ok(refused(Reason::OcspLocation, at));
199 };
200 charge(work, 8, at.start)?;
201 if !url(uri, &[b"http://"]) {
202 return Ok(refused(Reason::OcspLocation, at));
203 }
204 pointer = true;
205 }
206 }
207 Ok(Check::Accepted((access, pointer)))
208}
209
210fn read_distribution<'a>(
211 extension: &Extension<'a>,
212 work: &impl Work,
213) -> Result<Check<DistributionPoints<'a>>> {
214 let at = extension.span();
215 let points = DistributionPoints::read(extension, work).map_err(Error::pkix)?;
216 let [point] = points.points() else {
217 return Ok(refused(Reason::DistributionPoint, at));
218 };
219 let valid = match point.full_name() {
220 [GeneralName::Directory(name)] => !name.is_empty(),
221 [GeneralName::Uri(uri)] => {
222 charge(work, 16, at.start)?;
223 url(uri, &[b"http://", b"ldap://"])
224 }
225 _ => false,
226 };
227 if !valid || point.name_relative_to_issuer().is_some() {
228 return Ok(refused(Reason::DistributionPoint, at));
229 }
230 Ok(Check::Accepted(points))
231}
232
233fn read_no_revocation(extension: &Extension<'_>, work: &impl Work) -> Result<()> {
234 let mut reader = extension
235 .reader_scoped(work)
236 .map_err(|e| Error::encoding(e, super::FIELD))?;
237 let value = reader
238 .expect(Tag::primitive(universal::NULL))
239 .map_err(|e| Error::encoding(e, super::FIELD))?;
240 reader
241 .null(&value)
242 .map_err(|e| Error::encoding(e, super::FIELD))?;
243 super::super::end(&reader, super::FIELD)
244}
245
246fn check_targets(
247 targets: &[target::Target<'_>],
248 certificates: &[target::TargetCertificate<'_>],
249 work: &impl Work,
250) -> Result<Check<()>> {
251 if let Some(forbidden) = certificates.first() {
252 return Ok(refused(Reason::TargetCertificate, forbidden.element.span()));
253 }
254 if let Some(first) = targets.first() {
255 charge(work, targets.len() as u64, first.element.span().start)?;
256 }
257 for target in targets {
258 match &target.value {
259 target::Value::Name(name) | target::Value::Group(name) => {
260 if let rejected @ Check::Rejected(_) =
261 check_name(name, work, target.element.span())?
262 {
263 return Ok(rejected);
264 }
265 }
266 target::Value::Certificate(_) => {
267 return Ok(refused(Reason::TargetCertificate, target.element.span()));
268 }
269 }
270 }
271 Ok(Check::Accepted(()))
272}
273
274fn read_targeting<'a>(
275 extension: &Extension<'a>,
276 work: &impl Work,
277) -> Result<Check<target::TargetInformation<'a>>> {
278 let targets = target::TargetInformation::read(extension, work)?;
279 if let Check::Rejected(reason) = check_targets(targets.targets(), targets.certificates(), work)?
280 {
281 return Ok(Check::Rejected(reason));
282 }
283 Ok(Check::Accepted(targets))
284}
285
286fn read_proxying<'a>(
287 extension: &Extension<'a>,
288 work: &impl Work,
289) -> Result<Check<proxy::ProxyInfo<'a>>> {
290 let proxy = proxy::ProxyInfo::read(extension, work)?;
291 if let Check::Rejected(reason) = check_targets(proxy.targets(), proxy.certificates(), work)? {
292 return Ok(Check::Rejected(reason));
293 }
294 Ok(Check::Accepted(proxy))
295}
296
297fn read_authority_key<'a>(
298 extension: &Extension<'a>,
299 work: &impl Work,
300) -> Result<Check<AuthorityKeyIdentifier<'a>>> {
301 let key = AuthorityKeyIdentifier::read(extension, work).map_err(Error::pkix)?;
302 if let Check::Rejected(reason) = check_names(key.issuer(), work, extension.span())? {
303 return Ok(Check::Rejected(reason));
304 }
305 if key.key_id().is_none() {
307 return Ok(refused(Reason::AuthorityKeyIdentifier, extension.span()));
308 }
309 Ok(Check::Accepted(key))
310}
311
312pub(super) fn check<'r, 'a, W: Work>(
313 ac: &'r AttributeCertificate<'a>,
314 work: &W,
315) -> Result<Check<Profile<'r, 'a>>> {
316 let execution = work.execution_for(ac.parser);
317 if let Check::Rejected(reason) = check_fields(ac, &execution)? {
318 return Ok(Check::Rejected(reason));
319 }
320 let info = ac.information();
321 let at = ac.signed_element().span();
322 let mut facts = Profile::new(ac);
323 let Some(extensions) = &info.extensions else {
324 return Ok(Check::Accepted(facts));
325 };
326 let mut pointer = false;
327 for extension in extensions.iter(&execution) {
328 let extension = extension.map_err(Error::pkix)?;
329 let at = extension.span();
330 let dispatch_work = (extension.kind().as_bytes().len() as u64)
332 .checked_mul(8)
333 .and_then(|n| n.checked_add(8))
334 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, super::FIELD, at.start))?;
335 charge(&execution, dispatch_work, at.start)?;
336 match extension.kind() {
337 kind if kind == AUDIT_IDENTITY => {
338 if let Some(failure) = critical(extension, true) {
339 return Ok(failure);
340 }
341 facts.audit = Some(match read_audit(extension, &execution)? {
342 Check::Accepted(value) => value,
343 Check::Rejected(reason) => return Ok(Check::Rejected(reason)),
344 });
345 }
346 kind if kind == target::IDENTIFIER => {
347 if let Some(failure) = critical(extension, true) {
348 return Ok(failure);
349 }
350 facts.targets = Some(match read_targeting(extension, &execution)? {
351 Check::Accepted(value) => value,
352 Check::Rejected(reason) => return Ok(Check::Rejected(reason)),
353 });
354 }
355 kind if kind == proxy::IDENTIFIER => {
356 if let Some(failure) = critical(extension, true) {
357 return Ok(failure);
358 }
359 facts.proxy = Some(match read_proxying(extension, &execution)? {
360 Check::Accepted(value) => value,
361 Check::Rejected(reason) => return Ok(Check::Rejected(reason)),
362 });
363 }
364 kind if kind == oid::AUTHORITY_KEY_IDENTIFIER => {
365 if let Some(failure) = critical(extension, false) {
366 return Ok(failure);
367 }
368 facts.authority_key = Some(match read_authority_key(extension, &execution)? {
369 Check::Accepted(value) => value,
370 Check::Rejected(reason) => return Ok(Check::Rejected(reason)),
371 });
372 }
373 kind if kind == oid::AUTHORITY_INFO_ACCESS => {
374 if let Some(failure) = critical(extension, false) {
375 return Ok(failure);
376 }
377 let (access, has_pointer) = match read_access(extension, &execution)? {
378 Check::Accepted(value) => value,
379 Check::Rejected(reason) => return Ok(Check::Rejected(reason)),
380 };
381 pointer |= has_pointer;
382 facts.access = Some(access);
383 }
384 kind if kind == oid::CRL_DISTRIBUTION_POINTS => {
385 if let Some(failure) = critical(extension, false) {
386 return Ok(failure);
387 }
388 facts.distribution = Some(match read_distribution(extension, &execution)? {
389 Check::Accepted(value) => value,
390 Check::Rejected(reason) => return Ok(Check::Rejected(reason)),
391 });
392 pointer = true;
393 }
394 kind if kind == NO_REV_AVAILABLE => {
395 if let Some(failure) = critical(extension, false) {
396 return Ok(failure);
397 }
398 read_no_revocation(extension, &execution)?;
399 facts.revocation = Revocation::NoRevAvailable;
400 }
401 _ if extension.is_critical() => {
402 return Ok(refused(Reason::UnknownCritical, at));
403 }
404 _ => {}
405 }
406 }
407 if pointer && facts.revocation == Revocation::NoRevAvailable {
409 return Ok(refused(Reason::RevocationConflict, at));
410 }
411 Ok(Check::Accepted(facts))
412}