//! Attribute-related definitions as defined in X.501 (and updated by RFC 5280). use alloc::vec::Vec; use const_oid::db::{ rfc4519::{COUNTRY_NAME, DOMAIN_COMPONENT, SERIAL_NUMBER}, Database, DB, }; use core::{ fmt::{self, Write}, str::FromStr, }; use der::{ asn1::{ Any, Ia5StringRef, ObjectIdentifier, PrintableStringRef, SetOfVec, TeletexStringRef, Utf8StringRef, }, Decode, Encode, Error, ErrorKind, Sequence, Tag, Tagged, ValueOrd, }; /// X.501 `AttributeType` as defined in [RFC 5280 Appendix A.1]. /// /// ```text /// AttributeType ::= OBJECT IDENTIFIER /// ``` /// /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 pub type AttributeType = ObjectIdentifier; /// X.501 `AttributeValue` as defined in [RFC 5280 Appendix A.1]. /// /// ```text /// AttributeValue ::= ANY /// ``` /// /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 pub type AttributeValue = Any; /// X.501 `Attribute` as defined in [RFC 5280 Appendix A.1]. /// /// ```text /// Attribute ::= SEQUENCE { /// type AttributeType, /// values SET OF AttributeValue -- at least one value is required /// } /// ``` /// /// Note that [RFC 2986 Section 4] defines a constrained version of this type: /// /// ```text /// Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE { /// type ATTRIBUTE.&id({IOSet}), /// values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) /// } /// ``` /// /// The unconstrained version should be preferred. /// /// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 #[derive(Clone, Debug, PartialEq, Eq, Sequence, ValueOrd)] #[allow(missing_docs)] pub struct Attribute { pub oid: AttributeType, pub values: SetOfVec, } /// X.501 `Attributes` as defined in [RFC 2986 Section 4]. /// /// ```text /// Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }} /// ``` /// /// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 pub type Attributes = SetOfVec; /// X.501 `AttributeTypeAndValue` as defined in [RFC 5280 Appendix A.1]. /// /// ```text /// AttributeTypeAndValue ::= SEQUENCE { /// type AttributeType, /// value AttributeValue /// } /// ``` /// /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd)] #[allow(missing_docs)] pub struct AttributeTypeAndValue { pub oid: AttributeType, pub value: AttributeValue, } #[derive(Copy, Clone)] enum Escape { None, Some, Hex(u8), } struct Parser { state: Escape, bytes: Vec, } impl Parser { pub fn new() -> Self { Self { state: Escape::None, bytes: Vec::new(), } } fn push(&mut self, c: u8) { self.state = Escape::None; self.bytes.push(c); } pub fn add(&mut self, c: u8) -> Result<(), Error> { match (self.state, c) { (Escape::Hex(p), b'0'..=b'9') => self.push(p | (c - b'0')), (Escape::Hex(p), b'a'..=b'f') => self.push(p | (c - b'a' + 10)), (Escape::Hex(p), b'A'..=b'F') => self.push(p | (c - b'A' + 10)), (Escape::Some, b'0'..=b'9') => self.state = Escape::Hex((c - b'0') << 4), (Escape::Some, b'a'..=b'f') => self.state = Escape::Hex((c - b'a' + 10) << 4), (Escape::Some, b'A'..=b'F') => self.state = Escape::Hex((c - b'A' + 10) << 4), (Escape::Some, b' ' | b'"' | b'#' | b'=' | b'\\') => self.push(c), (Escape::Some, b'+' | b',' | b';' | b'<' | b'>') => self.push(c), (Escape::None, b'\\') => self.state = Escape::Some, (Escape::None, ..) => self.push(c), _ => return Err(ErrorKind::Failed.into()), } Ok(()) } pub fn as_bytes(&self) -> &[u8] { &self.bytes } } impl AttributeTypeAndValue { /// Parses the hex value in the `OID=#HEX` format. fn from_hex(oid: ObjectIdentifier, val: &str) -> Result { // Ensure an even number of hex bytes. let mut iter = match val.len() % 2 { 0 => [].iter().cloned().chain(val.bytes()), 1 => [0u8].iter().cloned().chain(val.bytes()), _ => unreachable!(), }; // Decode der bytes from hex. let mut bytes = Vec::with_capacity((val.len() + 1) / 2); while let (Some(h), Some(l)) = (iter.next(), iter.next()) { let mut byte = 0u8; for (half, shift) in [(h, 4), (l, 0)] { match half { b'0'..=b'9' => byte |= (half - b'0') << shift, b'a'..=b'f' => byte |= (half - b'a' + 10) << shift, b'A'..=b'F' => byte |= (half - b'A' + 10) << shift, _ => return Err(ErrorKind::Failed.into()), } } bytes.push(byte); } Ok(Self { oid, value: Any::from_der(&bytes)?, }) } /// Parses the string value in the `NAME=STRING` format. fn from_delimited_str(oid: ObjectIdentifier, val: &str) -> Result { // Undo escaping. let mut parser = Parser::new(); for c in val.bytes() { parser.add(c)?; } let tag = match oid { COUNTRY_NAME => Tag::PrintableString, DOMAIN_COMPONENT => Tag::Ia5String, // Serial numbers are formatted as Printable String as per RFC 5280 Appendix A.1: // https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 SERIAL_NUMBER => Tag::PrintableString, _ => Tag::Utf8String, }; Ok(Self { oid, value: Any::new(tag, parser.as_bytes())?, }) } /// Converts an AttributeTypeAndValue string into an encoded AttributeTypeAndValue /// /// This function follows the rules in [RFC 4514]. /// /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 #[deprecated( since = "0.2.1", note = "use AttributeTypeAndValue::from_str(...)?.to_der()" )] pub fn encode_from_string(s: &str) -> Result, Error> { Self::from_str(s)?.to_der() } } /// Parse an [`AttributeTypeAndValue`] string. /// /// This function follows the rules in [RFC 4514]. /// /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 impl FromStr for AttributeTypeAndValue { type Err = Error; fn from_str(s: &str) -> der::Result { let idx = s.find('=').ok_or_else(|| Error::from(ErrorKind::Failed))?; let (key, val) = s.split_at(idx); let val = &val[1..]; // Either decode or lookup the OID for the given key. let oid = match DB.by_name(key) { Some(oid) => *oid, None => ObjectIdentifier::new(key)?, }; // If the value is hex-encoded DER... match val.strip_prefix('#') { Some(val) => Self::from_hex(oid, val), None => Self::from_delimited_str(oid, val), } } } /// Serializes the structure according to the rules in [RFC 4514]. /// /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 impl fmt::Display for AttributeTypeAndValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let val = match self.value.tag() { Tag::PrintableString => PrintableStringRef::try_from(&self.value) .ok() .map(|s| s.as_str()), Tag::Utf8String => Utf8StringRef::try_from(&self.value) .ok() .map(|s| s.as_str()), Tag::Ia5String => Ia5StringRef::try_from(&self.value).ok().map(|s| s.as_str()), Tag::TeletexString => TeletexStringRef::try_from(&self.value) .ok() .map(|s| s.as_str()), _ => None, }; if let (Some(key), Some(val)) = (DB.shortest_name_by_oid(&self.oid), val) { write!(f, "{}=", key.to_ascii_uppercase())?; let mut iter = val.char_indices().peekable(); while let Some((i, c)) = iter.next() { match c { '#' if i == 0 => write!(f, "\\#")?, ' ' if i == 0 || iter.peek().is_none() => write!(f, "\\ ")?, '"' | '+' | ',' | ';' | '<' | '>' | '\\' => write!(f, "\\{}", c)?, '\x00'..='\x1f' | '\x7f' => write!(f, "\\{:02x}", c as u8)?, _ => f.write_char(c)?, } } } else { let value = self.value.to_der().or(Err(fmt::Error))?; write!(f, "{}=#", self.oid)?; for c in value { write!(f, "{:02x}", c)?; } } Ok(()) } } /// Helper trait to bring shortest name by oid lookups to Database trait ShortestName { fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&str>; } impl<'a> ShortestName for Database<'a> { fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str> { let mut best_match: Option<&'a str> = None; for m in self.find_names_for_oid(*oid) { if let Some(previous) = best_match { if m.len() < previous.len() { best_match = Some(m); } } else { best_match = Some(m); } } best_match } }