1use crate::error::Error;
2use rustls_pki_types::ServerName;
3use serde::{Deserialize, Serialize};
4use std::{fmt::Display, net::IpAddr, str::FromStr};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum SubjectName {
8 DnsName(String),
9 WildcardDnsName(String),
10 IPAddress(IpAddr),
11}
12
13impl SubjectName {
14 pub fn test(&self, name: &str) -> bool {
15 match self {
16 Self::DnsName(n) => n.eq_ignore_ascii_case(name),
17 Self::WildcardDnsName(n) => n.eq_ignore_ascii_case(
18 name.trim_start_matches(|c| c != '.')
19 .trim_start_matches('.'),
20 ),
21 Self::IPAddress(addr) => match addr {
22 IpAddr::V4(addr) => name.eq_ignore_ascii_case(&addr.to_string()),
23 IpAddr::V6(addr) => name.eq_ignore_ascii_case(&addr.to_string()),
24 },
25 }
26 }
27}
28
29impl Serialize for SubjectName {
30 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
31 where
32 S: serde::Serializer,
33 {
34 serializer.serialize_str(&self.to_string())
35 }
36}
37
38impl<'de> Deserialize<'de> for SubjectName {
39 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
40 where
41 D: serde::Deserializer<'de>,
42 {
43 let s = String::deserialize(deserializer)?;
44 Self::from_str(&s).map_err(serde::de::Error::custom)
45 }
46}
47
48impl Display for SubjectName {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 Self::DnsName(name) => write!(f, "{}", name),
52 Self::WildcardDnsName(name) => write!(f, "*.{}", name),
53 Self::IPAddress(addr) => write!(f, "{}", addr),
54 }
55 }
56}
57
58impl FromStr for SubjectName {
59 type Err = Error;
60
61 fn from_str(s: &str) -> Result<Self, Self::Err> {
62 if ServerName::try_from(s.replace('*', "a")).is_err() {
63 return Err(Error::InvalidSubjectName {
64 name: s.to_string(),
65 });
66 }
67 let wildcard = s.starts_with("*.");
68 let name = s.trim_start_matches("*.");
69 let ipaddr: Result<IpAddr, _> = name.parse();
70 match ipaddr {
71 Ok(addr) => Ok(Self::IPAddress(addr)),
72 _ => {
73 if wildcard {
74 Ok(Self::WildcardDnsName(name.to_string()))
75 } else {
76 Ok(Self::DnsName(name.to_string()))
77 }
78 }
79 }
80 }
81}
82
83#[cfg(test)]
84mod test {
85 use super::*;
86
87 #[test]
88 fn test_subject_name() {
89 assert_eq!(
90 SubjectName::from_str("*.example.com").unwrap(),
91 SubjectName::WildcardDnsName("example.com".to_owned())
92 );
93 assert_eq!(
94 SubjectName::from_str("example.com").unwrap(),
95 SubjectName::DnsName("example.com".to_owned())
96 );
97 assert_eq!(
98 SubjectName::from_str("127.0.0.1").unwrap(),
99 SubjectName::IPAddress(IpAddr::V4([127, 0, 0, 1].into()))
100 )
101 }
102
103 #[test]
104 fn test_subject_name_test() {
105 assert!(SubjectName::from_str("*.example.com")
106 .unwrap()
107 .test("app.example.com"));
108 assert!(SubjectName::from_str("example.com")
109 .unwrap()
110 .test("example.com"));
111 assert!(SubjectName::from_str("127.0.0.1")
112 .unwrap()
113 .test("127.0.0.1"));
114 }
115}