blob: 2b4336ad0c5e37ef2258dc75c4bd42f2fa4f2548 [file] [log] [blame] [edit]
//! 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};
use core::fmt::{self, Write};
use const_oid::db::DB;
use der::asn1::{AnyRef, ObjectIdentifier, SetOfVec};
use der::{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<'a> = AnyRef<'a>;
/// 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<'a> {
pub oid: AttributeType,
pub values: SetOfVec<AttributeValue<'a>>,
}
impl<'a> TryFrom<&'a [u8]> for Attribute<'a> {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
Self::from_der(bytes)
}
}
/// 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<'a> = SetOfVec<Attribute<'a>>;
/// 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
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd)]
#[allow(missing_docs)]
pub struct AttributeTypeAndValue<'a> {
pub oid: AttributeType,
pub value: AnyRef<'a>,
}
#[derive(Copy, Clone)]
enum Escape {
None,
Some,
Hex(u8),
}
struct Parser {
state: Escape,
bytes: Vec<u8>,
}
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 encode_hex(oid: ObjectIdentifier, val: &str) -> Result<Vec<u8>, Error> {
// 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);
}
// Serialize.
let value = AnyRef::from_der(&bytes)?;
let atv = AttributeTypeAndValue { oid, value };
atv.to_vec()
}
/// Parses the string value in the `NAME=STRING` format.
fn encode_str(oid: ObjectIdentifier, val: &str) -> Result<Vec<u8>, Error> {
// 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,
_ => Tag::Utf8String,
};
// Serialize.
let value = AnyRef::new(tag, parser.as_bytes())?;
let atv = AttributeTypeAndValue { oid, value };
atv.to_vec()
}
/// 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
pub fn encode_from_string(s: &str) -> Result<Vec<u8>, Error> {
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::encode_hex(oid, val),
None => Self::encode_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 => self.value.printable_string().ok().map(|s| s.as_str()),
Tag::Utf8String => self.value.utf8_string().ok().map(|s| s.as_str()),
Tag::Ia5String => self.value.ia5_string().ok().map(|s| s.as_str()),
Tag::TeletexString => self.value.teletex_string().ok().map(|s| s.as_str()),
_ => None,
};
if let (Some(key), Some(val)) = (DB.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_vec().or(Err(fmt::Error))?;
write!(f, "{}=#", self.oid)?;
for c in value {
write!(f, "{:02x}", c)?;
}
}
Ok(())
}
}