1use didbot_identity::did::is_loopback_host;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum CertificateSource {
15 Acme,
17 Local,
20}
21
22impl CertificateSource {
23 pub fn for_zone(zone: &str) -> Self {
25 if is_loopback_host(zone) {
26 Self::Local
27 } else {
28 Self::Acme
29 }
30 }
31
32 pub fn as_str(self) -> &'static str {
35 match self {
36 Self::Acme => "acme",
37 Self::Local => "local",
38 }
39 }
40
41 pub fn refusal_for(self, zone: &str) -> Option<String> {
47 let allowed = Self::for_zone(zone);
48 if allowed == self {
49 return None;
50 }
51 Some(match self {
52 Self::Acme => format!(
53 "--tls acme needs a real zone; --zone {zone} is under .localhost, which no \
54 DNS-01 challenge can complete against. Pass --tls local, which is what a \
55 .localhost zone is served from."
56 ),
57 Self::Local => format!(
58 "--tls local serves a certificate only this machine trusts; --zone {zone} is a \
59 real zone, so pass --tls acme and let a public authority issue for it."
60 ),
61 })
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn each_zone_has_exactly_one_source() {
71 for zone in [
72 "agents.localhost",
73 "localhost",
74 "opzone.localhost",
75 "127.0.0.1",
76 ] {
77 assert_eq!(CertificateSource::for_zone(zone), CertificateSource::Local);
78 assert!(CertificateSource::Local.refusal_for(zone).is_none());
79 assert!(CertificateSource::Acme.refusal_for(zone).is_some());
80 }
81 for zone in ["pds.did.bot", "agents.example.com", "localhost.example"] {
82 assert_eq!(CertificateSource::for_zone(zone), CertificateSource::Acme);
83 assert!(CertificateSource::Acme.refusal_for(zone).is_none());
84 assert!(CertificateSource::Local.refusal_for(zone).is_some());
85 }
86 }
87}