x509-cert 0.2.2

Pure Rust implementation of the X.509 Public Key Infrastructure Certificate format as described in RFC 5280
Documentation
//! 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},
    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<AttributeValue>,
}

/// 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<Attribute>;

/// 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<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 from_hex(oid: ObjectIdentifier, val: &str) -> Result<Self, 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);
        }

        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<Self, 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,
            // 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<Vec<u8>, 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<Self> {
        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.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(())
    }
}