mirror of https://github.com/breard-r/acmed.git
				
				
			
				 19 changed files with 449 additions and 139 deletions
			
			
		- 
					7CHANGELOG.md
 - 
					2README.md
 - 
					29acme_common/src/crypto/openssl_certificate.rs
 - 
					6acme_common/src/error.rs
 - 
					1acme_common/src/tests.rs
 - 
					84acme_common/src/tests/certificate.rs
 - 
					26acmed/config/default_hooks.toml
 - 
					36acmed/src/acme_proto.rs
 - 
					2acmed/src/acme_proto/structs.rs
 - 
					2acmed/src/acme_proto/structs/authorization.rs
 - 
					29acmed/src/acme_proto/structs/order.rs
 - 
					66acmed/src/certificate.rs
 - 
					54acmed/src/config.rs
 - 
					5acmed/src/hooks.rs
 - 
					149acmed/src/identifier.rs
 - 
					1acmed/src/main.rs
 - 
					2acmed/src/main_event_loop.rs
 - 
					8man/en/acmed.8
 - 
					79man/en/acmed.toml.5
 
@ -1,2 +1,3 @@ | 
				
			|||
mod certificate;
 | 
				
			|||
mod crypto_keys;
 | 
				
			|||
mod idna;
 | 
				
			|||
@ -0,0 +1,84 @@ | 
				
			|||
use crate::crypto::X509Certificate;
 | 
				
			|||
use std::collections::HashSet;
 | 
				
			|||
use std::iter::FromIterator;
 | 
				
			|||
 | 
				
			|||
const CERTIFICATE_P256_DOMAINS_PEM: &str = r#"-----BEGIN CERTIFICATE-----
 | 
				
			|||
MIICtDCCAZygAwIBAgIIf5BEPlNrrYkwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
 | 
				
			|||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAyZDE2ODgwHhcNMjAwODI1MTMwMzE3
 | 
				
			|||
WhcNMjUwODI1MTMwMzE3WjAYMRYwFAYDVQQDEw1sb2NhbC53aGF0LnRmMFkwEwYH
 | 
				
			|||
KoZIzj0CAQYIKoZIzj0DAQcDQgAE0c/unUqpoOMxxc8e1pkpPQTSsh2irQruOJgd
 | 
				
			|||
ITN9WLC4mzFSJ/ad64TFi4HsCFNd7mv/QRH6rW1s3LbocEvBuqOBvDCBuTAOBgNV
 | 
				
			|||
HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud
 | 
				
			|||
EwEB/wQCMAAwHQYDVR0OBBYEFGjf1TWIZyE+QP9SGkBN6dfviIsaMB8GA1UdIwQY
 | 
				
			|||
MBaAFLgD5DMU2ijpIxlxaAv82sQvb5ofMDoGA1UdEQQzMDGCDWxvY2FsLndoYXQu
 | 
				
			|||
dGaCDzEubG9jYWwud2hhdC50ZoIPMi5sb2NhbC53aGF0LnRmMA0GCSqGSIb3DQEB
 | 
				
			|||
CwUAA4IBAQDREOAU2JwHfSPGt4SYlQ3OmFl4HHI2f+XyNE/09uZVteM0aChkntgX
 | 
				
			|||
rAZltuAAX+coSlgv3a04hJBqioDG1R9MFtf4LZBhfkgZwbzucMt8Ga3QL3XFXOkn
 | 
				
			|||
FlOwb/ZEIjFsBFQWt1ZSA85WxIVkGsgMfQeGpu/p8gEmJAE5l0qHEVFP9cYNsIqg
 | 
				
			|||
wsUGwZzPZFLsBXurM2cEA7cTt2HryVXlQWl8QI5YFpIpa43itYaerfMldfIfNdJ9
 | 
				
			|||
8GLZPEfJb6t/UYYexXEkpQY9wGZkaTWvYeItuC0YlPY9RUCAl48Q85Yjf37Wbm5z
 | 
				
			|||
f810HGl+/c6ttyoHKmLfY/GcX07AUcLc
 | 
				
			|||
-----END CERTIFICATE-----"#;
 | 
				
			|||
const CERTIFICATE_P256_IP_PEM: &str = r#"-----BEGIN CERTIFICATE-----
 | 
				
			|||
MIICkTCCAXmgAwIBAgIIMW1X7DjQOFgwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
 | 
				
			|||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxYWM3MzcwHhcNMjAwODI1MTQzMjQw
 | 
				
			|||
WhcNMjUwODI1MTQzMjQwWjAOMQwwCgYDVQQDEwM6OjEwWTATBgcqhkjOPQIBBggq
 | 
				
			|||
hkjOPQMBBwNCAASF+MvxX7GBAVe3McuAc+0emdFpBfAQG4mt9j8417qT76qHHyJ6
 | 
				
			|||
oIHRNXAUxh4J78ihrvyph8TvqND73Nxk8Jj9o4GjMIGgMA4GA1UdDwEB/wQEAwIF
 | 
				
			|||
oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAd
 | 
				
			|||
BgNVHQ4EFgQU5R7EGzjpZqrs2o/ZwuBqNHlMB2AwHwYDVR0jBBgwFoAUhEUnWREW
 | 
				
			|||
GoAScr1wv/aXHTGOVoswIQYDVR0RBBowGIcQAAAAAAAAAAAAAAAAAAAAAYcEfwAA
 | 
				
			|||
ATANBgkqhkiG9w0BAQsFAAOCAQEAS8oRpjGakUU+KRtXCGoVlXgKYFe3u/G2aFMF
 | 
				
			|||
soApjvwd3L1W9b3bsT4FquF7F5qB6TGBwiXoNBoDAeVhRcUsHbmN8GZRUaq2TEsm
 | 
				
			|||
MwpPr8L4rqeRIuxY85AqmbGfMuFUie6r4FbwelnBniO0eMQkTW/XY41rbhGZ+lmj
 | 
				
			|||
DTQy08oj0892py2U/YbkL3JnCBwBba//f/Ji7nnSKdJl4Yd1iguA0nbdElcWaKk3
 | 
				
			|||
ij3t17FSNeI5uMOI3TRBr4k4bu3ZMnuN2DYFPnL6GiSEhyNrxaiac8xKuOXBICmJ
 | 
				
			|||
oyO7pZVvc5cDcP/USPcWJYcnR9gvuL8snQdFpWND8H19eZ+i0g==
 | 
				
			|||
-----END CERTIFICATE-----"#;
 | 
				
			|||
const CERTIFICATE_P256_DOMAINS_IP_PEM: &str = r#"-----BEGIN CERTIFICATE-----
 | 
				
			|||
MIICzDCCAbSgAwIBAgIIff0SyxJBhtMwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
 | 
				
			|||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxYWM3MzcwHhcNMjAwODI1MTQzNjE1
 | 
				
			|||
WhcNMjUwODI1MTQzNjE1WjAYMRYwFAYDVQQDEw1sb2NhbC53aGF0LnRmMFkwEwYH
 | 
				
			|||
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7Jp4AmF0TTcYfUy4TtZhN4bXn4DXWnqF0I6i
 | 
				
			|||
Yvz4kc0r2L01nrUrICg2bmCFM7BU9pr9fcCDodH3ZuhlRqBAf6OB1DCB0TAOBgNV
 | 
				
			|||
HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud
 | 
				
			|||
EwEB/wQCMAAwHQYDVR0OBBYEFHV0lnh55aQGfljcsjNkzZa4lTG6MB8GA1UdIwQY
 | 
				
			|||
MBaAFIRFJ1kRFhqAEnK9cL/2lx0xjlaLMFIGA1UdEQRLMEmCDWxvY2FsLndoYXQu
 | 
				
			|||
dGaCDzEubG9jYWwud2hhdC50ZoIPMi5sb2NhbC53aGF0LnRmhwR/AAABhxAAAAAA
 | 
				
			|||
AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQC3VmoTlrrTCWCd4eUB4RSB
 | 
				
			|||
+080uco6Jl7VMqcY5F+eG1S7p4Kqz6kc1wiiKB8ILA94hdP1qTbfphdGllYiEvbs
 | 
				
			|||
urj0x62cm5URahEDx4xn+dQkmh4XiiZgZVw2ccphjqJqJa28GsuR2zAxSkKMDnB7
 | 
				
			|||
eX1G4/Av0XE7RqJ3Frq8qa5EjjLJTw0iEaWS5NGtZxMqWEIetCgb0IDZNxNvbeAv
 | 
				
			|||
mmH6qnF3xQPx5FkwP/Yw4d9T4KhSHNf2/tImIlbuk3SEsOglGbKNY1juor8uw+J2
 | 
				
			|||
5XsUZxD5QiDbCFd3dGmH58XmkiQHXs8hhIbhu9ZLgp+fNv0enVMHTTI1gGpZ5MPm
 | 
				
			|||
-----END CERTIFICATE-----"#;
 | 
				
			|||
 | 
				
			|||
#[test]
 | 
				
			|||
fn test_san_domains() {
 | 
				
			|||
    let san = vec!["local.what.tf", "1.local.what.tf", "2.local.what.tf"];
 | 
				
			|||
    let san = HashSet::from_iter(san.iter().map(|v| v.to_string()));
 | 
				
			|||
    let crt = X509Certificate::from_pem(CERTIFICATE_P256_DOMAINS_PEM.as_bytes()).unwrap();
 | 
				
			|||
    assert_eq!(crt.subject_alt_names(), san);
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
#[test]
 | 
				
			|||
fn test_san_ip() {
 | 
				
			|||
    let san = vec!["127.0.0.1", "::1"];
 | 
				
			|||
    let san = HashSet::from_iter(san.iter().map(|v| v.to_string()));
 | 
				
			|||
    let crt = X509Certificate::from_pem(CERTIFICATE_P256_IP_PEM.as_bytes()).unwrap();
 | 
				
			|||
    assert_eq!(crt.subject_alt_names(), san);
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
#[test]
 | 
				
			|||
fn test_san_domains_and_ip() {
 | 
				
			|||
    let san = vec![
 | 
				
			|||
        "127.0.0.1",
 | 
				
			|||
        "::1",
 | 
				
			|||
        "local.what.tf",
 | 
				
			|||
        "1.local.what.tf",
 | 
				
			|||
        "2.local.what.tf",
 | 
				
			|||
    ];
 | 
				
			|||
    let san = HashSet::from_iter(san.iter().map(|v| v.to_string()));
 | 
				
			|||
    let crt = X509Certificate::from_pem(CERTIFICATE_P256_DOMAINS_IP_PEM.as_bytes()).unwrap();
 | 
				
			|||
    assert_eq!(crt.subject_alt_names(), san);
 | 
				
			|||
}
 | 
				
			|||
@ -0,0 +1,149 @@ | 
				
			|||
use crate::acme_proto::Challenge;
 | 
				
			|||
use acme_common::error::Error;
 | 
				
			|||
use acme_common::to_idna;
 | 
				
			|||
use serde::{Deserialize, Serialize};
 | 
				
			|||
use std::collections::HashMap;
 | 
				
			|||
use std::fmt;
 | 
				
			|||
use std::net::IpAddr;
 | 
				
			|||
use std::str::FromStr;
 | 
				
			|||
 | 
				
			|||
// RFC 3596, section 2.5
 | 
				
			|||
fn u8_to_nibbles_string(value: &u8) -> String {
 | 
				
			|||
    let bytes = value.to_ne_bytes();
 | 
				
			|||
    let first = bytes[0] & 0x0f;
 | 
				
			|||
    let second = (bytes[0] >> 4) & 0x0f;
 | 
				
			|||
    format!("{:x}.{:x}", first, second)
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
 | 
				
			|||
pub enum IdentifierType {
 | 
				
			|||
    #[serde(rename = "dns")]
 | 
				
			|||
    Dns,
 | 
				
			|||
    #[serde(rename = "ip")]
 | 
				
			|||
    Ip,
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
impl IdentifierType {
 | 
				
			|||
    pub fn supported_challenges(&self) -> Vec<Challenge> {
 | 
				
			|||
        match self {
 | 
				
			|||
            IdentifierType::Dns => vec![Challenge::Http01, Challenge::Dns01, Challenge::TlsAlpn01],
 | 
				
			|||
            IdentifierType::Ip => vec![Challenge::Http01, Challenge::TlsAlpn01],
 | 
				
			|||
        }
 | 
				
			|||
    }
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
impl fmt::Display for IdentifierType {
 | 
				
			|||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			|||
        let name = match self {
 | 
				
			|||
            IdentifierType::Dns => "dns",
 | 
				
			|||
            IdentifierType::Ip => "ip",
 | 
				
			|||
        };
 | 
				
			|||
        write!(f, "{}", name)
 | 
				
			|||
    }
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
#[derive(Clone, Debug)]
 | 
				
			|||
pub struct Identifier {
 | 
				
			|||
    pub id_type: IdentifierType,
 | 
				
			|||
    pub value: String,
 | 
				
			|||
    pub challenge: Challenge,
 | 
				
			|||
    pub env: HashMap<String, String>,
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
impl Identifier {
 | 
				
			|||
    pub fn new(
 | 
				
			|||
        id_type: IdentifierType,
 | 
				
			|||
        value: &str,
 | 
				
			|||
        challenge: &str,
 | 
				
			|||
        env: &HashMap<String, String>,
 | 
				
			|||
    ) -> Result<Self, Error> {
 | 
				
			|||
        let value = match id_type {
 | 
				
			|||
            IdentifierType::Dns => to_idna(value)?,
 | 
				
			|||
            IdentifierType::Ip => IpAddr::from_str(value)?.to_string(),
 | 
				
			|||
        };
 | 
				
			|||
        let challenge = Challenge::from_str(challenge)?;
 | 
				
			|||
        if !id_type.supported_challenges().contains(&challenge) {
 | 
				
			|||
            let msg = format!(
 | 
				
			|||
                "Challenge {} cannot be used with identifier of type {}",
 | 
				
			|||
                challenge, id_type
 | 
				
			|||
            );
 | 
				
			|||
            return Err(msg.into());
 | 
				
			|||
        }
 | 
				
			|||
        Ok(Identifier {
 | 
				
			|||
            id_type,
 | 
				
			|||
            value,
 | 
				
			|||
            challenge,
 | 
				
			|||
            env: env.clone(),
 | 
				
			|||
        })
 | 
				
			|||
    }
 | 
				
			|||
 | 
				
			|||
    pub fn get_tls_alpn_name(&self) -> Result<String, Error> {
 | 
				
			|||
        match &self.id_type {
 | 
				
			|||
            IdentifierType::Dns => Ok(self.value.to_owned()),
 | 
				
			|||
            IdentifierType::Ip => match IpAddr::from_str(&self.value)? {
 | 
				
			|||
                IpAddr::V4(ip) => {
 | 
				
			|||
                    let dn = ip
 | 
				
			|||
                        .octets()
 | 
				
			|||
                        .iter()
 | 
				
			|||
                        .rev()
 | 
				
			|||
                        .map(|v| v.to_string())
 | 
				
			|||
                        .collect::<Vec<String>>()
 | 
				
			|||
                        .join(".");
 | 
				
			|||
                    let dn = format!("{}.in-addr.arpa", dn);
 | 
				
			|||
                    Ok(dn)
 | 
				
			|||
                }
 | 
				
			|||
                IpAddr::V6(ip) => {
 | 
				
			|||
                    let dn = ip
 | 
				
			|||
                        .octets()
 | 
				
			|||
                        .iter()
 | 
				
			|||
                        .rev()
 | 
				
			|||
                        .map(u8_to_nibbles_string)
 | 
				
			|||
                        .collect::<Vec<String>>()
 | 
				
			|||
                        .join(".");
 | 
				
			|||
                    let dn = format!("{}.ip6.arpa", dn);
 | 
				
			|||
                    Ok(dn)
 | 
				
			|||
                }
 | 
				
			|||
            },
 | 
				
			|||
        }
 | 
				
			|||
    }
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
impl fmt::Display for Identifier {
 | 
				
			|||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			|||
        write!(f, "{}: {} ({})", self.id_type, self.value, self.challenge)
 | 
				
			|||
    }
 | 
				
			|||
}
 | 
				
			|||
 | 
				
			|||
#[cfg(test)]
 | 
				
			|||
mod tests {
 | 
				
			|||
    use super::*;
 | 
				
			|||
    use std::collections::HashMap;
 | 
				
			|||
 | 
				
			|||
    #[test]
 | 
				
			|||
    fn test_ipv4_tls_alpn_name() {
 | 
				
			|||
        let env = HashMap::new();
 | 
				
			|||
        let id = Identifier::new(IdentifierType::Ip, "203.0.113.1", "http-01", &env).unwrap();
 | 
				
			|||
        assert_eq!(&id.get_tls_alpn_name().unwrap(), "1.113.0.203.in-addr.arpa");
 | 
				
			|||
    }
 | 
				
			|||
 | 
				
			|||
    #[test]
 | 
				
			|||
    fn test_ipv6_tls_alpn_name() {
 | 
				
			|||
        let env = HashMap::new();
 | 
				
			|||
        let id = Identifier::new(IdentifierType::Ip, "2001:db8::1", "http-01", &env).unwrap();
 | 
				
			|||
        assert_eq!(
 | 
				
			|||
            &id.get_tls_alpn_name().unwrap(),
 | 
				
			|||
            "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa"
 | 
				
			|||
        );
 | 
				
			|||
        let id = Identifier::new(
 | 
				
			|||
            IdentifierType::Ip,
 | 
				
			|||
            "4321:0:1:2:3:4:567:89ab",
 | 
				
			|||
            "http-01",
 | 
				
			|||
            &env,
 | 
				
			|||
        )
 | 
				
			|||
        .unwrap();
 | 
				
			|||
        assert_eq!(
 | 
				
			|||
            &id.get_tls_alpn_name().unwrap(),
 | 
				
			|||
            "b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa"
 | 
				
			|||
        );
 | 
				
			|||
    }
 | 
				
			|||
}
 | 
				
			|||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue