1use super::{Attribute, AttributeValue, Name, Strings, Syntax, prepared, segments};
4use crate::error::{Error, ErrorCode, Located, Result};
5use crate::oid;
6use core::cmp::Ordering;
7use core::ops::Range;
8use structured_asn1::memory::{Memory, OwnedVec};
9use structured_asn1::{Class, Oid, OwnedOid, Parser, Tag, universal};
10
11const FIELD: &str = "name.selector";
12
13#[derive(Debug)]
21pub struct Selector {
22 parts: OwnedVec<OwnedVec<Selected>>,
23}
24
25#[derive(Debug)]
26struct Selected {
27 kind: Kind,
28 bytes: OwnedVec<u8>,
29 value: Value,
30 offset: u64,
31}
32
33#[derive(Debug)]
34enum Kind {
35 Known(Oid<'static>),
36 Numeric(OwnedOid),
37}
38
39impl Kind {
40 fn as_oid(&self) -> Oid<'_> {
41 match self {
42 Self::Known(kind) => *kind,
43 Self::Numeric(kind) => kind.as_oid(),
44 }
45 }
46}
47
48#[derive(Debug)]
49enum Value {
50 Assertion,
51 Ber {
52 tag: Tag,
53 contents: Range<usize>,
54 joined: Option<OwnedVec<u8>>,
55 },
56}
57
58impl Selector {
59 pub fn parse(text: &str, execution: &Memory) -> Result<Self> {
75 let work = (text.len() as u64)
78 .checked_mul(64)
79 .and_then(|n| n.checked_add(1))
80 .ok_or_else(|| {
81 Error::resource(structured_asn1::memory::MemoryError::WorkExceeded, FIELD, 0)
82 })?;
83 execution
84 .charge_work(work)
85 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
86 scan::<true>(text, |_, _, _, _| Ok(()))?;
87 let parser = Parser::ber(execution.clone());
88 let mut parts = execution.vector();
89 let mut current = execution.vector();
90 scan::<true>(text, |label, value, offset, last| {
91 current
92 .try_push(selected(label, value, offset, &parser, execution)?)
93 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
94 if last {
95 parts
96 .try_push(core::mem::replace(&mut current, execution.vector()))
97 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
98 }
99 Ok(())
100 })?;
101 execution
102 .charge_work(parts.len() as u64)
103 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
104 parts.reverse(); Ok(Self { parts })
106 }
107
108 pub fn matches_name(&self, name: &Name<'_>, execution: &Memory) -> Result<bool> {
112 if self.parts.len() != name.parts().len() {
113 return Ok(false);
114 }
115 for (selected, part) in self.parts.iter().zip(name.parts()) {
116 execution
117 .charge_work(1)
118 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
119 let fail = |reason| Error::resource(reason, FIELD, 0);
120 let order = prepared::compare_views(
121 selected.iter().map(|item| project(item, execution)),
122 part.attributes()
123 .iter()
124 .copied()
125 .map(|attribute| prepared::Prepared::of(attribute, execution)),
126 execution,
127 fail,
128 )?;
129 if order != Ordering::Equal {
130 return Ok(false);
131 }
132 }
133 Ok(true)
134 }
135
136 pub fn matches(&self, other: &Self, execution: &Memory) -> Result<bool> {
138 if self.parts.len() != other.parts.len() {
139 return Ok(false);
140 }
141 for (left, right) in self.parts.iter().zip(&other.parts) {
142 execution
143 .charge_work(1)
144 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
145 if prepared::compare_views(
146 left.iter().map(|item| project(item, execution)),
147 right.iter().map(|item| project(item, execution)),
148 execution,
149 |reason| Error::resource(reason, FIELD, 0),
150 )? != Ordering::Equal
151 {
152 return Ok(false);
153 }
154 }
155 Ok(true)
156 }
157}
158
159fn selected(
160 label: &str,
161 value: &str,
162 offset: u64,
163 parser: &Parser,
164 execution: &Memory,
165) -> Result<Selected> {
166 let kind = match descriptor(label) {
167 Some(kind) => Kind::Known(kind),
168 None => Kind::Numeric(OwnedOid::parse(label, execution).map_err(|error| {
169 Error::new(
170 ErrorCode::Encoding(error.code()),
171 FIELD,
172 offset + error.offset(),
173 )
174 })?),
175 };
176 let mut bytes = execution.vector();
177 let value = if let Some(hex) = value.strip_prefix('#') {
178 for pair in hex.as_bytes().as_chunks::<2>().0 {
179 bytes
180 .try_push(
181 (digit(pair[0]).expect("validated hex") << 4)
182 | digit(pair[1]).expect("validated hex"),
183 )
184 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
185 }
186 ber(&bytes, kind.as_oid(), parser, execution).map_err(|error| {
187 Error::new(
188 error.code(),
189 FIELD,
190 offset + label.len() as u64 + 2 + 2 * error.offset(),
191 )
192 })?
193 } else {
194 let mut at = 0;
195 let input = value.as_bytes();
196 while at < input.len() {
197 let byte = if input[at] == b'\\' {
198 at += 1;
199 if let Some(high) = digit(input[at]) {
200 at += 1;
201 (high << 4) | digit(input[at]).expect("validated escape")
202 } else {
203 input[at]
204 }
205 } else {
206 input[at]
207 };
208 bytes
209 .try_push(byte)
210 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
211 at += 1;
212 }
213 Value::Assertion
214 };
215 Ok(Selected {
216 kind,
217 bytes,
218 value,
219 offset,
220 })
221}
222
223fn ber(bytes: &[u8], kind: Oid<'_>, parser: &Parser, execution: &Memory) -> Result<Value> {
224 let mut reader = parser.read(bytes, 0).field(FIELD)?;
225 let element = reader
226 .next_element()
227 .field(FIELD)?
228 .ok_or_else(|| malformed(0))?;
229 if !reader.is_empty() {
230 return Err(malformed(element.span().end()));
231 }
232 super::walk_der(&reader, &element, FIELD, Strings::AsWritten)?;
237 let tag = element.tag();
238 let start = element.as_bytes().len()
239 - element.contents().len()
240 - if element.is_indefinite() { 2 } else { 0 };
241 let contents = start..start + element.contents().len();
242 execution
243 .charge_work(
244 (kind.as_bytes().len() as u64)
245 .checked_mul(40)
246 .ok_or_else(|| {
247 Error::resource(structured_asn1::memory::MemoryError::WorkExceeded, FIELD, 0)
248 })?,
249 )
250 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
251 let logical_encoding = match tag.number() {
252 Some(number) => super::AttributeValue {
253 tag: Tag::primitive(number),
254 contents: &[],
255 der: &[],
256 offset: 0,
257 depth: 0,
258 }
259 .encoding(),
260 None => super::Encoding::Other,
261 };
262 let joined = if tag.class() == Class::Universal
263 && tag.is_constructed()
264 && tag.number().is_some_and(super::segmented_universal_type)
265 && tag.number() != Some(universal::BIT_STRING)
266 && super::permits(kind, logical_encoding)
267 {
268 let mut joined = execution.vector();
269 segments::walk_in(
270 &reader,
271 &element,
272 FIELD,
273 false,
274 Some(&mut joined),
275 execution,
276 )?;
277 Some(joined)
278 } else {
279 None
280 };
281 Ok(Value::Ber {
282 tag,
283 contents,
284 joined,
285 })
286}
287
288fn project<'a>(item: &'a Selected, execution: &Memory) -> Result<prepared::Prepared<'a>> {
289 let kind = item.kind.as_oid();
290 let value = match &item.value {
291 Value::Assertion => {
292 execution
293 .charge_work((kind.as_bytes().len() as u64) * 40)
294 .map_err(|reason| Error::resource(reason, FIELD, item.offset))?;
295 let tag = match super::syntax_of(kind) {
296 Some(Syntax::Directory) => universal::UTF8_STRING,
297 Some(Syntax::Printable) => universal::PRINTABLE_STRING,
298 Some(Syntax::Ia5) => universal::IA5_STRING,
299 None => {
300 return Err(Error::new(
301 ErrorCode::UnsupportedNameSyntax,
302 FIELD,
303 item.offset,
304 ));
305 }
306 };
307 AttributeValue {
308 tag: Tag::primitive(tag),
309 contents: &item.bytes,
310 der: &[],
311 offset: item.offset,
312 depth: 0,
313 }
314 }
315 Value::Ber {
316 tag,
317 contents,
318 joined,
319 } => AttributeValue {
320 tag: if joined.is_some() {
321 Tag::primitive(tag.number().expect("universal string"))
322 } else {
323 *tag
324 },
325 contents: joined.as_deref().unwrap_or(&item.bytes[contents.clone()]),
326 der: &item.bytes,
327 offset: item.offset,
328 depth: 0,
329 },
330 };
331 let attribute = Attribute { kind, value };
332 if matches!(item.value, Value::Assertion) {
333 let work = (kind.as_bytes().len() as u64)
334 .checked_mul(64)
335 .and_then(|kinds| {
336 (value.contents.len() as u64)
337 .checked_mul(2)
338 .and_then(|contents| kinds.checked_add(contents))
339 })
340 .and_then(|work| work.checked_add(1))
341 .ok_or_else(|| {
342 Error::resource(
343 structured_asn1::memory::MemoryError::WorkExceeded,
344 FIELD,
345 item.offset,
346 )
347 })?;
348 execution
349 .charge_work(work)
350 .map_err(|reason| Error::resource(reason, FIELD, item.offset))?;
351 if !super::well_formed(&attribute) {
352 return Err(Error::new(ErrorCode::MalformedValue, FIELD, item.offset));
353 }
354 }
355 let prepared = prepared::Prepared::of(attribute, execution)?;
356 if matches!(item.value, Value::Assertion) && !prepared.is_prepared() {
357 return Err(Error::new(
358 ErrorCode::UnsupportedNameSyntax,
359 FIELD,
360 item.offset,
361 ));
362 }
363 Ok(prepared)
364}
365
366fn malformed(offset: u64) -> Error {
367 Error::new(ErrorCode::MalformedValue, FIELD, offset)
368}
369
370fn digit(byte: u8) -> Option<u8> {
371 match byte {
372 b'0'..=b'9' => Some(byte - b'0'),
373 b'a'..=b'f' => Some(byte - b'a' + 10),
374 b'A'..=b'F' => Some(byte - b'A' + 10),
375 _ => None,
376 }
377}
378
379fn label_fits<const REGISTERED: bool>(label: &str, offset: u64) -> Result<()> {
380 let Some(first) = label.as_bytes().first() else {
381 return Err(malformed(offset));
382 };
383 if first.is_ascii_digit() {
384 let mut count = 0;
385 for component in label.split('.') {
386 if component.is_empty()
387 || !component.bytes().all(|b| b.is_ascii_digit())
388 || (component.len() > 1 && component.starts_with('0'))
389 {
390 return Err(malformed(offset));
391 }
392 count += 1;
393 }
394 if count < 2 {
395 return Err(malformed(offset));
396 }
397 } else if !first.is_ascii_alphabetic()
398 || !label
399 .bytes()
400 .all(|b| b.is_ascii_alphanumeric() || b == b'-')
401 {
402 return Err(malformed(offset));
403 } else if REGISTERED && descriptor(label).is_none() {
404 return Err(Error::new(ErrorCode::UnsupportedNameSyntax, FIELD, offset));
405 }
406 Ok(())
407}
408
409fn scan<const REGISTERED: bool>(
412 text: &str,
413 mut visit: impl FnMut(&str, &str, u64, bool) -> Result<()>,
414) -> Result<()> {
415 if text.is_empty() {
416 return Ok(());
417 }
418 let bytes = text.as_bytes();
419 let mut at = 0;
420 loop {
421 let start = at;
422 while at < bytes.len() && bytes[at] != b'=' {
423 at += 1;
424 }
425 if at == bytes.len() {
426 return Err(malformed(start as u64));
427 }
428 let label = &text[start..at];
429 label_fits::<REGISTERED>(label, start as u64)?;
430 at += 1;
431 let value = at;
432 if bytes.get(at) == Some(&b'#') {
433 at += 1;
434 let hex = at;
435 while at < bytes.len() && !matches!(bytes[at], b',' | b'+') {
436 if digit(bytes[at]).is_none() {
437 return Err(malformed(at as u64));
438 }
439 at += 1;
440 }
441 if at == hex || !(at - hex).is_multiple_of(2) {
442 return Err(malformed(hex as u64));
443 }
444 } else {
445 let mut last_space = false;
446 while at < bytes.len() && !matches!(bytes[at], b',' | b'+') {
447 let byte = bytes[at];
448 if byte == b'\\' {
449 at += 1;
450 let Some(&escaped) = bytes.get(at) else {
451 return Err(malformed(at as u64));
452 };
453 if digit(escaped).is_some() {
454 at += 1;
455 if bytes.get(at).copied().and_then(digit).is_none() {
456 return Err(malformed(at as u64));
457 }
458 } else if !matches!(
459 escaped,
460 b' ' | b'"' | b'#' | b'+' | b',' | b';' | b'<' | b'=' | b'>' | b'\\'
461 ) {
462 return Err(malformed(at as u64));
463 }
464 last_space = false;
465 } else {
466 if matches!(byte, 0 | b'"' | b';' | b'<' | b'>')
467 || (at == value && matches!(byte, b' ' | b'#'))
468 {
469 return Err(malformed(at as u64));
470 }
471 last_space = byte == b' ';
472 }
473 at += 1;
474 }
475 if last_space {
476 return Err(malformed((at - 1) as u64));
477 }
478 }
479 let last = bytes.get(at) != Some(&b'+');
480 visit(label, &text[value..at], start as u64, last)?;
481 if at == bytes.len() {
482 return Ok(());
483 }
484 at += 1;
485 if at == bytes.len() {
486 return Err(malformed(at as u64));
487 }
488 }
489}
490
491pub(crate) fn syntax_fits(text: &str) -> bool {
495 scan::<false>(text, |_, _, _, _| Ok(())).is_ok()
496}
497
498pub(crate) fn attribute_type_syntax_fits(text: &str) -> bool {
500 label_fits::<false>(text, 0).is_ok()
501}
502
503const DESCRIPTORS: &[(&str, Oid<'static>)] = &[
504 ("CN", oid::COMMON_NAME),
505 ("L", oid::LOCALITY),
506 ("ST", oid::STATE_OR_PROVINCE),
507 ("O", oid::ORGANIZATION),
508 ("OU", oid::ORGANIZATIONAL_UNIT),
509 ("C", oid::COUNTRY),
510 ("STREET", oid::STREET_ADDRESS),
511 ("DC", oid::DOMAIN_COMPONENT),
512 ("UID", oid::USER_ID),
513 ("emailAddress", oid::EMAIL_ADDRESS),
514 ("serialNumber", oid::SERIAL_NUMBER),
515 ("surname", oid::SURNAME),
516 ("givenName", oid::GIVEN_NAME),
517 ("initials", oid::INITIALS),
518 ("generationQualifier", oid::GENERATION_QUALIFIER),
519 ("title", oid::TITLE),
520 ("dnQualifier", oid::DN_QUALIFIER),
521 ("pseudonym", oid::PSEUDONYM),
522 ("organizationIdentifier", oid::ORGANIZATION_IDENTIFIER),
523];
524
525pub(super) const REGISTERED_DESCRIPTORS: usize = 18;
530
531pub(super) fn registered_name(kind: Oid<'_>) -> Option<&'static str> {
532 DESCRIPTORS[..REGISTERED_DESCRIPTORS]
533 .iter()
534 .find(|(_, oid)| *oid == kind)
535 .map(|(name, _)| *name)
536}
537
538fn descriptor(text: &str) -> Option<Oid<'static>> {
539 if text.eq_ignore_ascii_case("sn") {
540 return Some(oid::SURNAME);
541 }
542 DESCRIPTORS
543 .iter()
544 .find(|(name, _)| name.eq_ignore_ascii_case(text))
545 .map(|(_, oid)| *oid)
546}