Skip to content

Commit

Permalink
verification: add RFC822Name (pyca#10487)
Browse files Browse the repository at this point in the history
* verification: add RFC822Name

Signed-off-by: William Woodruff <[email protected]>

* verification: clippy

Signed-off-by: William Woodruff <[email protected]>

* verification: clippage

Signed-off-by: William Woodruff <[email protected]>

* verification: feedback

Signed-off-by: William Woodruff <[email protected]>

---------

Signed-off-by: William Woodruff <[email protected]>
  • Loading branch information
woodruffw authored Feb 26, 2024
1 parent 0a1098f commit bcaf375
Showing 1 changed file with 108 additions and 1 deletion.
109 changes: 108 additions & 1 deletion src/rust/cryptography-x509-verification/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
use std::net::IpAddr;
use std::str::FromStr;

use asn1::IA5String;

// RFC 2822 3.2.4
static ATEXT_CHARS: &str = "!#$%&'*+-/=?^_`{|}~";

/// A `DNSName` is an `asn1::IA5String` with additional invariant preservations
/// per [RFC 5280 4.2.1.6], which in turn uses the preferred name syntax defined
/// in [RFC 1034 3.5] and amended in [RFC 1123 2.1].
Expand Down Expand Up @@ -298,9 +303,54 @@ impl IPConstraint {
}
}

/// An `RFC822Name` represents an email address, as defined in [RFC 822 6.1]
/// and as amended by [RFC 2821 4.1.2]. In particular, it represents the `Mailbox`
/// rule from RFC 2821's grammar.
///
/// This type does not currently support the quoted local-part form; email
/// addresses that use this form will be rejected.
///
/// [RFC 822 6.1]: https://datatracker.ietf.org/doc/html/rfc822#section-6.1
/// [RFC 2821 4.1.2]: https://datatracker.ietf.org/doc/html/rfc2821#section-4.1.2
pub struct RFC822Name<'a> {
pub mailbox: IA5String<'a>,
pub domain: DNSName<'a>,
}

impl<'a> RFC822Name<'a> {
pub fn new(value: &'a str) -> Option<Self> {
// Mailbox = Local-part "@" Domain
// Both must be present.
let (local_part, domain) = value.split_once('@')?;
let local_part = IA5String::new(local_part)?;

// Local-part = Dot-string / Quoted-string
// NOTE(ww): We do not support the Quoted-string form, for now.
//
// Dot-string: Atom *("." Atom)
// Atom = 1*atext
//
// NOTE(ww): `atext`'s production is in RFC 2822 3.2.4.
for component in local_part.as_str().split('.') {
if component.is_empty()
|| !component
.chars()
.all(|c| c.is_ascii_alphanumeric() || ATEXT_CHARS.contains(c))
{
return None;
}
}

Some(Self {
mailbox: local_part,
domain: DNSName::new(domain)?,
})
}
}

#[cfg(test)]
mod tests {
use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint};
use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint, RFC822Name};

#[test]
fn test_dnsname_debug_trait() {
Expand Down Expand Up @@ -587,4 +637,61 @@ mod tests {
assert!(!ipv6_128.matches(&IPAddress::from_str("2600::ff00:dede").unwrap()));
assert!(!ipv6_128.matches(&IPAddress::from_str("2600:db8::ff00:0").unwrap()));
}

#[test]
fn test_rfc822name() {
for bad_case in &[
"",
// Missing local-part.
"@example.com",
" @example.com",
" @example.com",
// Missing domain cases.
"foo",
"foo@",
"foo@ ",
"foo@ ",
// Invalid domains.
"foo@!!!",
"foo@white space",
"foo@🙈",
// Invalid local part (empty mailbox sections).
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
// Invalid local part (@ in mailbox).
"lol@[email protected]",
"lol\\@[email protected]",
"[email protected]@example.com",
"@@example.com",
// Invalid local part (invalid characters).
"lol\"[email protected]",
"lol;[email protected]",
"🙈@example.com",
// Intentionally unsupported quoted local parts.
"\"validbutunsupported\"@example.com",
] {
assert!(RFC822Name::new(bad_case).is_none());
}

// Each good case is (address, (mailbox, domain)).
for (address, (mailbox, domain)) in &[
// Normal mailboxes.
("[email protected]", ("foo", "example.com")),
("[email protected]", ("foo.bar", "example.com")),
("[email protected]", ("foo.bar.baz", "example.com")),
("[email protected]", ("1.2.3.4.5", "example.com")),
// Mailboxes with special but valid characters.
("{legal}@example.com", ("{legal}", "example.com")),
("{&*.legal}@example.com", ("{&*.legal}", "example.com")),
("``````````@example.com", ("``````````", "example.com")),
("[email protected]", ("hello?", "sub.example.com")),
] {
let parsed = RFC822Name::new(&address).unwrap();
assert_eq!(&parsed.mailbox.as_str(), mailbox);
assert_eq!(&parsed.domain.as_str(), domain);
}
}
}

0 comments on commit bcaf375

Please sign in to comment.