1 //! Attribute-related definitions as defined in X.501 (and updated by RFC 5280). 2 3 use alloc::vec::Vec; 4 use const_oid::db::{ 5 rfc4519::{COUNTRY_NAME, DOMAIN_COMPONENT, SERIAL_NUMBER}, 6 Database, DB, 7 }; 8 use core::{ 9 fmt::{self, Write}, 10 str::FromStr, 11 }; 12 use der::{ 13 asn1::{ 14 Any, Ia5StringRef, ObjectIdentifier, PrintableStringRef, SetOfVec, TeletexStringRef, 15 Utf8StringRef, 16 }, 17 Decode, Encode, Error, ErrorKind, Sequence, Tag, Tagged, ValueOrd, 18 }; 19 20 /// X.501 `AttributeType` as defined in [RFC 5280 Appendix A.1]. 21 /// 22 /// ```text 23 /// AttributeType ::= OBJECT IDENTIFIER 24 /// ``` 25 /// 26 /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 27 pub type AttributeType = ObjectIdentifier; 28 29 /// X.501 `AttributeValue` as defined in [RFC 5280 Appendix A.1]. 30 /// 31 /// ```text 32 /// AttributeValue ::= ANY 33 /// ``` 34 /// 35 /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 36 pub type AttributeValue = Any; 37 38 /// X.501 `Attribute` as defined in [RFC 5280 Appendix A.1]. 39 /// 40 /// ```text 41 /// Attribute ::= SEQUENCE { 42 /// type AttributeType, 43 /// values SET OF AttributeValue -- at least one value is required 44 /// } 45 /// ``` 46 /// 47 /// Note that [RFC 2986 Section 4] defines a constrained version of this type: 48 /// 49 /// ```text 50 /// Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE { 51 /// type ATTRIBUTE.&id({IOSet}), 52 /// values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) 53 /// } 54 /// ``` 55 /// 56 /// The unconstrained version should be preferred. 57 /// 58 /// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 59 /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 60 #[derive(Clone, Debug, PartialEq, Eq, Sequence, ValueOrd)] 61 #[allow(missing_docs)] 62 pub struct Attribute { 63 pub oid: AttributeType, 64 pub values: SetOfVec<AttributeValue>, 65 } 66 67 /// X.501 `Attributes` as defined in [RFC 2986 Section 4]. 68 /// 69 /// ```text 70 /// Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }} 71 /// ``` 72 /// 73 /// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 74 pub type Attributes = SetOfVec<Attribute>; 75 76 /// X.501 `AttributeTypeAndValue` as defined in [RFC 5280 Appendix A.1]. 77 /// 78 /// ```text 79 /// AttributeTypeAndValue ::= SEQUENCE { 80 /// type AttributeType, 81 /// value AttributeValue 82 /// } 83 /// ``` 84 /// 85 /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 86 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 87 #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd)] 88 #[allow(missing_docs)] 89 pub struct AttributeTypeAndValue { 90 pub oid: AttributeType, 91 pub value: AttributeValue, 92 } 93 94 #[derive(Copy, Clone)] 95 enum Escape { 96 None, 97 Some, 98 Hex(u8), 99 } 100 101 struct Parser { 102 state: Escape, 103 bytes: Vec<u8>, 104 } 105 106 impl Parser { new() -> Self107 pub fn new() -> Self { 108 Self { 109 state: Escape::None, 110 bytes: Vec::new(), 111 } 112 } 113 push(&mut self, c: u8)114 fn push(&mut self, c: u8) { 115 self.state = Escape::None; 116 self.bytes.push(c); 117 } 118 add(&mut self, c: u8) -> Result<(), Error>119 pub fn add(&mut self, c: u8) -> Result<(), Error> { 120 match (self.state, c) { 121 (Escape::Hex(p), b'0'..=b'9') => self.push(p | (c - b'0')), 122 (Escape::Hex(p), b'a'..=b'f') => self.push(p | (c - b'a' + 10)), 123 (Escape::Hex(p), b'A'..=b'F') => self.push(p | (c - b'A' + 10)), 124 125 (Escape::Some, b'0'..=b'9') => self.state = Escape::Hex((c - b'0') << 4), 126 (Escape::Some, b'a'..=b'f') => self.state = Escape::Hex((c - b'a' + 10) << 4), 127 (Escape::Some, b'A'..=b'F') => self.state = Escape::Hex((c - b'A' + 10) << 4), 128 129 (Escape::Some, b' ' | b'"' | b'#' | b'=' | b'\\') => self.push(c), 130 (Escape::Some, b'+' | b',' | b';' | b'<' | b'>') => self.push(c), 131 132 (Escape::None, b'\\') => self.state = Escape::Some, 133 (Escape::None, ..) => self.push(c), 134 135 _ => return Err(ErrorKind::Failed.into()), 136 } 137 138 Ok(()) 139 } 140 as_bytes(&self) -> &[u8]141 pub fn as_bytes(&self) -> &[u8] { 142 &self.bytes 143 } 144 } 145 146 impl AttributeTypeAndValue { 147 /// Parses the hex value in the `OID=#HEX` format. from_hex(oid: ObjectIdentifier, val: &str) -> Result<Self, Error>148 fn from_hex(oid: ObjectIdentifier, val: &str) -> Result<Self, Error> { 149 // Ensure an even number of hex bytes. 150 let mut iter = match val.len() % 2 { 151 0 => [].iter().cloned().chain(val.bytes()), 152 1 => [0u8].iter().cloned().chain(val.bytes()), 153 _ => unreachable!(), 154 }; 155 156 // Decode der bytes from hex. 157 let mut bytes = Vec::with_capacity((val.len() + 1) / 2); 158 159 while let (Some(h), Some(l)) = (iter.next(), iter.next()) { 160 let mut byte = 0u8; 161 162 for (half, shift) in [(h, 4), (l, 0)] { 163 match half { 164 b'0'..=b'9' => byte |= (half - b'0') << shift, 165 b'a'..=b'f' => byte |= (half - b'a' + 10) << shift, 166 b'A'..=b'F' => byte |= (half - b'A' + 10) << shift, 167 _ => return Err(ErrorKind::Failed.into()), 168 } 169 } 170 171 bytes.push(byte); 172 } 173 174 Ok(Self { 175 oid, 176 value: Any::from_der(&bytes)?, 177 }) 178 } 179 180 /// Parses the string value in the `NAME=STRING` format. from_delimited_str(oid: ObjectIdentifier, val: &str) -> Result<Self, Error>181 fn from_delimited_str(oid: ObjectIdentifier, val: &str) -> Result<Self, Error> { 182 // Undo escaping. 183 let mut parser = Parser::new(); 184 for c in val.bytes() { 185 parser.add(c)?; 186 } 187 188 let tag = match oid { 189 COUNTRY_NAME => Tag::PrintableString, 190 DOMAIN_COMPONENT => Tag::Ia5String, 191 // Serial numbers are formatted as Printable String as per RFC 5280 Appendix A.1: 192 // https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 193 SERIAL_NUMBER => Tag::PrintableString, 194 _ => Tag::Utf8String, 195 }; 196 197 Ok(Self { 198 oid, 199 value: Any::new(tag, parser.as_bytes())?, 200 }) 201 } 202 203 /// Converts an AttributeTypeAndValue string into an encoded AttributeTypeAndValue 204 /// 205 /// This function follows the rules in [RFC 4514]. 206 /// 207 /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 208 #[deprecated( 209 since = "0.2.1", 210 note = "use AttributeTypeAndValue::from_str(...)?.to_der()" 211 )] encode_from_string(s: &str) -> Result<Vec<u8>, Error>212 pub fn encode_from_string(s: &str) -> Result<Vec<u8>, Error> { 213 Self::from_str(s)?.to_der() 214 } 215 } 216 217 /// Parse an [`AttributeTypeAndValue`] string. 218 /// 219 /// This function follows the rules in [RFC 4514]. 220 /// 221 /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 222 impl FromStr for AttributeTypeAndValue { 223 type Err = Error; 224 from_str(s: &str) -> der::Result<Self>225 fn from_str(s: &str) -> der::Result<Self> { 226 let idx = s.find('=').ok_or_else(|| Error::from(ErrorKind::Failed))?; 227 let (key, val) = s.split_at(idx); 228 let val = &val[1..]; 229 230 // Either decode or lookup the OID for the given key. 231 let oid = match DB.by_name(key) { 232 Some(oid) => *oid, 233 None => ObjectIdentifier::new(key)?, 234 }; 235 236 // If the value is hex-encoded DER... 237 match val.strip_prefix('#') { 238 Some(val) => Self::from_hex(oid, val), 239 None => Self::from_delimited_str(oid, val), 240 } 241 } 242 } 243 244 /// Serializes the structure according to the rules in [RFC 4514]. 245 /// 246 /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 247 impl fmt::Display for AttributeTypeAndValue { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 249 let val = match self.value.tag() { 250 Tag::PrintableString => PrintableStringRef::try_from(&self.value) 251 .ok() 252 .map(|s| s.as_str()), 253 Tag::Utf8String => Utf8StringRef::try_from(&self.value) 254 .ok() 255 .map(|s| s.as_str()), 256 Tag::Ia5String => Ia5StringRef::try_from(&self.value).ok().map(|s| s.as_str()), 257 Tag::TeletexString => TeletexStringRef::try_from(&self.value) 258 .ok() 259 .map(|s| s.as_str()), 260 _ => None, 261 }; 262 263 if let (Some(key), Some(val)) = (DB.shortest_name_by_oid(&self.oid), val) { 264 write!(f, "{}=", key.to_ascii_uppercase())?; 265 266 let mut iter = val.char_indices().peekable(); 267 while let Some((i, c)) = iter.next() { 268 match c { 269 '#' if i == 0 => write!(f, "\\#")?, 270 ' ' if i == 0 || iter.peek().is_none() => write!(f, "\\ ")?, 271 '"' | '+' | ',' | ';' | '<' | '>' | '\\' => write!(f, "\\{}", c)?, 272 '\x00'..='\x1f' | '\x7f' => write!(f, "\\{:02x}", c as u8)?, 273 _ => f.write_char(c)?, 274 } 275 } 276 } else { 277 let value = self.value.to_der().or(Err(fmt::Error))?; 278 279 write!(f, "{}=#", self.oid)?; 280 for c in value { 281 write!(f, "{:02x}", c)?; 282 } 283 } 284 285 Ok(()) 286 } 287 } 288 289 /// Helper trait to bring shortest name by oid lookups to Database 290 trait ShortestName { shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&str>291 fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&str>; 292 } 293 294 impl<'a> ShortestName for Database<'a> { shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str>295 fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str> { 296 let mut best_match: Option<&'a str> = None; 297 298 for m in self.find_names_for_oid(*oid) { 299 if let Some(previous) = best_match { 300 if m.len() < previous.len() { 301 best_match = Some(m); 302 } 303 } else { 304 best_match = Some(m); 305 } 306 } 307 308 best_match 309 } 310 } 311