1use super::{
16 dns_name::{self, DnsNameRef},
17 ip_address::{self, IpAddrRef},
18 name::SubjectNameRef,
19};
20use crate::{
21 cert::{Cert, EndEntityOrCa},
22 der,
23 verify_cert::Budget,
24 Error,
25};
26#[cfg(feature = "alloc")]
27use {
28 alloc::vec::Vec,
29 dns_name::{GeneralDnsNameRef, WildcardDnsNameRef},
30};
31
32pub(crate) fn verify_cert_dns_name(
33 cert: &crate::EndEntityCert,
34 dns_name: DnsNameRef,
35) -> Result<(), Error> {
36 let cert = cert.inner();
37 let dns_name = untrusted::Input::from(dns_name.as_ref().as_bytes());
38 iterate_names(
39 Some(cert.subject),
40 cert.subject_alt_name,
41 Err(Error::CertNotValidForName),
42 &mut |name| {
43 if let GeneralName::DnsName(presented_id) = name {
44 match dns_name::presented_id_matches_reference_id(presented_id, dns_name) {
45 Ok(true) => return NameIteration::Stop(Ok(())),
46 Ok(false) | Err(Error::MalformedDnsIdentifier) => (),
47 Err(e) => return NameIteration::Stop(Err(e)),
48 }
49 }
50 NameIteration::KeepGoing
51 },
52 )
53}
54
55pub(crate) fn verify_cert_subject_name(
56 cert: &crate::EndEntityCert,
57 subject_name: SubjectNameRef,
58) -> Result<(), Error> {
59 let ip_address = match subject_name {
60 SubjectNameRef::DnsName(dns_name) => return verify_cert_dns_name(cert, dns_name),
61 SubjectNameRef::IpAddress(IpAddrRef::V4(_, ref ip_address_octets)) => {
62 untrusted::Input::from(ip_address_octets)
63 }
64 SubjectNameRef::IpAddress(IpAddrRef::V6(_, ref ip_address_octets)) => {
65 untrusted::Input::from(ip_address_octets)
66 }
67 };
68
69 iterate_names(
70 None,
73 cert.inner().subject_alt_name,
74 Err(Error::CertNotValidForName),
75 &mut |name| {
76 if let GeneralName::IpAddress(presented_id) = name {
77 match ip_address::presented_id_matches_reference_id(presented_id, ip_address) {
78 true => return NameIteration::Stop(Ok(())),
79 false => (),
80 }
81 }
82 NameIteration::KeepGoing
83 },
84 )
85}
86
87pub(crate) fn check_name_constraints(
89 input: Option<&mut untrusted::Reader>,
90 subordinate_certs: &Cert,
91 budget: &mut Budget,
92) -> Result<(), Error> {
93 let input = match input {
94 Some(input) => input,
95 None => {
96 return Ok(());
97 }
98 };
99
100 fn parse_subtrees<'b>(
101 inner: &mut untrusted::Reader<'b>,
102 subtrees_tag: der::Tag,
103 ) -> Result<Option<untrusted::Input<'b>>, Error> {
104 if !inner.peek(subtrees_tag.into()) {
105 return Ok(None);
106 }
107 der::expect_tag_and_get_value(inner, subtrees_tag).map(Some)
108 }
109
110 let permitted_subtrees = parse_subtrees(input, der::Tag::ContextSpecificConstructed0)?;
111 let excluded_subtrees = parse_subtrees(input, der::Tag::ContextSpecificConstructed1)?;
112
113 let mut child = subordinate_certs;
114 loop {
115 iterate_names(
116 Some(child.subject),
117 child.subject_alt_name,
118 Ok(()),
119 &mut |name| {
120 check_presented_id_conforms_to_constraints(
121 name,
122 permitted_subtrees,
123 excluded_subtrees,
124 budget,
125 )
126 },
127 )?;
128
129 child = match child.ee_or_ca {
130 EndEntityOrCa::Ca(child_cert) => child_cert,
131 EndEntityOrCa::EndEntity => {
132 break;
133 }
134 };
135 }
136
137 Ok(())
138}
139
140fn check_presented_id_conforms_to_constraints(
141 name: GeneralName,
142 permitted_subtrees: Option<untrusted::Input>,
143 excluded_subtrees: Option<untrusted::Input>,
144 budget: &mut Budget,
145) -> NameIteration {
146 match check_presented_id_conforms_to_constraints_in_subtree(
147 name,
148 Subtrees::PermittedSubtrees,
149 permitted_subtrees,
150 budget,
151 ) {
152 stop @ NameIteration::Stop(..) => {
153 return stop;
154 }
155 NameIteration::KeepGoing => (),
156 };
157
158 check_presented_id_conforms_to_constraints_in_subtree(
159 name,
160 Subtrees::ExcludedSubtrees,
161 excluded_subtrees,
162 budget,
163 )
164}
165
166#[derive(Clone, Copy)]
167enum Subtrees {
168 PermittedSubtrees,
169 ExcludedSubtrees,
170}
171
172fn check_presented_id_conforms_to_constraints_in_subtree(
173 name: GeneralName,
174 subtrees: Subtrees,
175 constraints: Option<untrusted::Input>,
176 budget: &mut Budget,
177) -> NameIteration {
178 let mut constraints = match constraints {
179 Some(constraints) => untrusted::Reader::new(constraints),
180 None => {
181 return NameIteration::KeepGoing;
182 }
183 };
184
185 let mut has_permitted_subtrees_match = false;
186 let mut has_permitted_subtrees_mismatch = false;
187
188 while !constraints.at_end() {
189 if let Err(e) = budget.consume_name_constraint_comparison() {
190 return NameIteration::Stop(Err(e));
191 }
192
193 fn general_subtree<'b>(
201 input: &mut untrusted::Reader<'b>,
202 ) -> Result<GeneralName<'b>, Error> {
203 let general_subtree = der::expect_tag_and_get_value(input, der::Tag::Sequence)?;
204 general_subtree.read_all(Error::BadDer, GeneralName::from_der)
205 }
206
207 let base = match general_subtree(&mut constraints) {
208 Ok(base) => base,
209 Err(err) => {
210 return NameIteration::Stop(Err(err));
211 }
212 };
213
214 let matches = match (name, base) {
215 (GeneralName::DnsName(name), GeneralName::DnsName(base)) => {
216 dns_name::presented_id_matches_constraint(name, base)
217 }
218
219 (GeneralName::DirectoryName(name), GeneralName::DirectoryName(base)) => Ok(
220 presented_directory_name_matches_constraint(name, base, subtrees),
221 ),
222
223 (GeneralName::IpAddress(name), GeneralName::IpAddress(base)) => {
224 ip_address::presented_id_matches_constraint(name, base)
225 }
226
227 (GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
236 if name_tag == base_tag =>
237 {
238 Err(Error::NameConstraintViolation)
239 }
240
241 _ => {
242 continue;
245 }
246 };
247
248 match (subtrees, matches) {
249 (Subtrees::PermittedSubtrees, Ok(true)) => {
250 has_permitted_subtrees_match = true;
251 }
252
253 (Subtrees::PermittedSubtrees, Ok(false)) => {
254 has_permitted_subtrees_mismatch = true;
255 }
256
257 (Subtrees::ExcludedSubtrees, Ok(true)) => {
258 return NameIteration::Stop(Err(Error::NameConstraintViolation));
259 }
260
261 (Subtrees::ExcludedSubtrees, Ok(false)) => (),
262
263 (_, Err(err)) => {
264 return NameIteration::Stop(Err(err));
265 }
266 }
267 }
268
269 if has_permitted_subtrees_mismatch && !has_permitted_subtrees_match {
270 NameIteration::Stop(Err(Error::NameConstraintViolation))
274 } else {
275 NameIteration::KeepGoing
276 }
277}
278
279fn presented_directory_name_matches_constraint(
280 _name: untrusted::Input,
281 _constraint: untrusted::Input,
282 subtrees: Subtrees,
283) -> bool {
284 match subtrees {
298 Subtrees::PermittedSubtrees => false,
299 Subtrees::ExcludedSubtrees => true,
300 }
301}
302
303#[derive(Clone, Copy)]
304enum NameIteration {
305 KeepGoing,
306 Stop(Result<(), Error>),
307}
308
309fn iterate_names<'names>(
310 subject: Option<untrusted::Input<'names>>,
311 subject_alt_name: Option<untrusted::Input<'names>>,
312 result_if_never_stopped_early: Result<(), Error>,
313 f: &mut impl FnMut(GeneralName<'names>) -> NameIteration,
314) -> Result<(), Error> {
315 if let Some(subject_alt_name) = subject_alt_name {
316 let mut subject_alt_name = untrusted::Reader::new(subject_alt_name);
317 while !subject_alt_name.at_end() {
324 let name = GeneralName::from_der(&mut subject_alt_name)?;
325 match f(name) {
326 NameIteration::Stop(result) => {
327 return result;
328 }
329 NameIteration::KeepGoing => (),
330 }
331 }
332 }
333
334 if let Some(subject) = subject {
335 match f(GeneralName::DirectoryName(subject)) {
336 NameIteration::Stop(result) => return result,
337 NameIteration::KeepGoing => (),
338 };
339 }
340
341 result_if_never_stopped_early
342}
343
344#[cfg(feature = "alloc")]
345pub(crate) fn list_cert_dns_names<'names>(
346 cert: &'names crate::EndEntityCert<'names>,
347) -> Result<impl Iterator<Item = GeneralDnsNameRef<'names>>, Error> {
348 let cert = &cert.inner();
349 let mut names = Vec::new();
350
351 iterate_names(
352 Some(cert.subject),
353 cert.subject_alt_name,
354 Ok(()),
355 &mut |name| {
356 if let GeneralName::DnsName(presented_id) = name {
357 let dns_name = DnsNameRef::try_from_ascii(presented_id.as_slice_less_safe())
358 .map(GeneralDnsNameRef::DnsName)
359 .or_else(|_| {
360 WildcardDnsNameRef::try_from_ascii(presented_id.as_slice_less_safe())
361 .map(GeneralDnsNameRef::Wildcard)
362 });
363
364 if let Ok(name) = dns_name {
367 names.push(name)
368 }
369 }
370 NameIteration::KeepGoing
371 },
372 )
373 .map(|_| names.into_iter())
374}
375
376#[derive(Clone, Copy)]
382enum GeneralName<'a> {
383 DnsName(untrusted::Input<'a>),
384 DirectoryName(untrusted::Input<'a>),
385 IpAddress(untrusted::Input<'a>),
386
387 Unsupported(u8),
391}
392
393impl<'a> GeneralName<'a> {
394 fn from_der(input: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
395 use ring::io::der::{CONSTRUCTED, CONTEXT_SPECIFIC};
396 #[allow(clippy::identity_op)]
397 const OTHER_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
398 const RFC822_NAME_TAG: u8 = CONTEXT_SPECIFIC | 1;
399 const DNS_NAME_TAG: u8 = CONTEXT_SPECIFIC | 2;
400 const X400_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 3;
401 const DIRECTORY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 4;
402 const EDI_PARTY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 5;
403 const UNIFORM_RESOURCE_IDENTIFIER_TAG: u8 = CONTEXT_SPECIFIC | 6;
404 const IP_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | 7;
405 const REGISTERED_ID_TAG: u8 = CONTEXT_SPECIFIC | 8;
406
407 let (tag, value) = der::read_tag_and_get_value(input)?;
408 Ok(match tag {
409 DNS_NAME_TAG => GeneralName::DnsName(value),
410 DIRECTORY_NAME_TAG => GeneralName::DirectoryName(value),
411 IP_ADDRESS_TAG => GeneralName::IpAddress(value),
412
413 OTHER_NAME_TAG
414 | RFC822_NAME_TAG
415 | X400_ADDRESS_TAG
416 | EDI_PARTY_NAME_TAG
417 | UNIFORM_RESOURCE_IDENTIFIER_TAG
418 | REGISTERED_ID_TAG => {
419 GeneralName::Unsupported(tag & !(CONTEXT_SPECIFIC | CONSTRUCTED))
420 }
421
422 _ => return Err(Error::BadDer),
423 })
424 }
425}