Rodolphe Bréard 2 years ago
parent
commit
1afc6dc27e
  1. 30
      acme_common/build.rs
  2. 78
      acme_common/src/crypto.rs
  3. 122
      acme_common/src/crypto/jws_signature_algorithm.rs
  4. 168
      acme_common/src/crypto/key_type.rs
  5. 340
      acme_common/src/crypto/openssl_certificate.rs
  6. 44
      acme_common/src/crypto/openssl_hash.rs
  7. 566
      acme_common/src/crypto/openssl_keys.rs
  8. 38
      acme_common/src/crypto/openssl_subject_attribute.rs
  9. 38
      acme_common/src/crypto/openssl_version.rs
  10. 130
      acme_common/src/error.rs
  11. 88
      acme_common/src/lib.rs
  12. 112
      acme_common/src/logs.rs
  13. 106
      acme_common/src/tests/certificate.rs
  14. 492
      acme_common/src/tests/crypto_keys.rs
  15. 622
      acme_common/src/tests/hash.rs
  16. 42
      acme_common/src/tests/idna.rs
  17. 42
      acme_common/src/tests/jws_signature_algorithm.rs
  18. 146
      acmed/build.rs
  19. 518
      acmed/src/account.rs
  20. 130
      acmed/src/account/contact.rs
  21. 280
      acmed/src/account/storage.rs
  22. 384
      acmed/src/acme_proto.rs
  23. 246
      acmed/src/acme_proto/account.rs
  24. 24
      acmed/src/acme_proto/certificate.rs
  25. 178
      acmed/src/acme_proto/http.rs
  26. 20
      acmed/src/acme_proto/structs.rs
  27. 272
      acmed/src/acme_proto/structs/account.rs
  28. 440
      acmed/src/acme_proto/structs/authorization.rs
  29. 178
      acmed/src/acme_proto/structs/directory.rs
  30. 210
      acmed/src/acme_proto/structs/error.rs
  31. 160
      acmed/src/acme_proto/structs/order.rs
  32. 342
      acmed/src/certificate.rs
  33. 1216
      acmed/src/config.rs
  34. 58
      acmed/src/duration.rs
  35. 198
      acmed/src/endpoint.rs
  36. 294
      acmed/src/hooks.rs
  37. 378
      acmed/src/http.rs
  38. 220
      acmed/src/identifier.rs
  39. 310
      acmed/src/jws.rs
  40. 8
      acmed/src/logs.rs
  41. 218
      acmed/src/main.rs
  42. 358
      acmed/src/main_event_loop.rs
  43. 418
      acmed/src/storage.rs
  44. 102
      acmed/src/template.rs
  45. 1
      rustfmt.toml
  46. 44
      tacd/build.rs
  47. 148
      tacd/src/main.rs
  48. 66
      tacd/src/openssl_server.rs

30
acme_common/build.rs

@ -1,23 +1,23 @@
use std::env; use std::env;
macro_rules! set_rustc_env_var { macro_rules! set_rustc_env_var {
($name: expr, $value: expr) => {{
println!("cargo:rustc-env={}={}", $name, $value);
}};
($name: expr, $value: expr) => {{
println!("cargo:rustc-env={}={}", $name, $value);
}};
} }
#[allow(clippy::unusual_byte_groupings)] #[allow(clippy::unusual_byte_groupings)]
fn main() { fn main() {
if let Ok(v) = env::var("DEP_OPENSSL_VERSION_NUMBER") {
let version = u64::from_str_radix(&v, 16).unwrap();
// OpenSSL 1.1.1
if version >= 0x1_01_01_00_0 {
println!("cargo:rustc-cfg=ed25519");
println!("cargo:rustc-cfg=ed448");
}
set_rustc_env_var!("ACMED_TLS_LIB_NAME", "OpenSSL");
}
if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() {
set_rustc_env_var!("ACMED_TLS_LIB_NAME", "LibreSSL");
}
if let Ok(v) = env::var("DEP_OPENSSL_VERSION_NUMBER") {
let version = u64::from_str_radix(&v, 16).unwrap();
// OpenSSL 1.1.1
if version >= 0x1_01_01_00_0 {
println!("cargo:rustc-cfg=ed25519");
println!("cargo:rustc-cfg=ed448");
}
set_rustc_env_var!("ACMED_TLS_LIB_NAME", "OpenSSL");
}
if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() {
set_rustc_env_var!("ACMED_TLS_LIB_NAME", "LibreSSL");
}
} }

78
acme_common/src/crypto.rs

@ -24,59 +24,59 @@ pub const CRT_NB_DAYS_VALIDITY: u32 = 7;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum BaseSubjectAttribute { pub enum BaseSubjectAttribute {
CountryName,
GenerationQualifier,
GivenName,
Initials,
LocalityName,
Name,
OrganizationName,
OrganizationalUnitName,
Pkcs9EmailAddress,
PostalAddress,
PostalCode,
StateOrProvinceName,
Street,
Surname,
Title,
CountryName,
GenerationQualifier,
GivenName,
Initials,
LocalityName,
Name,
OrganizationName,
OrganizationalUnitName,
Pkcs9EmailAddress,
PostalAddress,
PostalCode,
StateOrProvinceName,
Street,
Surname,
Title,
} }
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum BaseHashFunction { pub enum BaseHashFunction {
Sha256,
Sha384,
Sha512,
Sha256,
Sha384,
Sha512,
} }
impl BaseHashFunction { impl BaseHashFunction {
pub fn list_possible_values() -> Vec<&'static str> {
vec!["sha256", "sha384", "sha512"]
}
pub fn list_possible_values() -> Vec<&'static str> {
vec!["sha256", "sha384", "sha512"]
}
} }
impl FromStr for BaseHashFunction { impl FromStr for BaseHashFunction {
type Err = Error;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let s = s.to_lowercase().replace(['-', '_'], "");
match s.as_str() {
"sha256" => Ok(BaseHashFunction::Sha256),
"sha384" => Ok(BaseHashFunction::Sha384),
"sha512" => Ok(BaseHashFunction::Sha512),
_ => Err(format!("{}: unknown hash function.", s).into()),
}
}
fn from_str(s: &str) -> Result<Self, Error> {
let s = s.to_lowercase().replace(['-', '_'], "");
match s.as_str() {
"sha256" => Ok(BaseHashFunction::Sha256),
"sha384" => Ok(BaseHashFunction::Sha384),
"sha512" => Ok(BaseHashFunction::Sha512),
_ => Err(format!("{}: unknown hash function.", s).into()),
}
}
} }
impl fmt::Display for BaseHashFunction { impl fmt::Display for BaseHashFunction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
BaseHashFunction::Sha256 => "sha256",
BaseHashFunction::Sha384 => "sha384",
BaseHashFunction::Sha512 => "sha512",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
BaseHashFunction::Sha256 => "sha256",
BaseHashFunction::Sha384 => "sha384",
BaseHashFunction::Sha512 => "sha512",
};
write!(f, "{}", s)
}
} }
pub use jws_signature_algorithm::JwsSignatureAlgorithm; pub use jws_signature_algorithm::JwsSignatureAlgorithm;

122
acme_common/src/crypto/jws_signature_algorithm.rs

@ -4,78 +4,78 @@ use std::str::FromStr;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum JwsSignatureAlgorithm { pub enum JwsSignatureAlgorithm {
Hs256,
Hs384,
Hs512,
Rs256,
Es256,
Es384,
Es512,
#[cfg(ed25519)]
Ed25519,
#[cfg(ed448)]
Ed448,
Hs256,
Hs384,
Hs512,
Rs256,
Es256,
Es384,
Es512,
#[cfg(ed25519)]
Ed25519,
#[cfg(ed448)]
Ed448,
} }
impl FromStr for JwsSignatureAlgorithm { impl FromStr for JwsSignatureAlgorithm {
type Err = Error;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"hs256" => Ok(JwsSignatureAlgorithm::Hs256),
"hs384" => Ok(JwsSignatureAlgorithm::Hs384),
"hs512" => Ok(JwsSignatureAlgorithm::Hs512),
"rs256" => Ok(JwsSignatureAlgorithm::Rs256),
"es256" => Ok(JwsSignatureAlgorithm::Es256),
"es384" => Ok(JwsSignatureAlgorithm::Es384),
"es512" => Ok(JwsSignatureAlgorithm::Es512),
#[cfg(ed25519)]
"ed25519" => Ok(JwsSignatureAlgorithm::Ed25519),
#[cfg(ed448)]
"ed448" => Ok(JwsSignatureAlgorithm::Ed448),
_ => Err(format!("{}: unknown algorithm.", s).into()),
}
}
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"hs256" => Ok(JwsSignatureAlgorithm::Hs256),
"hs384" => Ok(JwsSignatureAlgorithm::Hs384),
"hs512" => Ok(JwsSignatureAlgorithm::Hs512),
"rs256" => Ok(JwsSignatureAlgorithm::Rs256),
"es256" => Ok(JwsSignatureAlgorithm::Es256),
"es384" => Ok(JwsSignatureAlgorithm::Es384),
"es512" => Ok(JwsSignatureAlgorithm::Es512),
#[cfg(ed25519)]
"ed25519" => Ok(JwsSignatureAlgorithm::Ed25519),
#[cfg(ed448)]
"ed448" => Ok(JwsSignatureAlgorithm::Ed448),
_ => Err(format!("{}: unknown algorithm.", s).into()),
}
}
} }
impl fmt::Display for JwsSignatureAlgorithm { impl fmt::Display for JwsSignatureAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
JwsSignatureAlgorithm::Hs256 => "HS256",
JwsSignatureAlgorithm::Hs384 => "HS384",
JwsSignatureAlgorithm::Hs512 => "HS512",
JwsSignatureAlgorithm::Rs256 => "RS256",
JwsSignatureAlgorithm::Es256 => "ES256",
JwsSignatureAlgorithm::Es384 => "ES384",
JwsSignatureAlgorithm::Es512 => "ES512",
#[cfg(ed25519)]
JwsSignatureAlgorithm::Ed25519 => "Ed25519",
#[cfg(ed448)]
JwsSignatureAlgorithm::Ed448 => "Ed448",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
JwsSignatureAlgorithm::Hs256 => "HS256",
JwsSignatureAlgorithm::Hs384 => "HS384",
JwsSignatureAlgorithm::Hs512 => "HS512",
JwsSignatureAlgorithm::Rs256 => "RS256",
JwsSignatureAlgorithm::Es256 => "ES256",
JwsSignatureAlgorithm::Es384 => "ES384",
JwsSignatureAlgorithm::Es512 => "ES512",
#[cfg(ed25519)]
JwsSignatureAlgorithm::Ed25519 => "Ed25519",
#[cfg(ed448)]
JwsSignatureAlgorithm::Ed448 => "Ed448",
};
write!(f, "{}", s)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::JwsSignatureAlgorithm;
use std::str::FromStr;
use super::JwsSignatureAlgorithm;
use std::str::FromStr;
#[test]
fn test_es256_from_str() {
let variants = ["ES256", "Es256", "es256"];
for v in variants.iter() {
let a = JwsSignatureAlgorithm::from_str(v);
assert!(a.is_ok());
let a = a.unwrap();
assert_eq!(a, JwsSignatureAlgorithm::Es256);
}
}
#[test]
fn test_es256_from_str() {
let variants = ["ES256", "Es256", "es256"];
for v in variants.iter() {
let a = JwsSignatureAlgorithm::from_str(v);
assert!(a.is_ok());
let a = a.unwrap();
assert_eq!(a, JwsSignatureAlgorithm::Es256);
}
}
#[test]
fn test_es256_to_str() {
let a = JwsSignatureAlgorithm::Es256;
assert_eq!(a.to_string().as_str(), "ES256");
}
#[test]
fn test_es256_to_str() {
let a = JwsSignatureAlgorithm::Es256;
assert_eq!(a.to_string().as_str(), "ES256");
}
} }

168
acme_common/src/crypto/key_type.rs

@ -5,100 +5,100 @@ use std::str::FromStr;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum KeyType { pub enum KeyType {
Rsa2048,
Rsa4096,
EcdsaP256,
EcdsaP384,
EcdsaP521,
#[cfg(ed25519)]
Ed25519,
#[cfg(ed448)]
Ed448,
Rsa2048,
Rsa4096,
EcdsaP256,
EcdsaP384,
EcdsaP521,
#[cfg(ed25519)]
Ed25519,
#[cfg(ed448)]
Ed448,
} }
impl KeyType { impl KeyType {
pub fn get_default_signature_alg(&self) -> JwsSignatureAlgorithm {
match self {
KeyType::Rsa2048 | KeyType::Rsa4096 => JwsSignatureAlgorithm::Rs256,
KeyType::EcdsaP256 => JwsSignatureAlgorithm::Es256,
KeyType::EcdsaP384 => JwsSignatureAlgorithm::Es384,
KeyType::EcdsaP521 => JwsSignatureAlgorithm::Es512,
#[cfg(ed25519)]
KeyType::Ed25519 => JwsSignatureAlgorithm::Ed25519,
#[cfg(ed448)]
KeyType::Ed448 => JwsSignatureAlgorithm::Ed448,
}
}
pub fn get_default_signature_alg(&self) -> JwsSignatureAlgorithm {
match self {
KeyType::Rsa2048 | KeyType::Rsa4096 => JwsSignatureAlgorithm::Rs256,
KeyType::EcdsaP256 => JwsSignatureAlgorithm::Es256,
KeyType::EcdsaP384 => JwsSignatureAlgorithm::Es384,
KeyType::EcdsaP521 => JwsSignatureAlgorithm::Es512,
#[cfg(ed25519)]
KeyType::Ed25519 => JwsSignatureAlgorithm::Ed25519,
#[cfg(ed448)]
KeyType::Ed448 => JwsSignatureAlgorithm::Ed448,
}
}
pub fn check_alg_compatibility(&self, alg: &JwsSignatureAlgorithm) -> Result<(), Error> {
let ok = match self {
KeyType::Rsa2048 | KeyType::Rsa4096 => *alg == JwsSignatureAlgorithm::Rs256,
KeyType::EcdsaP256 | KeyType::EcdsaP384 | KeyType::EcdsaP521 => {
*alg == self.get_default_signature_alg()
}
#[cfg(ed25519)]
KeyType::Ed25519 => *alg == self.get_default_signature_alg(),
#[cfg(ed448)]
KeyType::Ed448 => *alg == self.get_default_signature_alg(),
};
if ok {
Ok(())
} else {
let err_msg = format!(
"incompatible signature algorithm: {} cannot be used with an {} key",
alg, self
);
Err(err_msg.into())
}
}
pub fn check_alg_compatibility(&self, alg: &JwsSignatureAlgorithm) -> Result<(), Error> {
let ok = match self {
KeyType::Rsa2048 | KeyType::Rsa4096 => *alg == JwsSignatureAlgorithm::Rs256,
KeyType::EcdsaP256 | KeyType::EcdsaP384 | KeyType::EcdsaP521 => {
*alg == self.get_default_signature_alg()
}
#[cfg(ed25519)]
KeyType::Ed25519 => *alg == self.get_default_signature_alg(),
#[cfg(ed448)]
KeyType::Ed448 => *alg == self.get_default_signature_alg(),
};
if ok {
Ok(())
} else {
let err_msg = format!(
"incompatible signature algorithm: {} cannot be used with an {} key",
alg, self
);
Err(err_msg.into())
}
}
pub fn list_possible_values() -> Vec<&'static str> {
vec![
"rsa2048",
"rsa4096",
"ecdsa-p256",
"ecdsa-p384",
"ecdsa-p521",
#[cfg(ed25519)]
"ed25519",
#[cfg(ed448)]
"ed448",
]
}
pub fn list_possible_values() -> Vec<&'static str> {
vec![
"rsa2048",
"rsa4096",
"ecdsa-p256",
"ecdsa-p384",
"ecdsa-p521",
#[cfg(ed25519)]
"ed25519",
#[cfg(ed448)]
"ed448",
]
}
} }
impl FromStr for KeyType { impl FromStr for KeyType {
type Err = Error;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().replace('-', "_").as_str() {
"rsa2048" => Ok(KeyType::Rsa2048),
"rsa4096" => Ok(KeyType::Rsa4096),
"ecdsa_p256" => Ok(KeyType::EcdsaP256),
"ecdsa_p384" => Ok(KeyType::EcdsaP384),
"ecdsa_p521" => Ok(KeyType::EcdsaP521),
#[cfg(ed25519)]
"ed25519" => Ok(KeyType::Ed25519),
#[cfg(ed448)]
"ed448" => Ok(KeyType::Ed448),
_ => Err(format!("{}: unknown algorithm", s).into()),
}
}
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().replace('-', "_").as_str() {
"rsa2048" => Ok(KeyType::Rsa2048),
"rsa4096" => Ok(KeyType::Rsa4096),
"ecdsa_p256" => Ok(KeyType::EcdsaP256),
"ecdsa_p384" => Ok(KeyType::EcdsaP384),
"ecdsa_p521" => Ok(KeyType::EcdsaP521),
#[cfg(ed25519)]
"ed25519" => Ok(KeyType::Ed25519),
#[cfg(ed448)]
"ed448" => Ok(KeyType::Ed448),
_ => Err(format!("{}: unknown algorithm", s).into()),
}
}
} }
impl fmt::Display for KeyType { impl fmt::Display for KeyType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
KeyType::Rsa2048 => "rsa2048",
KeyType::Rsa4096 => "rsa4096",
KeyType::EcdsaP256 => "ecdsa-p256",
KeyType::EcdsaP384 => "ecdsa-p384",
KeyType::EcdsaP521 => "ecdsa-p521",
#[cfg(ed25519)]
KeyType::Ed25519 => "ed25519",
#[cfg(ed448)]
KeyType::Ed448 => "ed448",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
KeyType::Rsa2048 => "rsa2048",
KeyType::Rsa4096 => "rsa4096",
KeyType::EcdsaP256 => "ecdsa-p256",
KeyType::EcdsaP384 => "ecdsa-p384",
KeyType::EcdsaP521 => "ecdsa-p521",
#[cfg(ed25519)]
KeyType::Ed25519 => "ed25519",
#[cfg(ed448)]
KeyType::Ed448 => "ed448",
};
write!(f, "{}", s)
}
} }

340
acme_common/src/crypto/openssl_certificate.rs

@ -13,190 +13,190 @@ use std::net::IpAddr;
use std::time::Duration; use std::time::Duration;
fn get_digest(digest: HashFunction, key_pair: &KeyPair) -> MessageDigest { fn get_digest(digest: HashFunction, key_pair: &KeyPair) -> MessageDigest {
#[cfg(not(any(ed25519, ed448)))]
let digest = digest.native_digest();
let _ = key_pair;
#[cfg(any(ed25519, ed448))]
let digest = match key_pair.key_type {
#[cfg(ed25519)]
KeyType::Ed25519 => MessageDigest::null(),
#[cfg(ed448)]
KeyType::Ed448 => MessageDigest::null(),
_ => digest.native_digest(),
};
digest
#[cfg(not(any(ed25519, ed448)))]
let digest = digest.native_digest();
let _ = key_pair;
#[cfg(any(ed25519, ed448))]
let digest = match key_pair.key_type {
#[cfg(ed25519)]
KeyType::Ed25519 => MessageDigest::null(),
#[cfg(ed448)]
KeyType::Ed448 => MessageDigest::null(),
_ => digest.native_digest(),
};
digest
} }
pub struct Csr { pub struct Csr {
inner_csr: X509Req,
inner_csr: X509Req,
} }
impl Csr { impl Csr {
pub fn new(
key_pair: &KeyPair,
digest: HashFunction,
domains: &[String],
ips: &[String],
subject_attributes: &HashMap<SubjectAttribute, String>,
) -> Result<Self, Error> {
let mut builder = X509ReqBuilder::new()?;
builder.set_pubkey(&key_pair.inner_key)?;
if !subject_attributes.is_empty() {
let mut snb = X509NameBuilder::new()?;
for (sattr, val) in subject_attributes.iter() {
snb.append_entry_by_nid(sattr.get_nid(), val)?;
}
let name = snb.build();
builder.set_subject_name(&name)?;
}
let ctx = builder.x509v3_context(None);
let mut san = SubjectAlternativeName::new();
for dns in domains.iter() {
san.dns(dns);
}
for ip in ips.iter() {
san.ip(ip);
}
let san = san.build(&ctx)?;
let mut ext_stack = Stack::new()?;
ext_stack.push(san)?;
builder.add_extensions(&ext_stack)?;
let digest = get_digest(digest, key_pair);
builder.sign(&key_pair.inner_key, digest)?;
Ok(Csr {
inner_csr: builder.build(),
})
}
pub fn to_der_base64(&self) -> Result<String, Error> {
let csr = self.inner_csr.to_der()?;
let csr = b64_encode(&csr);
Ok(csr)
}
pub fn to_pem(&self) -> Result<String, Error> {
let csr = self.inner_csr.to_pem()?;
Ok(String::from_utf8(csr)?)
}
pub fn new(
key_pair: &KeyPair,
digest: HashFunction,
domains: &[String],
ips: &[String],
subject_attributes: &HashMap<SubjectAttribute, String>,
) -> Result<Self, Error> {
let mut builder = X509ReqBuilder::new()?;
builder.set_pubkey(&key_pair.inner_key)?;
if !subject_attributes.is_empty() {
let mut snb = X509NameBuilder::new()?;
for (sattr, val) in subject_attributes.iter() {
snb.append_entry_by_nid(sattr.get_nid(), val)?;
}
let name = snb.build();
builder.set_subject_name(&name)?;
}
let ctx = builder.x509v3_context(None);
let mut san = SubjectAlternativeName::new();
for dns in domains.iter() {
san.dns(dns);
}
for ip in ips.iter() {
san.ip(ip);
}
let san = san.build(&ctx)?;
let mut ext_stack = Stack::new()?;
ext_stack.push(san)?;
builder.add_extensions(&ext_stack)?;
let digest = get_digest(digest, key_pair);
builder.sign(&key_pair.inner_key, digest)?;
Ok(Csr {
inner_csr: builder.build(),
})
}
pub fn to_der_base64(&self) -> Result<String, Error> {
let csr = self.inner_csr.to_der()?;
let csr = b64_encode(&csr);
Ok(csr)
}
pub fn to_pem(&self) -> Result<String, Error> {
let csr = self.inner_csr.to_pem()?;
Ok(String::from_utf8(csr)?)
}
} }
pub struct X509Certificate { pub struct X509Certificate {
pub inner_cert: X509,
pub inner_cert: X509,
} }
impl X509Certificate { impl X509Certificate {
pub fn from_pem(pem_data: &[u8]) -> Result<Self, Error> {
Ok(X509Certificate {
inner_cert: X509::from_pem(pem_data)?,
})
}
pub fn from_pem_native(pem_data: &[u8]) -> Result<native_tls::Certificate, Error> {
Ok(native_tls::Certificate::from_pem(pem_data)?)
}
pub fn from_acme_ext(
domain: &str,
acme_ext: &str,
key_type: KeyType,
digest: HashFunction,
) -> Result<(KeyPair, Self), Error> {
let key_pair = gen_keypair(key_type)?;
let digest = get_digest(digest, &key_pair);
let inner_cert = gen_certificate(domain, &key_pair, &digest, acme_ext)?;
let cert = X509Certificate { inner_cert };
Ok((key_pair, cert))
}
pub fn expires_in(&self) -> Result<Duration, Error> {
let now = Asn1Time::days_from_now(0)?;
let not_after = self.inner_cert.not_after();
let diff = now.diff(not_after)?;
let nb_secs = diff.days * 24 * 60 * 60 + diff.secs;
let nb_secs = if nb_secs > 0 { nb_secs as u64 } else { 0 };
Ok(Duration::from_secs(nb_secs))
}
pub fn subject_alt_names(&self) -> HashSet<String> {
match self.inner_cert.subject_alt_names() {
Some(s) => s
.iter()
.filter(|v| v.dnsname().is_some() || v.ipaddress().is_some())
.map(|v| match v.dnsname() {
Some(d) => d.to_string(),
None => match v.ipaddress() {
Some(i) => match i.len() {
4 => {
let ipv4: [u8; 4] = [i[0], i[1], i[2], i[3]];
IpAddr::from(ipv4).to_string()
}
16 => {
let ipv6: [u8; 16] = [
i[0], i[1], i[2], i[3], i[4], i[5], i[6], i[7], i[8], i[9],
i[10], i[11], i[12], i[13], i[14], i[15],
];
IpAddr::from(ipv6).to_string()
}
_ => String::new(),
},
None => String::new(),
},
})
.collect(),
None => HashSet::new(),
}
}
pub fn from_pem(pem_data: &[u8]) -> Result<Self, Error> {
Ok(X509Certificate {
inner_cert: X509::from_pem(pem_data)?,
})
}
pub fn from_pem_native(pem_data: &[u8]) -> Result<native_tls::Certificate, Error> {
Ok(native_tls::Certificate::from_pem(pem_data)?)
}
pub fn from_acme_ext(
domain: &str,
acme_ext: &str,
key_type: KeyType,
digest: HashFunction,
) -> Result<(KeyPair, Self), Error> {
let key_pair = gen_keypair(key_type)?;
let digest = get_digest(digest, &key_pair);
let inner_cert = gen_certificate(domain, &key_pair, &digest, acme_ext)?;
let cert = X509Certificate { inner_cert };
Ok((key_pair, cert))
}
pub fn expires_in(&self) -> Result<Duration, Error> {
let now = Asn1Time::days_from_now(0)?;
let not_after = self.inner_cert.not_after();
let diff = now.diff(not_after)?;
let nb_secs = diff.days * 24 * 60 * 60 + diff.secs;
let nb_secs = if nb_secs > 0 { nb_secs as u64 } else { 0 };
Ok(Duration::from_secs(nb_secs))
}
pub fn subject_alt_names(&self) -> HashSet<String> {
match self.inner_cert.subject_alt_names() {
Some(s) => s
.iter()
.filter(|v| v.dnsname().is_some() || v.ipaddress().is_some())
.map(|v| match v.dnsname() {
Some(d) => d.to_string(),
None => match v.ipaddress() {
Some(i) => match i.len() {
4 => {
let ipv4: [u8; 4] = [i[0], i[1], i[2], i[3]];
IpAddr::from(ipv4).to_string()
}
16 => {
let ipv6: [u8; 16] = [
i[0], i[1], i[2], i[3], i[4], i[5], i[6], i[7], i[8], i[9],
i[10], i[11], i[12], i[13], i[14], i[15],
];
IpAddr::from(ipv6).to_string()
}
_ => String::new(),
},
None => String::new(),
},
})
.collect(),
None => HashSet::new(),
}
}
} }
fn gen_certificate( fn gen_certificate(
domain: &str,
key_pair: &KeyPair,
digest: &MessageDigest,
acme_ext: &str,
domain: &str,
key_pair: &KeyPair,
digest: &MessageDigest,
acme_ext: &str,
) -> Result<X509, Error> { ) -> Result<X509, Error> {
let mut x509_name = X509NameBuilder::new()?;
x509_name.append_entry_by_text("O", super::APP_ORG)?;
let ca_name = format!("{} TLS-ALPN-01 Authority", super::APP_NAME);
x509_name.append_entry_by_text("CN", &ca_name)?;
let x509_name = x509_name.build();
let mut builder = X509Builder::new()?;
builder.set_version(super::X509_VERSION)?;
let serial_number = {
let mut serial = BigNum::new()?;
serial.rand(super::CRT_SERIAL_NB_BITS - 1, MsbOption::MAYBE_ZERO, false)?;
serial.to_asn1_integer()?
};
builder.set_serial_number(&serial_number)?;
builder.set_subject_name(&x509_name)?;
builder.set_issuer_name(&x509_name)?;
builder.set_pubkey(&key_pair.inner_key)?;
let not_before = Asn1Time::days_from_now(0)?;
builder.set_not_before(&not_before)?;
let not_after = Asn1Time::days_from_now(super::CRT_NB_DAYS_VALIDITY)?;
builder.set_not_after(&not_after)?;
builder.append_extension(BasicConstraints::new().build()?)?;
let ctx = builder.x509v3_context(None, None);
let san_ext = SubjectAlternativeName::new().dns(domain).build(&ctx)?;
builder.append_extension(san_ext)?;
if !acme_ext.is_empty() {
let ctx = builder.x509v3_context(None, None);
let mut v: Vec<&str> = acme_ext.split('=').collect();
let value = v.pop().ok_or_else(|| Error::from(super::INVALID_EXT_MSG))?;
let acme_ext_name = v.pop().ok_or_else(|| Error::from(super::INVALID_EXT_MSG))?;
if !v.is_empty() {
return Err(Error::from(super::INVALID_EXT_MSG));
}
let acme_ext = X509Extension::new(None, Some(&ctx), acme_ext_name, value)
.map_err(|_| Error::from(super::INVALID_EXT_MSG))?;
builder
.append_extension(acme_ext)
.map_err(|_| Error::from(super::INVALID_EXT_MSG))?;
}
builder.sign(&key_pair.inner_key, *digest)?;
let cert = builder.build();
Ok(cert)
let mut x509_name = X509NameBuilder::new()?;
x509_name.append_entry_by_text("O", super::APP_ORG)?;
let ca_name = format!("{} TLS-ALPN-01 Authority", super::APP_NAME);
x509_name.append_entry_by_text("CN", &ca_name)?;
let x509_name = x509_name.build();
let mut builder = X509Builder::new()?;
builder.set_version(super::X509_VERSION)?;
let serial_number = {
let mut serial = BigNum::new()?;
serial.rand(super::CRT_SERIAL_NB_BITS - 1, MsbOption::MAYBE_ZERO, false)?;
serial.to_asn1_integer()?
};
builder.set_serial_number(&serial_number)?;
builder.set_subject_name(&x509_name)?;
builder.set_issuer_name(&x509_name)?;
builder.set_pubkey(&key_pair.inner_key)?;
let not_before = Asn1Time::days_from_now(0)?;
builder.set_not_before(&not_before)?;
let not_after = Asn1Time::days_from_now(super::CRT_NB_DAYS_VALIDITY)?;
builder.set_not_after(&not_after)?;
builder.append_extension(BasicConstraints::new().build()?)?;
let ctx = builder.x509v3_context(None, None);
let san_ext = SubjectAlternativeName::new().dns(domain).build(&ctx)?;
builder.append_extension(san_ext)?;
if !acme_ext.is_empty() {
let ctx = builder.x509v3_context(None, None);
let mut v: Vec<&str> = acme_ext.split('=').collect();
let value = v.pop().ok_or_else(|| Error::from(super::INVALID_EXT_MSG))?;
let acme_ext_name = v.pop().ok_or_else(|| Error::from(super::INVALID_EXT_MSG))?;
if !v.is_empty() {
return Err(Error::from(super::INVALID_EXT_MSG));
}
let acme_ext = X509Extension::new(None, Some(&ctx), acme_ext_name, value)
.map_err(|_| Error::from(super::INVALID_EXT_MSG))?;
builder
.append_extension(acme_ext)
.map_err(|_| Error::from(super::INVALID_EXT_MSG))?;
}
builder.sign(&key_pair.inner_key, *digest)?;
let cert = builder.build();
Ok(cert)
} }

44
acme_common/src/crypto/openssl_hash.rs

@ -7,28 +7,28 @@ use openssl::sign::Signer;
pub type HashFunction = super::BaseHashFunction; pub type HashFunction = super::BaseHashFunction;
impl HashFunction { impl HashFunction {
pub fn hash(&self, data: &[u8]) -> Vec<u8> {
match self {
HashFunction::Sha256 => sha256(data).to_vec(),
HashFunction::Sha384 => sha384(data).to_vec(),
HashFunction::Sha512 => sha512(data).to_vec(),
}
}
pub fn hash(&self, data: &[u8]) -> Vec<u8> {
match self {
HashFunction::Sha256 => sha256(data).to_vec(),
HashFunction::Sha384 => sha384(data).to_vec(),
HashFunction::Sha512 => sha512(data).to_vec(),
}
}
pub fn hmac(&self, key: &[u8], data: &[u8]) -> Result<Vec<u8>, Error> {
let key = PKey::hmac(key)?;
let h_func = self.native_digest();
let mut signer = Signer::new(h_func, &key)?;
signer.update(data)?;
let res = signer.sign_to_vec()?;
Ok(res)
}
pub fn hmac(&self, key: &[u8], data: &[u8]) -> Result<Vec<u8>, Error> {
let key = PKey::hmac(key)?;
let h_func = self.native_digest();
let mut signer = Signer::new(h_func, &key)?;
signer.update(data)?;
let res = signer.sign_to_vec()?;
Ok(res)
}
pub(crate) fn native_digest(&self) -> MessageDigest {
match self {
HashFunction::Sha256 => MessageDigest::sha256(),
HashFunction::Sha384 => MessageDigest::sha384(),
HashFunction::Sha512 => MessageDigest::sha512(),
}
}
pub(crate) fn native_digest(&self) -> MessageDigest {
match self {
HashFunction::Sha256 => MessageDigest::sha256(),
HashFunction::Sha384 => MessageDigest::sha384(),
HashFunction::Sha512 => MessageDigest::sha512(),
}
}
} }

566
acme_common/src/crypto/openssl_keys.rs

@ -13,333 +13,333 @@ use serde_json::json;
use serde_json::value::Value; use serde_json::value::Value;
macro_rules! get_key_type { macro_rules! get_key_type {
($key: expr) => {
match $key.id() {
Id::RSA => match $key.rsa()?.size() {
256 => KeyType::Rsa2048,
512 => KeyType::Rsa4096,
s => {
return Err(format!("{}: unsupported RSA key size", s * 8).into());
}
},
Id::EC => match $key.ec_key()?.group().curve_name() {
Some(Nid::X9_62_PRIME256V1) => KeyType::EcdsaP256,
Some(Nid::SECP384R1) => KeyType::EcdsaP384,
Some(Nid::SECP521R1) => KeyType::EcdsaP521,
Some(nid) => {
return Err(format!("{:?}: unsupported EC key", nid).into());
}
None => {
return Err("unsupported EC key".into());
}
},
#[cfg(ed25519)]
Id::ED25519 => KeyType::Ed25519,
#[cfg(ed448)]
Id::ED448 => KeyType::Ed448,
_ => {
return Err("unsupported key type".into());
}
}
};
($key: expr) => {
match $key.id() {
Id::RSA => match $key.rsa()?.size() {
256 => KeyType::Rsa2048,
512 => KeyType::Rsa4096,
s => {
return Err(format!("{}: unsupported RSA key size", s * 8).into());
}
},
Id::EC => match $key.ec_key()?.group().curve_name() {
Some(Nid::X9_62_PRIME256V1) => KeyType::EcdsaP256,
Some(Nid::SECP384R1) => KeyType::EcdsaP384,
Some(Nid::SECP521R1) => KeyType::EcdsaP521,
Some(nid) => {
return Err(format!("{:?}: unsupported EC key", nid).into());
}
None => {
return Err("unsupported EC key".into());
}
},
#[cfg(ed25519)]
Id::ED25519 => KeyType::Ed25519,
#[cfg(ed448)]
Id::ED448 => KeyType::Ed448,
_ => {
return Err("unsupported key type".into());
}
}
};
} }
macro_rules! get_ecdsa_sig_part { macro_rules! get_ecdsa_sig_part {
($part: expr, $size: ident) => {{
let mut p = $part.to_vec();
let length = p.len();
if length != $size {
let mut s: Vec<u8> = Vec::with_capacity($size);
s.resize_with($size - length, || 0);
s.append(&mut p);
s
} else {
p
}
}};
($part: expr, $size: ident) => {{
let mut p = $part.to_vec();
let length = p.len();
if length != $size {
let mut s: Vec<u8> = Vec::with_capacity($size);
s.resize_with($size - length, || 0);
s.append(&mut p);
s
} else {
p
}
}};
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct KeyPair { pub struct KeyPair {
pub key_type: KeyType,
pub inner_key: PKey<Private>,
pub key_type: KeyType,
pub inner_key: PKey<Private>,
} }
impl KeyPair { impl KeyPair {
pub fn from_der(der_data: &[u8]) -> Result<Self, Error> {
let inner_key = PKey::private_key_from_der(der_data)?;
let key_type = get_key_type!(inner_key);
Ok(KeyPair {
key_type,
inner_key,
})
}
pub fn from_der(der_data: &[u8]) -> Result<Self, Error> {
let inner_key = PKey::private_key_from_der(der_data)?;
let key_type = get_key_type!(inner_key);
Ok(KeyPair {
key_type,
inner_key,
})
}
pub fn from_pem(pem_data: &[u8]) -> Result<Self, Error> {
let inner_key = PKey::private_key_from_pem(pem_data)?;
let key_type = get_key_type!(inner_key);
Ok(KeyPair {
key_type,
inner_key,
})
}
pub fn from_pem(pem_data: &[u8]) -> Result<Self, Error> {
let inner_key = PKey::private_key_from_pem(pem_data)?;
let key_type = get_key_type!(inner_key);
Ok(KeyPair {
key_type,
inner_key,
})
}
pub fn private_key_to_der(&self) -> Result<Vec<u8>, Error> {
self.inner_key.private_key_to_der().map_err(Error::from)
}
pub fn private_key_to_der(&self) -> Result<Vec<u8>, Error> {
self.inner_key.private_key_to_der().map_err(Error::from)
}
pub fn private_key_to_pem(&self) -> Result<Vec<u8>, Error> {
self.inner_key
.private_key_to_pem_pkcs8()
.map_err(Error::from)
}
pub fn private_key_to_pem(&self) -> Result<Vec<u8>, Error> {
self.inner_key
.private_key_to_pem_pkcs8()
.map_err(Error::from)
}
pub fn public_key_to_pem(&self) -> Result<Vec<u8>, Error> {
self.inner_key.public_key_to_pem().map_err(Error::from)
}
pub fn public_key_to_pem(&self) -> Result<Vec<u8>, Error> {
self.inner_key.public_key_to_pem().map_err(Error::from)
}
pub fn sign(&self, alg: &JwsSignatureAlgorithm, data: &[u8]) -> Result<Vec<u8>, Error> {
self.key_type.check_alg_compatibility(alg)?;
match alg {
JwsSignatureAlgorithm::Hs256
| JwsSignatureAlgorithm::Hs384
| JwsSignatureAlgorithm::Hs512 => Err(format!(
"{} key pair cannot be used for the {} signature algorithm",
self.key_type, alg
)
.into()),
JwsSignatureAlgorithm::Rs256 => self.sign_rsa(&MessageDigest::sha256(), data),
JwsSignatureAlgorithm::Es256 => self.sign_ecdsa(&HashFunction::Sha256, data),
JwsSignatureAlgorithm::Es384 => self.sign_ecdsa(&HashFunction::Sha384, data),
JwsSignatureAlgorithm::Es512 => self.sign_ecdsa(&HashFunction::Sha512, data),
#[cfg(ed25519)]
JwsSignatureAlgorithm::Ed25519 => self.sign_eddsa(data),
#[cfg(ed448)]
JwsSignatureAlgorithm::Ed448 => self.sign_eddsa(data),
}
}
pub fn sign(&self, alg: &JwsSignatureAlgorithm, data: &[u8]) -> Result<Vec<u8>, Error> {
self.key_type.check_alg_compatibility(alg)?;
match alg {
JwsSignatureAlgorithm::Hs256
| JwsSignatureAlgorithm::Hs384
| JwsSignatureAlgorithm::Hs512 => Err(format!(
"{} key pair cannot be used for the {} signature algorithm",
self.key_type, alg
)
.into()),
JwsSignatureAlgorithm::Rs256 => self.sign_rsa(&MessageDigest::sha256(), data),
JwsSignatureAlgorithm::Es256 => self.sign_ecdsa(&HashFunction::Sha256, data),
JwsSignatureAlgorithm::Es384 => self.sign_ecdsa(&HashFunction::Sha384, data),
JwsSignatureAlgorithm::Es512 => self.sign_ecdsa(&HashFunction::Sha512, data),
#[cfg(ed25519)]
JwsSignatureAlgorithm::Ed25519 => self.sign_eddsa(data),
#[cfg(ed448)]
JwsSignatureAlgorithm::Ed448 => self.sign_eddsa(data),
}
}
fn sign_rsa(&self, hash_func: &MessageDigest, data: &[u8]) -> Result<Vec<u8>, Error> {
let mut signer = Signer::new(*hash_func, &self.inner_key)?;
signer.update(data)?;
let signature = signer.sign_to_vec()?;
Ok(signature)
}
fn sign_rsa(&self, hash_func: &MessageDigest, data: &[u8]) -> Result<Vec<u8>, Error> {
let mut signer = Signer::new(*hash_func, &self.inner_key)?;
signer.update(data)?;
let signature = signer.sign_to_vec()?;
Ok(signature)
}
fn sign_ecdsa(&self, hash_func: &HashFunction, data: &[u8]) -> Result<Vec<u8>, Error> {
let fingerprint = hash_func.hash(data);
let signature = EcdsaSig::sign(&fingerprint, self.inner_key.ec_key()?.as_ref())?;
let sig_size = match self.key_type {
KeyType::EcdsaP256 => 32,
KeyType::EcdsaP384 => 48,
KeyType::EcdsaP521 => 66,
_ => {
return Err("not an ecdsa key".into());
}
};
let r = get_ecdsa_sig_part!(signature.r(), sig_size);
let mut s = get_ecdsa_sig_part!(signature.s(), sig_size);
let mut signature = r;
signature.append(&mut s);
Ok(signature)
}
fn sign_ecdsa(&self, hash_func: &HashFunction, data: &[u8]) -> Result<Vec<u8>, Error> {
let fingerprint = hash_func.hash(data);
let signature = EcdsaSig::sign(&fingerprint, self.inner_key.ec_key()?.as_ref())?;
let sig_size = match self.key_type {
KeyType::EcdsaP256 => 32,
KeyType::EcdsaP384 => 48,
KeyType::EcdsaP521 => 66,
_ => {
return Err("not an ecdsa key".into());
}
};
let r = get_ecdsa_sig_part!(signature.r(), sig_size);
let mut s = get_ecdsa_sig_part!(signature.s(), sig_size);
let mut signature = r;
signature.append(&mut s);
Ok(signature)
}
#[cfg(any(ed25519, ed448))]
fn sign_eddsa(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
let mut signer = Signer::new_without_digest(&self.inner_key)?;
let signature = signer.sign_oneshot_to_vec(data)?;
Ok(signature)
}
#[cfg(any(ed25519, ed448))]
fn sign_eddsa(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
let mut signer = Signer::new_without_digest(&self.inner_key)?;
let signature = signer.sign_oneshot_to_vec(data)?;
Ok(signature)
}
pub fn jwk_public_key(&self) -> Result<Value, Error> {
self.get_jwk_public_key(false)
}
pub fn jwk_public_key(&self) -> Result<Value, Error> {
self.get_jwk_public_key(false)
}
pub fn jwk_public_key_thumbprint(&self) -> Result<Value, Error> {
self.get_jwk_public_key(true)
}
pub fn jwk_public_key_thumbprint(&self) -> Result<Value, Error> {
self.get_jwk_public_key(true)
}
fn get_jwk_public_key(&self, thumbprint: bool) -> Result<Value, Error> {
match self.key_type {
KeyType::Rsa2048 | KeyType::Rsa4096 => self.get_rsa_jwk(thumbprint),
KeyType::EcdsaP256 | KeyType::EcdsaP384 | KeyType::EcdsaP521 => {
self.get_ecdsa_jwk(thumbprint)
}
#[cfg(ed25519)]
KeyType::Ed25519 => self.get_eddsa_jwk(thumbprint),
#[cfg(ed448)]
KeyType::Ed448 => self.get_eddsa_jwk(thumbprint),
}
}
fn get_jwk_public_key(&self, thumbprint: bool) -> Result<Value, Error> {
match self.key_type {
KeyType::Rsa2048 | KeyType::Rsa4096 => self.get_rsa_jwk(thumbprint),
KeyType::EcdsaP256 | KeyType::EcdsaP384 | KeyType::EcdsaP521 => {
self.get_ecdsa_jwk(thumbprint)
}
#[cfg(ed25519)]
KeyType::Ed25519 => self.get_eddsa_jwk(thumbprint),
#[cfg(ed448)]
KeyType::Ed448 => self.get_eddsa_jwk(thumbprint),
}
}
fn get_rsa_jwk(&self, thumbprint: bool) -> Result<Value, Error> {
let rsa = self.inner_key.rsa().unwrap();
let e = rsa.e();
let n = rsa.n();
let e = b64_encode(&e.to_vec());
let n = b64_encode(&n.to_vec());
let jwk = if thumbprint {
json!({
"kty": "RSA",
"e": e,
"n": n,
})
} else {
json!({
"alg": "RS256",
"kty": "RSA",
"use": "sig",
"e": e,
"n": n,
})
};
Ok(jwk)
}
fn get_rsa_jwk(&self, thumbprint: bool) -> Result<Value, Error> {
let rsa = self.inner_key.rsa().unwrap();
let e = rsa.e();
let n = rsa.n();
let e = b64_encode(&e.to_vec());
let n = b64_encode(&n.to_vec());
let jwk = if thumbprint {
json!({
"kty": "RSA",
"e": e,
"n": n,
})
} else {
json!({
"alg": "RS256",
"kty": "RSA",
"use": "sig",
"e": e,
"n": n,
})
};
Ok(jwk)
}
fn get_ecdsa_jwk(&self, thumbprint: bool) -> Result<Value, Error> {
let (crv, alg, size, curve) = match self.key_type {
KeyType::EcdsaP256 => ("P-256", "ES256", 32, Nid::X9_62_PRIME256V1),
KeyType::EcdsaP384 => ("P-384", "ES384", 48, Nid::SECP384R1),
KeyType::EcdsaP521 => ("P-521", "ES512", 66, Nid::SECP521R1),
_ => {
return Err("not an ECDSA elliptic curve".into());
}
};
let group = EcGroup::from_curve_name(curve).unwrap();
let mut ctx = BigNumContext::new().unwrap();
let mut x = BigNum::new().unwrap();
let mut y = BigNum::new().unwrap();
self.inner_key
.ec_key()
.unwrap()
.public_key()
.affine_coordinates_gfp(&group, &mut x, &mut y, &mut ctx)?;
let x = b64_encode(&x.to_vec_padded(size)?);
let y = b64_encode(&y.to_vec_padded(size)?);
let jwk = if thumbprint {
json!({
"crv": crv,
"kty": "EC",
"x": x,
"y": y,
})
} else {
json!({
"alg": alg,
"crv": crv,
"kty": "EC",
"use": "sig",
"x": x,
"y": y,
})
};
Ok(jwk)
}
fn get_ecdsa_jwk(&self, thumbprint: bool) -> Result<Value, Error> {
let (crv, alg, size, curve) = match self.key_type {
KeyType::EcdsaP256 => ("P-256", "ES256", 32, Nid::X9_62_PRIME256V1),
KeyType::EcdsaP384 => ("P-384", "ES384", 48, Nid::SECP384R1),
KeyType::EcdsaP521 => ("P-521", "ES512", 66, Nid::SECP521R1),
_ => {
return Err("not an ECDSA elliptic curve".into());
}
};
let group = EcGroup::from_curve_name(curve).unwrap();
let mut ctx = BigNumContext::new().unwrap();
let mut x = BigNum::new().unwrap();
let mut y = BigNum::new().unwrap();
self.inner_key
.ec_key()
.unwrap()
.public_key()
.affine_coordinates_gfp(&group, &mut x, &mut y, &mut ctx)?;
let x = b64_encode(&x.to_vec_padded(size)?);
let y = b64_encode(&y.to_vec_padded(size)?);
let jwk = if thumbprint {
json!({
"crv": crv,
"kty": "EC",
"x": x,
"y": y,
})
} else {
json!({
"alg": alg,
"crv": crv,
"kty": "EC",
"use": "sig",
"x": x,
"y": y,
})
};
Ok(jwk)
}
#[cfg(any(ed25519, ed448))]
fn get_eddsa_jwk(&self, thumbprint: bool) -> Result<Value, Error> {
let crv = match self.key_type {
#[cfg(ed25519)]
KeyType::Ed25519 => "Ed25519",
#[cfg(ed448)]
KeyType::Ed448 => "Ed448",
_ => {
return Err("not an EdDSA elliptic curve".into());
}
};
#[cfg(any(ed25519, ed448))]
fn get_eddsa_jwk(&self, thumbprint: bool) -> Result<Value, Error> {
let crv = match self.key_type {
#[cfg(ed25519)]
KeyType::Ed25519 => "Ed25519",
#[cfg(ed448)]
KeyType::Ed448 => "Ed448",
_ => {
return Err("not an EdDSA elliptic curve".into());
}
};
/*
* /!\ WARNING: HAZARDOUS AND UGLY CODE /!\
*
* I couldn't find a way to get the value of `x` using the OpenSSL
* interface, therefore I had to hack my way arround.
*
* The idea behind this hack is to export the public key in PEM, then
* get the PEM base64 part, convert it to base64url without padding
* and finally truncate the first part so only the value of `x`
* remains.
*/
/*
* /!\ WARNING: HAZARDOUS AND UGLY CODE /!\
*
* I couldn't find a way to get the value of `x` using the OpenSSL
* interface, therefore I had to hack my way arround.
*
* The idea behind this hack is to export the public key in PEM, then
* get the PEM base64 part, convert it to base64url without padding
* and finally truncate the first part so only the value of `x`
* remains.
*/
// -----BEGIN UGLY-----
let mut x = String::new();
let public_pem = self.public_key_to_pem()?;
let public_pem = String::from_utf8(public_pem)?;
for pem_line in public_pem.lines() {
if !pem_line.is_empty() && !pem_line.starts_with("-----") {
x += &pem_line
.trim()
.trim_end_matches('=')
.replace('/', "_")
.replace('+', "-");
}
}
x.replace_range(..16, "");
// -----END UGLY-----
// -----BEGIN UGLY-----
let mut x = String::new();
let public_pem = self.public_key_to_pem()?;
let public_pem = String::from_utf8(public_pem)?;
for pem_line in public_pem.lines() {
if !pem_line.is_empty() && !pem_line.starts_with("-----") {
x += &pem_line
.trim()
.trim_end_matches('=')
.replace('/', "_")
.replace('+', "-");
}
}
x.replace_range(..16, "");
// -----END UGLY-----
let jwk = if thumbprint {
json!({
"crv": crv,
"kty": "OKP",
"x": &x,
})
} else {
json!({
"alg": "EdDSA",
"crv": crv,
"kty": "OKP",
"use": "sig",
"x": &x,
})
};
Ok(jwk)
}
let jwk = if thumbprint {
json!({
"crv": crv,
"kty": "OKP",
"x": &x,
})
} else {
json!({
"alg": "EdDSA",
"crv": crv,
"kty": "OKP",
"use": "sig",
"x": &x,
})
};
Ok(jwk)
}
} }
fn gen_rsa_pair(nb_bits: u32) -> Result<PKey<Private>, Error> { fn gen_rsa_pair(nb_bits: u32) -> Result<PKey<Private>, Error> {
let priv_key = Rsa::generate(nb_bits)?;
let pk = PKey::from_rsa(priv_key).map_err(|_| Error::from(""))?;
Ok(pk)
let priv_key = Rsa::generate(nb_bits)?;
let pk = PKey::from_rsa(priv_key).map_err(|_| Error::from(""))?;
Ok(pk)
} }
fn gen_ec_pair(nid: Nid) -> Result<PKey<Private>, Error> { fn gen_ec_pair(nid: Nid) -> Result<PKey<Private>, Error> {
let mut group = EcGroup::from_curve_name(nid)?;
let mut group = EcGroup::from_curve_name(nid)?;
// Use NAMED_CURVE format; OpenSSL 1.0.1 and 1.0.2 default to EXPLICIT_CURVE which won't work (see #9)
group.set_asn1_flag(Asn1Flag::NAMED_CURVE);
// Use NAMED_CURVE format; OpenSSL 1.0.1 and 1.0.2 default to EXPLICIT_CURVE which won't work (see #9)
group.set_asn1_flag(Asn1Flag::NAMED_CURVE);
let ec_priv_key = EcKey::generate(&group).map_err(|_| Error::from(""))?;
let pk = PKey::from_ec_key(ec_priv_key).map_err(|_| Error::from(""))?;
Ok(pk)
let ec_priv_key = EcKey::generate(&group).map_err(|_| Error::from(""))?;
let pk = PKey::from_ec_key(ec_priv_key).map_err(|_| Error::from(""))?;
Ok(pk)
} }
#[cfg(ed25519)] #[cfg(ed25519)]
fn gen_ed25519_pair() -> Result<PKey<Private>, Error> { fn gen_ed25519_pair() -> Result<PKey<Private>, Error> {
let pk = PKey::generate_ed25519().map_err(|_| Error::from(""))?;
Ok(pk)
let pk = PKey::generate_ed25519().map_err(|_| Error::from(""))?;
Ok(pk)
} }
#[cfg(ed448)] #[cfg(ed448)]
fn gen_ed448_pair() -> Result<PKey<Private>, Error> { fn gen_ed448_pair() -> Result<PKey<Private>, Error> {
let pk = PKey::generate_ed448().map_err(|_| Error::from(""))?;
Ok(pk)
let pk = PKey::generate_ed448().map_err(|_| Error::from(""))?;
Ok(pk)
} }
pub fn gen_keypair(key_type: KeyType) -> Result<KeyPair, Error> { pub fn gen_keypair(key_type: KeyType) -> Result<KeyPair, Error> {
let priv_key = match key_type {
KeyType::Rsa2048 => gen_rsa_pair(2048),
KeyType::Rsa4096 => gen_rsa_pair(4096),
KeyType::EcdsaP256 => gen_ec_pair(Nid::X9_62_PRIME256V1),
KeyType::EcdsaP384 => gen_ec_pair(Nid::SECP384R1),
KeyType::EcdsaP521 => gen_ec_pair(Nid::SECP521R1),
#[cfg(ed25519)]
KeyType::Ed25519 => gen_ed25519_pair(),
#[cfg(ed448)]
KeyType::Ed448 => gen_ed448_pair(),
}
.map_err(|_| Error::from(format!("unable to generate a {} key pair", key_type)))?;
let key_pair = KeyPair {
key_type,
inner_key: priv_key,
};
Ok(key_pair)
let priv_key = match key_type {
KeyType::Rsa2048 => gen_rsa_pair(2048),
KeyType::Rsa4096 => gen_rsa_pair(4096),
KeyType::EcdsaP256 => gen_ec_pair(Nid::X9_62_PRIME256V1),
KeyType::EcdsaP384 => gen_ec_pair(Nid::SECP384R1),
KeyType::EcdsaP521 => gen_ec_pair(Nid::SECP521R1),
#[cfg(ed25519)]
KeyType::Ed25519 => gen_ed25519_pair(),
#[cfg(ed448)]
KeyType::Ed448 => gen_ed448_pair(),
}
.map_err(|_| Error::from(format!("unable to generate a {} key pair", key_type)))?;
let key_pair = KeyPair {
key_type,
inner_key: priv_key,
};
Ok(key_pair)
} }

38
acme_common/src/crypto/openssl_subject_attribute.rs

@ -3,23 +3,23 @@ use openssl::nid::Nid;
pub type SubjectAttribute = super::BaseSubjectAttribute; pub type SubjectAttribute = super::BaseSubjectAttribute;
impl SubjectAttribute { impl SubjectAttribute {
pub fn get_nid(&self) -> Nid {
match self {
SubjectAttribute::CountryName => Nid::COUNTRYNAME,
SubjectAttribute::GenerationQualifier => Nid::GENERATIONQUALIFIER,
SubjectAttribute::GivenName => Nid::GIVENNAME,
SubjectAttribute::Initials => Nid::INITIALS,
SubjectAttribute::LocalityName => Nid::LOCALITYNAME,
SubjectAttribute::Name => Nid::NAME,
SubjectAttribute::OrganizationName => Nid::ORGANIZATIONNAME,
SubjectAttribute::OrganizationalUnitName => Nid::ORGANIZATIONALUNITNAME,
SubjectAttribute::Pkcs9EmailAddress => Nid::PKCS9_EMAILADDRESS,
SubjectAttribute::PostalAddress => Nid::POSTALADDRESS,
SubjectAttribute::PostalCode => Nid::POSTALCODE,
SubjectAttribute::StateOrProvinceName => Nid::STATEORPROVINCENAME,
SubjectAttribute::Street => Nid::STREETADDRESS,
SubjectAttribute::Surname => Nid::SURNAME,
SubjectAttribute::Title => Nid::TITLE,
}
}
pub fn get_nid(&self) -> Nid {
match self {
SubjectAttribute::CountryName => Nid::COUNTRYNAME,
SubjectAttribute::GenerationQualifier => Nid::GENERATIONQUALIFIER,
SubjectAttribute::GivenName => Nid::GIVENNAME,
SubjectAttribute::Initials => Nid::INITIALS,
SubjectAttribute::LocalityName => Nid::LOCALITYNAME,
SubjectAttribute::Name => Nid::NAME,
SubjectAttribute::OrganizationName => Nid::ORGANIZATIONNAME,
SubjectAttribute::OrganizationalUnitName => Nid::ORGANIZATIONALUNITNAME,
SubjectAttribute::Pkcs9EmailAddress => Nid::PKCS9_EMAILADDRESS,
SubjectAttribute::PostalAddress => Nid::POSTALADDRESS,
SubjectAttribute::PostalCode => Nid::POSTALCODE,
SubjectAttribute::StateOrProvinceName => Nid::STATEORPROVINCENAME,
SubjectAttribute::Street => Nid::STREETADDRESS,
SubjectAttribute::Surname => Nid::SURNAME,
SubjectAttribute::Title => Nid::TITLE,
}
}
} }

38
acme_common/src/crypto/openssl_version.rs

@ -1,27 +1,27 @@
pub fn get_lib_name() -> String { pub fn get_lib_name() -> String {
env!("ACMED_TLS_LIB_NAME").to_string()
env!("ACMED_TLS_LIB_NAME").to_string()
} }
pub fn get_lib_version() -> String { pub fn get_lib_version() -> String {
let v = openssl::version::number() as u64;
let mut version = vec![];
for i in 0..3 {
let n = get_openssl_version_unit(v, i);
version.push(format!("{}", n));
}
let version = version.join(".");
let p = get_openssl_version_unit(v, 3);
if p != 0 {
let p = p + 0x60;
let p = std::char::from_u32(p as u32).unwrap();
format!("{}{}", version, p)
} else {
version
}
let v = openssl::version::number() as u64;
let mut version = vec![];
for i in 0..3 {
let n = get_openssl_version_unit(v, i);
version.push(format!("{}", n));
}
let version = version.join(".");
let p = get_openssl_version_unit(v, 3);
if p != 0 {
let p = p + 0x60;
let p = std::char::from_u32(p as u32).unwrap();
format!("{}{}", version, p)
} else {
version
}
} }
fn get_openssl_version_unit(n: u64, pos: u32) -> u64 { fn get_openssl_version_unit(n: u64, pos: u32) -> u64 {
let p = 0x000f_f000_0000 >> (8 * pos);
let n = n & p;
n >> (8 * (3 - pos) + 4)
let p = 0x000f_f000_0000 >> (8 * pos);
let n = n & p;
n >> (8 * (3 - pos) + 4)
} }

130
acme_common/src/error.rs

@ -2,132 +2,132 @@ use std::fmt;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Error { pub struct Error {
pub message: String,
pub message: String,
} }
impl Error { impl Error {
pub fn prefix(&self, prefix: &str) -> Self {
Error {
message: format!("{}: {}", prefix, &self.message),
}
}
pub fn prefix(&self, prefix: &str) -> Self {
Error {
message: format!("{}: {}", prefix, &self.message),
}
}
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
} }
impl From<&str> for Error { impl From<&str> for Error {
fn from(error: &str) -> Self {
Error {
message: error.to_string(),
}
}
fn from(error: &str) -> Self {
Error {
message: error.to_string(),
}
}
} }
impl From<String> for Error { impl From<String> for Error {
fn from(error: String) -> Self {
error.as_str().into()
}
fn from(error: String) -> Self {
error.as_str().into()
}
} }
impl From<&String> for Error { impl From<&String> for Error {
fn from(error: &String) -> Self {
error.as_str().into()
}
fn from(error: &String) -> Self {
error.as_str().into()
}
} }
impl From<std::io::Error> for Error { impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
format!("IO error: {}", error).into()
}
fn from(error: std::io::Error) -> Self {
format!("IO error: {}", error).into()
}
} }
impl From<std::net::AddrParseError> for Error { impl From<std::net::AddrParseError> for Error {
fn from(error: std::net::AddrParseError) -> Self {
format!("{}", error).into()
}
fn from(error: std::net::AddrParseError) -> Self {
format!("{}", error).into()
}
} }
impl From<std::string::FromUtf8Error> for Error { impl From<std::string::FromUtf8Error> for Error {
fn from(error: std::string::FromUtf8Error) -> Self {
format!("UTF-8 error: {}", error).into()
}
fn from(error: std::string::FromUtf8Error) -> Self {
format!("UTF-8 error: {}", error).into()
}
} }
impl From<std::sync::mpsc::RecvError> for Error { impl From<std::sync::mpsc::RecvError> for Error {
fn from(error: std::sync::mpsc::RecvError) -> Self {
format!("MSPC receiver error: {}", error).into()
}
fn from(error: std::sync::mpsc::RecvError) -> Self {
format!("MSPC receiver error: {}", error).into()
}
} }
impl From<std::time::SystemTimeError> for Error { impl From<std::time::SystemTimeError> for Error {
fn from(error: std::time::SystemTimeError) -> Self {
format!("SystemTimeError difference: {:?}", error.duration()).into()
}
fn from(error: std::time::SystemTimeError) -> Self {
format!("SystemTimeError difference: {:?}", error.duration()).into()
}
} }
impl From<base64::DecodeError> for Error { impl From<base64::DecodeError> for Error {
fn from(error: base64::DecodeError) -> Self {
format!("base 64 decode error: {}", error).into()
}
fn from(error: base64::DecodeError) -> Self {
format!("base 64 decode error: {}", error).into()
}
} }
impl From<syslog::Error> for Error { impl From<syslog::Error> for Error {
fn from(error: syslog::Error) -> Self {
format!("syslog error: {}", error).into()
}
fn from(error: syslog::Error) -> Self {
format!("syslog error: {}", error).into()
}
} }
impl From<toml::de::Error> for Error { impl From<toml::de::Error> for Error {
fn from(error: toml::de::Error) -> Self {
format!("IO error: {}", error).into()
}
fn from(error: toml::de::Error) -> Self {
format!("IO error: {}", error).into()
}
} }
impl From<serde_json::error::Error> for Error { impl From<serde_json::error::Error> for Error {
fn from(error: serde_json::error::Error) -> Self {
format!("IO error: {}", error).into()
}
fn from(error: serde_json::error::Error) -> Self {
format!("IO error: {}", error).into()
}
} }
impl From<attohttpc::Error> for Error { impl From<attohttpc::Error> for Error {
fn from(error: attohttpc::Error) -> Self {
format!("HTTP error: {}", error).into()
}
fn from(error: attohttpc::Error) -> Self {
format!("HTTP error: {}", error).into()
}
} }
impl From<glob::PatternError> for Error { impl From<glob::PatternError> for Error {
fn from(error: glob::PatternError) -> Self {
format!("pattern error: {}", error).into()
}
fn from(error: glob::PatternError) -> Self {
format!("pattern error: {}", error).into()
}
} }
impl From<tinytemplate::error::Error> for Error { impl From<tinytemplate::error::Error> for Error {
fn from(error: tinytemplate::error::Error) -> Self {
format!("template error: {}", error).into()
}
fn from(error: tinytemplate::error::Error) -> Self {
format!("template error: {}", error).into()
}
} }
#[cfg(feature = "crypto_openssl")] #[cfg(feature = "crypto_openssl")]
impl From<native_tls::Error> for Error { impl From<native_tls::Error> for Error {
fn from(error: native_tls::Error) -> Self {
format!("{}", error).into()
}
fn from(error: native_tls::Error) -> Self {
format!("{}", error).into()
}
} }
#[cfg(feature = "crypto_openssl")] #[cfg(feature = "crypto_openssl")]
impl From<openssl::error::ErrorStack> for Error { impl From<openssl::error::ErrorStack> for Error {
fn from(error: openssl::error::ErrorStack) -> Self {
format!("{}", error).into()
}
fn from(error: openssl::error::ErrorStack) -> Self {
format!("{}", error).into()
}
} }
#[cfg(unix)] #[cfg(unix)]
impl From<nix::Error> for Error { impl From<nix::Error> for Error {
fn from(error: nix::Error) -> Self {
format!("{}", error).into()
}
fn from(error: nix::Error) -> Self {
format!("{}", error).into()
}
} }

88
acme_common/src/lib.rs

@ -11,66 +11,66 @@ pub mod logs;
mod tests; mod tests;
macro_rules! exit_match { macro_rules! exit_match {
($e: expr) => {
match $e {
Ok(_) => {}
Err(e) => {
log::error!("error: {}", e);
std::process::exit(3);
}
}
};
($e: expr) => {
match $e {
Ok(_) => {}
Err(e) => {
log::error!("error: {}", e);
std::process::exit(3);
}
}
};
} }
pub fn to_idna(domain_name: &str) -> Result<String, error::Error> { pub fn to_idna(domain_name: &str) -> Result<String, error::Error> {
let mut idna_parts = vec![];
let parts: Vec<&str> = domain_name.split('.').collect();
for name in parts.iter() {
let raw_name = name.to_lowercase();
let idna_name = if name.is_ascii() {
raw_name
} else {
let idna_name = punycode::encode(&raw_name)
.map_err(|_| error::Error::from("IDNA encoding failed."))?;
format!("xn--{}", idna_name)
};
idna_parts.push(idna_name);
}
Ok(idna_parts.join("."))
let mut idna_parts = vec![];
let parts: Vec<&str> = domain_name.split('.').collect();
for name in parts.iter() {
let raw_name = name.to_lowercase();
let idna_name = if name.is_ascii() {
raw_name
} else {
let idna_name = punycode::encode(&raw_name)
.map_err(|_| error::Error::from("IDNA encoding failed."))?;
format!("xn--{}", idna_name)
};
idna_parts.push(idna_name);
}
Ok(idna_parts.join("."))
} }
pub fn b64_encode<T: ?Sized + AsRef<[u8]>>(input: &T) -> String { pub fn b64_encode<T: ?Sized + AsRef<[u8]>>(input: &T) -> String {
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(input)
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(input)
} }
pub fn b64_decode<T: ?Sized + AsRef<[u8]>>(input: &T) -> Result<Vec<u8>, error::Error> { pub fn b64_decode<T: ?Sized + AsRef<[u8]>>(input: &T) -> Result<Vec<u8>, error::Error> {
let res = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(input)?;
Ok(res)
let res = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(input)?;
Ok(res)
} }
pub fn init_server(foreground: bool, pid_file: Option<&str>) { pub fn init_server(foreground: bool, pid_file: Option<&str>) {
if !foreground {
let mut daemonize = Daemonize::new();
if let Some(f) = pid_file {
daemonize = daemonize.pid_file(f);
}
exit_match!(daemonize.start());
} else if let Some(f) = pid_file {
exit_match!(write_pid_file(f).map_err(|e| e.prefix(f)));
}
if !foreground {
let mut daemonize = Daemonize::new();
if let Some(f) = pid_file {
daemonize = daemonize.pid_file(f);
}
exit_match!(daemonize.start());
} else if let Some(f) = pid_file {
exit_match!(write_pid_file(f).map_err(|e| e.prefix(f)));
}
} }
fn write_pid_file(pid_file: &str) -> Result<(), error::Error> { fn write_pid_file(pid_file: &str) -> Result<(), error::Error> {
let data = format!("{}\n", process::id()).into_bytes();
let mut file = File::create(pid_file)?;
file.write_all(&data)?;
file.sync_all()?;
Ok(())
let data = format!("{}\n", process::id()).into_bytes();
let mut file = File::create(pid_file)?;
file.write_all(&data)?;
file.sync_all()?;
Ok(())
} }
pub fn clean_pid_file(pid_file: Option<&str>) -> Result<(), error::Error> { pub fn clean_pid_file(pid_file: Option<&str>) -> Result<(), error::Error> {
if let Some(f) = pid_file {
fs::remove_file(f)?;
}
Ok(())
if let Some(f) = pid_file {
fs::remove_file(f)?;
}
Ok(())
} }

112
acme_common/src/logs.rs

@ -8,79 +8,79 @@ pub const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Warn;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum LogSystem { pub enum LogSystem {
SysLog,
StdErr,
SysLog,
StdErr,
} }
fn get_loglevel(log_level: Option<&str>) -> Result<LevelFilter, Error> { fn get_loglevel(log_level: Option<&str>) -> Result<LevelFilter, Error> {
let level = match log_level {
Some(v) => match v {
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
_ => {
return Err(format!("{}: invalid log level", v).into());
}
},
None => DEFAULT_LOG_LEVEL,
};
Ok(level)
let level = match log_level {
Some(v) => match v {
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
_ => {
return Err(format!("{}: invalid log level", v).into());
}
},
None => DEFAULT_LOG_LEVEL,
};
Ok(level)
} }
fn set_log_syslog(log_level: LevelFilter) -> Result<(), Error> { fn set_log_syslog(log_level: LevelFilter) -> Result<(), Error> {
syslog::init(
Facility::LOG_DAEMON,
log_level,
Some(env!("CARGO_PKG_NAME")),
)?;
Ok(())
syslog::init(
Facility::LOG_DAEMON,
log_level,
Some(env!("CARGO_PKG_NAME")),
)?;
Ok(())
} }
fn set_log_stderr(log_level: LevelFilter) -> Result<(), Error> { fn set_log_stderr(log_level: LevelFilter) -> Result<(), Error> {
let mut builder = Builder::from_env("ACMED_LOG_LEVEL");
builder.filter_level(log_level);
builder.init();
Ok(())
let mut builder = Builder::from_env("ACMED_LOG_LEVEL");
builder.filter_level(log_level);
builder.init();
Ok(())
} }
pub fn set_log_system( pub fn set_log_system(
log_level: Option<&str>,
has_syslog: bool,
has_stderr: bool,
log_level: Option<&str>,
has_syslog: bool,
has_stderr: bool,
) -> Result<(LogSystem, LevelFilter), Error> { ) -> Result<(LogSystem, LevelFilter), Error> {
let log_level = get_loglevel(log_level)?;
let logtype = if has_syslog {
LogSystem::SysLog
} else if has_stderr {
LogSystem::StdErr
} else {
DEFAULT_LOG_SYSTEM
};
match logtype {
LogSystem::SysLog => set_log_syslog(log_level)?,
LogSystem::StdErr => set_log_stderr(log_level)?,
};
Ok((logtype, log_level))
let log_level = get_loglevel(log_level)?;
let logtype = if has_syslog {
LogSystem::SysLog
} else if has_stderr {
LogSystem::StdErr
} else {
DEFAULT_LOG_SYSTEM
};
match logtype {
LogSystem::SysLog => set_log_syslog(log_level)?,
LogSystem::StdErr => set_log_stderr(log_level)?,
};
Ok((logtype, log_level))
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{set_log_system, DEFAULT_LOG_LEVEL, DEFAULT_LOG_SYSTEM};
use super::{set_log_system, DEFAULT_LOG_LEVEL, DEFAULT_LOG_SYSTEM};
#[test]
fn test_invalid_level() {
let ret = set_log_system(Some("invalid"), false, false);
assert!(ret.is_err());
}
#[test]
fn test_invalid_level() {
let ret = set_log_system(Some("invalid"), false, false);
assert!(ret.is_err());
}
#[test]
fn test_default_values() {
let ret = set_log_system(None, false, false);
assert!(ret.is_ok());
let (logtype, log_level) = ret.unwrap();
assert_eq!(logtype, DEFAULT_LOG_SYSTEM);
assert_eq!(log_level, DEFAULT_LOG_LEVEL);
}
#[test]
fn test_default_values() {
let ret = set_log_system(None, false, false);
assert!(ret.is_ok());
let (logtype, log_level) = ret.unwrap();
assert_eq!(logtype, DEFAULT_LOG_SYSTEM);
assert_eq!(log_level, DEFAULT_LOG_LEVEL);
}
} }

106
acme_common/src/tests/certificate.rs

@ -83,99 +83,99 @@ ehm+yKg=
#[test] #[test]
fn test_san_domains() { 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);
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] #[test]
fn test_san_ip() { 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);
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] #[test]
fn test_san_domains_and_ip() { 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);
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);
} }
#[test] #[test]
fn generate_rsa2048_certificate() { fn generate_rsa2048_certificate() {
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::Rsa2048, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::Rsa2048);
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::Rsa2048, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::Rsa2048);
} }
#[test] #[test]
fn generate_rsa4096_certificate() { fn generate_rsa4096_certificate() {
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::Rsa4096, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::Rsa4096);
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::Rsa4096, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::Rsa4096);
} }
#[test] #[test]
fn generate_ecdsa_p256_certificate() { fn generate_ecdsa_p256_certificate() {
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP256, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::EcdsaP256);
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP256, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::EcdsaP256);
} }
#[test] #[test]
fn generate_ecdsa_p384_certificate() { fn generate_ecdsa_p384_certificate() {
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP384, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::EcdsaP384);
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP384, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::EcdsaP384);
} }
#[cfg(ed25519)] #[cfg(ed25519)]
#[test] #[test]
fn generate_ed25519_certificate() { fn generate_ed25519_certificate() {
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::Ed25519, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::Ed25519);
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::Ed25519, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::Ed25519);
} }
#[cfg(ed448)] #[cfg(ed448)]
#[test] #[test]
fn generate_ed448_certificate() { fn generate_ed448_certificate() {
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::Ed448, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::Ed448);
let (kp, _) =
X509Certificate::from_acme_ext("example.org", "", KeyType::Ed448, HashFunction::Sha256)
.unwrap();
assert_eq!(kp.key_type, KeyType::Ed448);
} }
#[test] #[test]
fn cert_expiration_date_future() { fn cert_expiration_date_future() {
let (_, crt) =
X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP256, HashFunction::Sha256)
.unwrap();
let duration = crt.expires_in().unwrap().as_secs();
let validity_sec = CRT_NB_DAYS_VALIDITY as u64 * 24 * 60 * 60;
let delta = 60;
assert!(duration > validity_sec - delta);
assert!(duration < validity_sec + delta);
let (_, crt) =
X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP256, HashFunction::Sha256)
.unwrap();
let duration = crt.expires_in().unwrap().as_secs();
let validity_sec = CRT_NB_DAYS_VALIDITY as u64 * 24 * 60 * 60;
let delta = 60;
assert!(duration > validity_sec - delta);
assert!(duration < validity_sec + delta);
} }
#[test] #[test]
fn cert_expiration_date_past() { fn cert_expiration_date_past() {
let crt = X509Certificate::from_pem(CERTIFICATE_EXPIRED_PEM.as_bytes()).unwrap();
let duration = crt.expires_in().unwrap().as_secs();
assert_eq!(duration, 0);
let crt = X509Certificate::from_pem(CERTIFICATE_EXPIRED_PEM.as_bytes()).unwrap();
let duration = crt.expires_in().unwrap().as_secs();
assert_eq!(duration, 0);
} }

492
acme_common/src/tests/crypto_keys.rs

@ -104,308 +104,308 @@ uMXco2JR1CSjKWgqgu1CAKadJIYiv2EgIw==
#[test] #[test]
fn test_rsa_2048_jwk() { fn test_rsa_2048_jwk() {
let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("e"));
assert!(jwk.contains_key("n"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "RSA");
assert_eq!(jwk.get("e").unwrap(), "AQAB");
assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q");
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "RS256");
let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("e"));
assert!(jwk.contains_key("n"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "RSA");
assert_eq!(jwk.get("e").unwrap(), "AQAB");
assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q");
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "RS256");
} }
#[test] #[test]
fn test_rsa_2048_jwk_thumbprint() { fn test_rsa_2048_jwk_thumbprint() {
let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("e"));
assert!(jwk.contains_key("n"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "RSA");
assert_eq!(jwk.get("e").unwrap(), "AQAB");
assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q");
let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("e"));
assert!(jwk.contains_key("n"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "RSA");
assert_eq!(jwk.get("e").unwrap(), "AQAB");
assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q");
} }
#[test] #[test]
fn test_rsa_4096_jwk() { fn test_rsa_4096_jwk() {
let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("e"));
assert!(jwk.contains_key("n"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "RSA");
assert_eq!(jwk.get("e").unwrap(), "AQAB");
assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E");
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "RS256");
let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("e"));
assert!(jwk.contains_key("n"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "RSA");
assert_eq!(jwk.get("e").unwrap(), "AQAB");
assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E");
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "RS256");
} }
#[test] #[test]
fn test_rsa_4096_jwk_thumbprint() { fn test_rsa_4096_jwk_thumbprint() {
let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("e"));
assert!(jwk.contains_key("n"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "RSA");
assert_eq!(jwk.get("e").unwrap(), "AQAB");
assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E");
let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("e"));
assert!(jwk.contains_key("n"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "RSA");
assert_eq!(jwk.get("e").unwrap(), "AQAB");
assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E");
} }
#[test] #[test]
fn test_ecdsa_p256_jwk() { fn test_ecdsa_p256_jwk() {
let k = KeyPair::from_pem(KEY_ECDSA_P256_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 6);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("y"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "EC");
assert_eq!(jwk.get("crv").unwrap(), "P-256");
assert_eq!(
jwk.get("x").unwrap(),
"VpJrz2a8rASzmbHStuDxNCjQc8ZiDnrGvVeRayNskrQ"
);
assert_eq!(
jwk.get("y").unwrap(),
"GrVCHhF5hN68efEgdoYS7acUT88qhMKQbULVcBgPBUg"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "ES256");
let k = KeyPair::from_pem(KEY_ECDSA_P256_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 6);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("y"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "EC");
assert_eq!(jwk.get("crv").unwrap(), "P-256");
assert_eq!(
jwk.get("x").unwrap(),
"VpJrz2a8rASzmbHStuDxNCjQc8ZiDnrGvVeRayNskrQ"
);
assert_eq!(
jwk.get("y").unwrap(),
"GrVCHhF5hN68efEgdoYS7acUT88qhMKQbULVcBgPBUg"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "ES256");
} }
#[test] #[test]
fn test_ecdsa_p256_jwk_thumbprint() { fn test_ecdsa_p256_jwk_thumbprint() {
let k = KeyPair::from_pem(KEY_ECDSA_P256_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 4);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("y"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "EC");
assert_eq!(jwk.get("crv").unwrap(), "P-256");
assert_eq!(
jwk.get("x").unwrap(),
"VpJrz2a8rASzmbHStuDxNCjQc8ZiDnrGvVeRayNskrQ"
);
assert_eq!(
jwk.get("y").unwrap(),
"GrVCHhF5hN68efEgdoYS7acUT88qhMKQbULVcBgPBUg"
);
let k = KeyPair::from_pem(KEY_ECDSA_P256_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 4);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("y"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "EC");
assert_eq!(jwk.get("crv").unwrap(), "P-256");
assert_eq!(
jwk.get("x").unwrap(),
"VpJrz2a8rASzmbHStuDxNCjQc8ZiDnrGvVeRayNskrQ"
);
assert_eq!(
jwk.get("y").unwrap(),
"GrVCHhF5hN68efEgdoYS7acUT88qhMKQbULVcBgPBUg"
);
} }
#[test] #[test]
fn test_ecdsa_p384_jwk() { fn test_ecdsa_p384_jwk() {
let k = KeyPair::from_pem(KEY_ECDSA_P384_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 6);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("y"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "EC");
assert_eq!(jwk.get("crv").unwrap(), "P-384");
assert_eq!(
jwk.get("x").unwrap(),
"N7TmS8prIp0DAGvwg1saML4UK61oe2PPJTeGLJt0iW-PMNcetFPcMF4WCa0ez80a"
);
assert_eq!(
jwk.get("y").unwrap(),
"RE5dtMDKV9Y8hsKf3fqLzMx75WORJaGswqC68xkRNjo0HcTar4tCB9VF9eSFfTMU"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "ES384");
let k = KeyPair::from_pem(KEY_ECDSA_P384_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 6);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("y"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "EC");
assert_eq!(jwk.get("crv").unwrap(), "P-384");
assert_eq!(
jwk.get("x").unwrap(),
"N7TmS8prIp0DAGvwg1saML4UK61oe2PPJTeGLJt0iW-PMNcetFPcMF4WCa0ez80a"
);
assert_eq!(
jwk.get("y").unwrap(),
"RE5dtMDKV9Y8hsKf3fqLzMx75WORJaGswqC68xkRNjo0HcTar4tCB9VF9eSFfTMU"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "ES384");
} }
#[test] #[test]
fn test_ecdsa_p384_jwk_thumbprint() { fn test_ecdsa_p384_jwk_thumbprint() {
let k = KeyPair::from_pem(KEY_ECDSA_P384_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 4);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("y"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "EC");
assert_eq!(jwk.get("crv").unwrap(), "P-384");
assert_eq!(
jwk.get("x").unwrap(),
"N7TmS8prIp0DAGvwg1saML4UK61oe2PPJTeGLJt0iW-PMNcetFPcMF4WCa0ez80a"
);
assert_eq!(
jwk.get("y").unwrap(),
"RE5dtMDKV9Y8hsKf3fqLzMx75WORJaGswqC68xkRNjo0HcTar4tCB9VF9eSFfTMU"
);
let k = KeyPair::from_pem(KEY_ECDSA_P384_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 4);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("y"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "EC");
assert_eq!(jwk.get("crv").unwrap(), "P-384");
assert_eq!(
jwk.get("x").unwrap(),
"N7TmS8prIp0DAGvwg1saML4UK61oe2PPJTeGLJt0iW-PMNcetFPcMF4WCa0ez80a"
);
assert_eq!(
jwk.get("y").unwrap(),
"RE5dtMDKV9Y8hsKf3fqLzMx75WORJaGswqC68xkRNjo0HcTar4tCB9VF9eSFfTMU"
);
} }
#[cfg(ed25519)] #[cfg(ed25519)]
#[test] #[test]
fn test_ed25519_jwk() { fn test_ed25519_jwk() {
let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed25519");
assert_eq!(
jwk.get("x").unwrap(),
"DUX9ja8pq2wfkxuIaHzmhkdcVXMav_3rk5Y5ozOcp4o"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "EdDSA");
let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed25519");
assert_eq!(
jwk.get("x").unwrap(),
"DUX9ja8pq2wfkxuIaHzmhkdcVXMav_3rk5Y5ozOcp4o"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "EdDSA");
} }
#[cfg(ed25519)] #[cfg(ed25519)]
#[test] #[test]
fn test_ed25519_jwk_thumbprint() { fn test_ed25519_jwk_thumbprint() {
let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed25519");
assert_eq!(
jwk.get("x").unwrap(),
"DUX9ja8pq2wfkxuIaHzmhkdcVXMav_3rk5Y5ozOcp4o"
);
let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed25519");
assert_eq!(
jwk.get("x").unwrap(),
"DUX9ja8pq2wfkxuIaHzmhkdcVXMav_3rk5Y5ozOcp4o"
);
} }
#[cfg(ed25519)] #[cfg(ed25519)]
#[test] #[test]
fn test_ed25519_jwk_bis() { fn test_ed25519_jwk_bis() {
let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM_BIS.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed25519");
assert_eq!(
jwk.get("x").unwrap(),
"i9K0eV5qOJ_l_TWjWFLm8R-JbyGdlqFFeL_J0eEXFnc"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "EdDSA");
let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM_BIS.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed25519");
assert_eq!(
jwk.get("x").unwrap(),
"i9K0eV5qOJ_l_TWjWFLm8R-JbyGdlqFFeL_J0eEXFnc"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "EdDSA");
} }
#[cfg(ed25519)] #[cfg(ed25519)]
#[test] #[test]
fn test_ed25519_jwk_thumbprint_bis() { fn test_ed25519_jwk_thumbprint_bis() {
let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM_BIS.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed25519");
assert_eq!(
jwk.get("x").unwrap(),
"i9K0eV5qOJ_l_TWjWFLm8R-JbyGdlqFFeL_J0eEXFnc"
);
let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM_BIS.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed25519");
assert_eq!(
jwk.get("x").unwrap(),
"i9K0eV5qOJ_l_TWjWFLm8R-JbyGdlqFFeL_J0eEXFnc"
);
} }
#[cfg(ed448)] #[cfg(ed448)]
#[test] #[test]
fn test_ed448_jwk() { fn test_ed448_jwk() {
let k = KeyPair::from_pem(KEY_ECDSA_ED448_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed448");
assert_eq!(
jwk.get("x").unwrap(),
"b9GZ8b1hip3UMzkkNBdMF4JWBTZojxsNHK-jQBH94SY3boVs4Oeo291E1dGXz7RUMqIXjkSbU4EA"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "EdDSA");
let k = KeyPair::from_pem(KEY_ECDSA_ED448_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 5);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(jwk.contains_key("use"));
assert!(jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed448");
assert_eq!(
jwk.get("x").unwrap(),
"b9GZ8b1hip3UMzkkNBdMF4JWBTZojxsNHK-jQBH94SY3boVs4Oeo291E1dGXz7RUMqIXjkSbU4EA"
);
assert_eq!(jwk.get("use").unwrap(), "sig");
assert_eq!(jwk.get("alg").unwrap(), "EdDSA");
} }
#[cfg(ed448)] #[cfg(ed448)]
#[test] #[test]
fn test_ed448_jwk_thumbprint() { fn test_ed448_jwk_thumbprint() {
let k = KeyPair::from_pem(KEY_ECDSA_ED448_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed448");
assert_eq!(
jwk.get("x").unwrap(),
"b9GZ8b1hip3UMzkkNBdMF4JWBTZojxsNHK-jQBH94SY3boVs4Oeo291E1dGXz7RUMqIXjkSbU4EA"
);
let k = KeyPair::from_pem(KEY_ECDSA_ED448_PEM.as_bytes()).unwrap();
let jwk = k.jwk_public_key_thumbprint().unwrap();
assert!(jwk.is_object());
let jwk = jwk.as_object().unwrap();
assert_eq!(jwk.len(), 3);
assert!(jwk.contains_key("kty"));
assert!(jwk.contains_key("crv"));
assert!(jwk.contains_key("x"));
assert!(!jwk.contains_key("use"));
assert!(!jwk.contains_key("alg"));
assert_eq!(jwk.get("kty").unwrap(), "OKP");
assert_eq!(jwk.get("crv").unwrap(), "Ed448");
assert_eq!(
jwk.get("x").unwrap(),
"b9GZ8b1hip3UMzkkNBdMF4JWBTZojxsNHK-jQBH94SY3boVs4Oeo291E1dGXz7RUMqIXjkSbU4EA"
);
} }

622
acme_common/src/tests/hash.rs

@ -2,343 +2,343 @@ use crate::crypto::HashFunction;
#[test] #[test]
fn test_hash_from_str() { fn test_hash_from_str() {
let test_vectors = vec![
("sha256", HashFunction::Sha256),
("Sha256", HashFunction::Sha256),
("sha-256", HashFunction::Sha256),
("SHA_256", HashFunction::Sha256),
("sha384", HashFunction::Sha384),
("Sha-512", HashFunction::Sha512),
];
for (s, ref_h) in test_vectors {
let h: HashFunction = s.parse().unwrap();
assert_eq!(h, ref_h);
}
let test_vectors = vec![
("sha256", HashFunction::Sha256),
("Sha256", HashFunction::Sha256),
("sha-256", HashFunction::Sha256),
("SHA_256", HashFunction::Sha256),
("sha384", HashFunction::Sha384),
("Sha-512", HashFunction::Sha512),
];
for (s, ref_h) in test_vectors {
let h: HashFunction = s.parse().unwrap();
assert_eq!(h, ref_h);
}
} }
#[test] #[test]
fn test_hash_from_invalid_str() { fn test_hash_from_invalid_str() {
let test_vectors = vec!["sha42", "sha", "", "plop"];
for s in test_vectors {
let h = s.parse::<HashFunction>();
assert!(h.is_err());
}
let test_vectors = vec!["sha42", "sha", "", "plop"];
for s in test_vectors {
let h = s.parse::<HashFunction>();
assert!(h.is_err());
}
} }
#[test] #[test]
fn test_hash_sha256() { fn test_hash_sha256() {
let test_vectors = vec![
(
"Hello World!".as_bytes(),
vec![
127, 131, 177, 101, 127, 241, 252, 83, 185, 45, 193, 129, 72, 161, 214, 93, 252,
45, 75, 31, 163, 214, 119, 40, 74, 221, 210, 0, 18, 109, 144, 105,
],
),
(
&[],
vec![
227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, 39,
174, 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85,
],
),
(
&[
194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8,
45, 114,
],
vec![
65, 72, 199, 76, 128, 174, 196, 223, 91, 235, 87, 119, 200, 212, 133, 13, 219, 223,
60, 4, 73, 70, 65, 41, 226, 83, 221, 107, 112, 29, 205, 28,
],
),
];
for (data, expected) in test_vectors {
let h = HashFunction::Sha256;
let res = h.hash(data);
assert_eq!(res, expected);
}
let test_vectors = vec![
(
"Hello World!".as_bytes(),
vec![
127, 131, 177, 101, 127, 241, 252, 83, 185, 45, 193, 129, 72, 161, 214, 93, 252,
45, 75, 31, 163, 214, 119, 40, 74, 221, 210, 0, 18, 109, 144, 105,
],
),
(
&[],
vec![
227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, 39,
174, 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85,
],
),
(
&[
194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8,
45, 114,
],
vec![
65, 72, 199, 76, 128, 174, 196, 223, 91, 235, 87, 119, 200, 212, 133, 13, 219, 223,
60, 4, 73, 70, 65, 41, 226, 83, 221, 107, 112, 29, 205, 28,
],
),
];
for (data, expected) in test_vectors {
let h = HashFunction::Sha256;
let res = h.hash(data);
assert_eq!(res, expected);
}
} }
#[test] #[test]
fn test_hmac_sha256() { fn test_hmac_sha256() {
let test_vectors = vec![
(
vec![
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
],
vec![72, 105, 32, 84, 104, 101, 114, 101],
vec![
176, 52, 76, 97, 216, 219, 56, 83, 92, 168, 175, 206, 175, 11, 241, 43, 136, 29,
194, 0, 201, 131, 61, 167, 38, 233, 55, 108, 46, 50, 207, 247,
],
),
(
vec![74, 101, 102, 101],
vec![
119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111,
114, 32, 110, 111, 116, 104, 105, 110, 103, 63,
],
vec![
91, 220, 193, 70, 191, 96, 117, 78, 106, 4, 36, 38, 8, 149, 117, 199, 90, 0, 63, 8,
157, 39, 57, 131, 157, 236, 88, 185, 100, 236, 56, 67,
],
),
(
vec![
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170,
],
vec![
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221,
],
vec![
119, 62, 169, 30, 54, 128, 14, 70, 133, 77, 184, 235, 208, 145, 129, 167, 41, 89,
9, 139, 62, 248, 193, 34, 217, 99, 85, 20, 206, 213, 101, 254,
],
),
(
vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25,
],
vec![
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205,
],
vec![
130, 85, 138, 56, 154, 68, 60, 14, 164, 204, 129, 152, 153, 242, 8, 58, 133, 240,
250, 163, 229, 120, 248, 7, 122, 46, 63, 244, 103, 41, 102, 91,
],
),
];
for (key, data, expected) in test_vectors {
let h = HashFunction::Sha256;
let res = h.hmac(&key, &data).unwrap();
assert_eq!(res, expected);
}
let test_vectors = vec![
(
vec![
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
],
vec![72, 105, 32, 84, 104, 101, 114, 101],
vec![
176, 52, 76, 97, 216, 219, 56, 83, 92, 168, 175, 206, 175, 11, 241, 43, 136, 29,
194, 0, 201, 131, 61, 167, 38, 233, 55, 108, 46, 50, 207, 247,
],
),
(
vec![74, 101, 102, 101],
vec![
119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111,
114, 32, 110, 111, 116, 104, 105, 110, 103, 63,
],
vec![
91, 220, 193, 70, 191, 96, 117, 78, 106, 4, 36, 38, 8, 149, 117, 199, 90, 0, 63, 8,
157, 39, 57, 131, 157, 236, 88, 185, 100, 236, 56, 67,
],
),
(
vec![
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170,
],
vec![
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221,
],
vec![
119, 62, 169, 30, 54, 128, 14, 70, 133, 77, 184, 235, 208, 145, 129, 167, 41, 89,
9, 139, 62, 248, 193, 34, 217, 99, 85, 20, 206, 213, 101, 254,
],
),
(
vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25,
],
vec![
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205,
],
vec![
130, 85, 138, 56, 154, 68, 60, 14, 164, 204, 129, 152, 153, 242, 8, 58, 133, 240,
250, 163, 229, 120, 248, 7, 122, 46, 63, 244, 103, 41, 102, 91,
],
),
];
for (key, data, expected) in test_vectors {
let h = HashFunction::Sha256;
let res = h.hmac(&key, &data).unwrap();
assert_eq!(res, expected);
}
} }
#[test] #[test]
fn test_hash_sha384() { fn test_hash_sha384() {
let test_vectors = vec![
(
"Hello World!".as_bytes(),
vec![
191, 215, 108, 14, 187, 208, 6, 254, 229, 131, 65, 5, 71, 193, 136, 123, 2, 146,
190, 118, 213, 130, 217, 108, 36, 45, 42, 121, 39, 35, 227, 253, 111, 208, 97, 249,
213, 207, 209, 59, 143, 150, 19, 88, 230, 173, 186, 74,
],
),
(
&[],
vec![
56, 176, 96, 167, 81, 172, 150, 56, 76, 217, 50, 126, 177, 177, 227, 106, 33, 253,
183, 17, 20, 190, 7, 67, 76, 12, 199, 191, 99, 246, 225, 218, 39, 78, 222, 191,
231, 111, 101, 251, 213, 26, 210, 241, 72, 152, 185, 91,
],
),
(
&[
194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8,
45, 114,
],
vec![
170, 126, 84, 2, 141, 91, 106, 70, 80, 53, 98, 101, 184, 3, 34, 146, 130, 238, 146,
221, 113, 197, 154, 91, 4, 208, 229, 15, 8, 179, 51, 29, 224, 200, 187, 127, 9,
243, 29, 171, 189, 124, 60, 39, 3, 74, 171, 156,
],
),
];
for (data, expected) in test_vectors {
let h = HashFunction::Sha384;
let res = h.hash(data);
assert_eq!(res, expected);
}
let test_vectors = vec![
(
"Hello World!".as_bytes(),
vec![
191, 215, 108, 14, 187, 208, 6, 254, 229, 131, 65, 5, 71, 193, 136, 123, 2, 146,
190, 118, 213, 130, 217, 108, 36, 45, 42, 121, 39, 35, 227, 253, 111, 208, 97, 249,
213, 207, 209, 59, 143, 150, 19, 88, 230, 173, 186, 74,
],
),
(
&[],
vec![
56, 176, 96, 167, 81, 172, 150, 56, 76, 217, 50, 126, 177, 177, 227, 106, 33, 253,
183, 17, 20, 190, 7, 67, 76, 12, 199, 191, 99, 246, 225, 218, 39, 78, 222, 191,
231, 111, 101, 251, 213, 26, 210, 241, 72, 152, 185, 91,
],
),
(
&[
194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8,
45, 114,
],
vec![
170, 126, 84, 2, 141, 91, 106, 70, 80, 53, 98, 101, 184, 3, 34, 146, 130, 238, 146,
221, 113, 197, 154, 91, 4, 208, 229, 15, 8, 179, 51, 29, 224, 200, 187, 127, 9,
243, 29, 171, 189, 124, 60, 39, 3, 74, 171, 156,
],
),
];
for (data, expected) in test_vectors {
let h = HashFunction::Sha384;
let res = h.hash(data);
assert_eq!(res, expected);
}
} }
#[test] #[test]
fn test_hmac_sha384() { fn test_hmac_sha384() {
let test_vectors = vec![
(
vec![
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
],
vec![72, 105, 32, 84, 104, 101, 114, 101],
vec![
175, 208, 57, 68, 216, 72, 149, 98, 107, 8, 37, 244, 171, 70, 144, 127, 21, 249,
218, 219, 228, 16, 30, 198, 130, 170, 3, 76, 124, 235, 197, 156, 250, 234, 158,
169, 7, 110, 222, 127, 74, 241, 82, 232, 178, 250, 156, 182,
],
),
(
vec![74, 101, 102, 101],
vec![
119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111,
114, 32, 110, 111, 116, 104, 105, 110, 103, 63,
],
vec![
175, 69, 210, 227, 118, 72, 64, 49, 97, 127, 120, 210, 181, 138, 107, 27, 156, 126,
244, 100, 245, 160, 27, 71, 228, 46, 195, 115, 99, 34, 68, 94, 142, 34, 64, 202,
94, 105, 226, 199, 139, 50, 57, 236, 250, 178, 22, 73,
],
),
(
vec![
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170,
],
vec![
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221,
],
vec![
136, 6, 38, 8, 211, 230, 173, 138, 10, 162, 172, 224, 20, 200, 168, 111, 10, 166,
53, 217, 71, 172, 159, 235, 232, 62, 244, 229, 89, 102, 20, 75, 42, 90, 179, 157,
193, 56, 20, 185, 78, 58, 182, 225, 1, 163, 79, 39,
],
),
(
vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25,
],
vec![
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205,
],
vec![
62, 138, 105, 183, 120, 60, 37, 133, 25, 51, 171, 98, 144, 175, 108, 167, 122, 153,
129, 72, 8, 80, 0, 156, 197, 87, 124, 110, 31, 87, 59, 78, 104, 1, 221, 35, 196,
167, 214, 121, 204, 248, 163, 134, 198, 116, 207, 251,
],
),
];
for (key, data, expected) in test_vectors {
let h = HashFunction::Sha384;
let res = h.hmac(&key, &data).unwrap();
assert_eq!(res, expected);
}
let test_vectors = vec![
(
vec![
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
],
vec![72, 105, 32, 84, 104, 101, 114, 101],
vec![
175, 208, 57, 68, 216, 72, 149, 98, 107, 8, 37, 244, 171, 70, 144, 127, 21, 249,
218, 219, 228, 16, 30, 198, 130, 170, 3, 76, 124, 235, 197, 156, 250, 234, 158,
169, 7, 110, 222, 127, 74, 241, 82, 232, 178, 250, 156, 182,
],
),
(
vec![74, 101, 102, 101],
vec![
119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111,
114, 32, 110, 111, 116, 104, 105, 110, 103, 63,
],
vec![
175, 69, 210, 227, 118, 72, 64, 49, 97, 127, 120, 210, 181, 138, 107, 27, 156, 126,
244, 100, 245, 160, 27, 71, 228, 46, 195, 115, 99, 34, 68, 94, 142, 34, 64, 202,
94, 105, 226, 199, 139, 50, 57, 236, 250, 178, 22, 73,
],
),
(
vec![
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170,
],
vec![
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221,
],
vec![
136, 6, 38, 8, 211, 230, 173, 138, 10, 162, 172, 224, 20, 200, 168, 111, 10, 166,
53, 217, 71, 172, 159, 235, 232, 62, 244, 229, 89, 102, 20, 75, 42, 90, 179, 157,
193, 56, 20, 185, 78, 58, 182, 225, 1, 163, 79, 39,
],
),
(
vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25,
],
vec![
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205,
],
vec![
62, 138, 105, 183, 120, 60, 37, 133, 25, 51, 171, 98, 144, 175, 108, 167, 122, 153,
129, 72, 8, 80, 0, 156, 197, 87, 124, 110, 31, 87, 59, 78, 104, 1, 221, 35, 196,
167, 214, 121, 204, 248, 163, 134, 198, 116, 207, 251,
],
),
];
for (key, data, expected) in test_vectors {
let h = HashFunction::Sha384;
let res = h.hmac(&key, &data).unwrap();
assert_eq!(res, expected);
}
} }
#[test] #[test]
fn test_hash_sha512() { fn test_hash_sha512() {
let test_vectors = vec![
(
"Hello World!".as_bytes(),
vec![
134, 24, 68, 214, 112, 78, 133, 115, 254, 195, 77, 150, 126, 32, 188, 254, 243,
212, 36, 207, 72, 190, 4, 230, 220, 8, 242, 189, 88, 199, 41, 116, 51, 113, 1, 94,
173, 137, 28, 195, 207, 28, 157, 52, 180, 146, 100, 181, 16, 117, 27, 31, 249, 229,
55, 147, 123, 196, 107, 93, 111, 244, 236, 200,
],
),
(
&[],
vec![
207, 131, 225, 53, 126, 239, 184, 189, 241, 84, 40, 80, 214, 109, 128, 7, 214, 32,
228, 5, 11, 87, 21, 220, 131, 244, 169, 33, 211, 108, 233, 206, 71, 208, 209, 60,
93, 133, 242, 176, 255, 131, 24, 210, 135, 126, 236, 47, 99, 185, 49, 189, 71, 65,
122, 129, 165, 56, 50, 122, 249, 39, 218, 62,
],
),
(
&[
194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8,
45, 114,
],
vec![
58, 93, 210, 174, 119, 179, 246, 25, 14, 148, 182, 109, 28, 14, 16, 80, 45, 231,
104, 169, 130, 43, 39, 221, 12, 112, 85, 159, 123, 6, 227, 35, 61, 24, 158, 190,
162, 11, 247, 204, 98, 41, 242, 5, 52, 116, 149, 220, 124, 82, 159, 181, 74, 210,
85, 190, 59, 130, 209, 8, 181, 247, 192, 65,
],
),
];
for (data, expected) in test_vectors {
let h = HashFunction::Sha512;
let res = h.hash(data);
assert_eq!(res, expected);
}
let test_vectors = vec![
(
"Hello World!".as_bytes(),
vec![
134, 24, 68, 214, 112, 78, 133, 115, 254, 195, 77, 150, 126, 32, 188, 254, 243,
212, 36, 207, 72, 190, 4, 230, 220, 8, 242, 189, 88, 199, 41, 116, 51, 113, 1, 94,
173, 137, 28, 195, 207, 28, 157, 52, 180, 146, 100, 181, 16, 117, 27, 31, 249, 229,
55, 147, 123, 196, 107, 93, 111, 244, 236, 200,
],
),
(
&[],
vec![
207, 131, 225, 53, 126, 239, 184, 189, 241, 84, 40, 80, 214, 109, 128, 7, 214, 32,
228, 5, 11, 87, 21, 220, 131, 244, 169, 33, 211, 108, 233, 206, 71, 208, 209, 60,
93, 133, 242, 176, 255, 131, 24, 210, 135, 126, 236, 47, 99, 185, 49, 189, 71, 65,
122, 129, 165, 56, 50, 122, 249, 39, 218, 62,
],
),
(
&[
194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8,
45, 114,
],
vec![
58, 93, 210, 174, 119, 179, 246, 25, 14, 148, 182, 109, 28, 14, 16, 80, 45, 231,
104, 169, 130, 43, 39, 221, 12, 112, 85, 159, 123, 6, 227, 35, 61, 24, 158, 190,
162, 11, 247, 204, 98, 41, 242, 5, 52, 116, 149, 220, 124, 82, 159, 181, 74, 210,
85, 190, 59, 130, 209, 8, 181, 247, 192, 65,
],
),
];
for (data, expected) in test_vectors {
let h = HashFunction::Sha512;
let res = h.hash(data);
assert_eq!(res, expected);
}
} }
#[test] #[test]
fn test_hmac_sha512() { fn test_hmac_sha512() {
let test_vectors = vec![
(
vec![
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
],
vec![72, 105, 32, 84, 104, 101, 114, 101],
vec![
135, 170, 124, 222, 165, 239, 97, 157, 79, 240, 180, 36, 26, 29, 108, 176, 35, 121,
244, 226, 206, 78, 194, 120, 122, 208, 179, 5, 69, 225, 124, 222, 218, 168, 51,
183, 214, 184, 167, 2, 3, 139, 39, 78, 174, 163, 244, 228, 190, 157, 145, 78, 235,
97, 241, 112, 46, 105, 108, 32, 58, 18, 104, 84,
],
),
(
vec![74, 101, 102, 101],
vec![
119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111,
114, 32, 110, 111, 116, 104, 105, 110, 103, 63,
],
vec![
22, 75, 122, 123, 252, 248, 25, 226, 227, 149, 251, 231, 59, 86, 224, 163, 135,
189, 100, 34, 46, 131, 31, 214, 16, 39, 12, 215, 234, 37, 5, 84, 151, 88, 191, 117,
192, 90, 153, 74, 109, 3, 79, 101, 248, 240, 230, 253, 202, 234, 177, 163, 77, 74,
107, 75, 99, 110, 7, 10, 56, 188, 231, 55,
],
),
(
vec![
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170,
],
vec![
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221,
],
vec![
250, 115, 176, 8, 157, 86, 162, 132, 239, 176, 240, 117, 108, 137, 11, 233, 177,
181, 219, 221, 142, 232, 26, 54, 85, 248, 62, 51, 178, 39, 157, 57, 191, 62, 132,
130, 121, 167, 34, 200, 6, 180, 133, 164, 126, 103, 200, 7, 185, 70, 163, 55, 190,
232, 148, 38, 116, 39, 136, 89, 225, 50, 146, 251,
],
),
(
vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25,
],
vec![
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205,
],
vec![
176, 186, 70, 86, 55, 69, 140, 105, 144, 229, 168, 197, 246, 29, 74, 247, 229, 118,
217, 127, 249, 75, 135, 45, 231, 111, 128, 80, 54, 30, 227, 219, 169, 28, 165, 193,
26, 162, 94, 180, 214, 121, 39, 92, 197, 120, 128, 99, 165, 241, 151, 65, 18, 12,
79, 45, 226, 173, 235, 235, 16, 162, 152, 221,
],
),
];
for (key, data, expected) in test_vectors {
let h = HashFunction::Sha512;
let res = h.hmac(&key, &data).unwrap();
assert_eq!(res, expected);
}
let test_vectors = vec![
(
vec![
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
],
vec![72, 105, 32, 84, 104, 101, 114, 101],
vec![
135, 170, 124, 222, 165, 239, 97, 157, 79, 240, 180, 36, 26, 29, 108, 176, 35, 121,
244, 226, 206, 78, 194, 120, 122, 208, 179, 5, 69, 225, 124, 222, 218, 168, 51,
183, 214, 184, 167, 2, 3, 139, 39, 78, 174, 163, 244, 228, 190, 157, 145, 78, 235,
97, 241, 112, 46, 105, 108, 32, 58, 18, 104, 84,
],
),
(
vec![74, 101, 102, 101],
vec![
119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111,
114, 32, 110, 111, 116, 104, 105, 110, 103, 63,
],
vec![
22, 75, 122, 123, 252, 248, 25, 226, 227, 149, 251, 231, 59, 86, 224, 163, 135,
189, 100, 34, 46, 131, 31, 214, 16, 39, 12, 215, 234, 37, 5, 84, 151, 88, 191, 117,
192, 90, 153, 74, 109, 3, 79, 101, 248, 240, 230, 253, 202, 234, 177, 163, 77, 74,
107, 75, 99, 110, 7, 10, 56, 188, 231, 55,
],
),
(
vec![
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170,
],
vec![
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
221, 221,
],
vec![
250, 115, 176, 8, 157, 86, 162, 132, 239, 176, 240, 117, 108, 137, 11, 233, 177,
181, 219, 221, 142, 232, 26, 54, 85, 248, 62, 51, 178, 39, 157, 57, 191, 62, 132,
130, 121, 167, 34, 200, 6, 180, 133, 164, 126, 103, 200, 7, 185, 70, 163, 55, 190,
232, 148, 38, 116, 39, 136, 89, 225, 50, 146, 251,
],
),
(
vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25,
],
vec![
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
205, 205,
],
vec![
176, 186, 70, 86, 55, 69, 140, 105, 144, 229, 168, 197, 246, 29, 74, 247, 229, 118,
217, 127, 249, 75, 135, 45, 231, 111, 128, 80, 54, 30, 227, 219, 169, 28, 165, 193,
26, 162, 94, 180, 214, 121, 39, 92, 197, 120, 128, 99, 165, 241, 151, 65, 18, 12,
79, 45, 226, 173, 235, 235, 16, 162, 152, 221,
],
),
];
for (key, data, expected) in test_vectors {
let h = HashFunction::Sha512;
let res = h.hmac(&key, &data).unwrap();
assert_eq!(res, expected);
}
} }

42
acme_common/src/tests/idna.rs

@ -2,41 +2,41 @@ use crate::to_idna;
#[test] #[test]
fn test_no_idna() { fn test_no_idna() {
let idna_res = to_idna("HeLo.example.com");
assert!(idna_res.is_ok());
assert_eq!(idna_res.unwrap(), "helo.example.com");
let idna_res = to_idna("HeLo.example.com");
assert!(idna_res.is_ok());
assert_eq!(idna_res.unwrap(), "helo.example.com");
} }
#[test] #[test]
fn test_simple_idna() { fn test_simple_idna() {
let idna_res = to_idna("Hélo.Example.com");
assert!(idna_res.is_ok());
assert_eq!(idna_res.unwrap(), "xn--hlo-bma.example.com");
let idna_res = to_idna("Hélo.Example.com");
assert!(idna_res.is_ok());
assert_eq!(idna_res.unwrap(), "xn--hlo-bma.example.com");
} }
#[test] #[test]
fn test_multiple_idna() { fn test_multiple_idna() {
let idna_res = to_idna("ns1.hÉlo.aç-éièè.example.com");
assert!(idna_res.is_ok());
assert_eq!(
idna_res.unwrap(),
"ns1.xn--hlo-bma.xn--a-i-2lahae.example.com"
);
let idna_res = to_idna("ns1.hÉlo.aç-éièè.example.com");
assert!(idna_res.is_ok());
assert_eq!(
idna_res.unwrap(),
"ns1.xn--hlo-bma.xn--a-i-2lahae.example.com"
);
} }
#[test] #[test]
fn test_already_idna() { fn test_already_idna() {
let idna_res = to_idna("xn--hlo-bma.example.com");
assert!(idna_res.is_ok());
assert_eq!(idna_res.unwrap(), "xn--hlo-bma.example.com");
let idna_res = to_idna("xn--hlo-bma.example.com");
assert!(idna_res.is_ok());
assert_eq!(idna_res.unwrap(), "xn--hlo-bma.example.com");
} }
#[test] #[test]
fn test_mixed_idna_parts() { fn test_mixed_idna_parts() {
let idna_res = to_idna("ns1.xn--hlo-bma.aç-éièè.example.com");
assert!(idna_res.is_ok());
assert_eq!(
idna_res.unwrap(),
"ns1.xn--hlo-bma.xn--a-i-2lahae.example.com"
);
let idna_res = to_idna("ns1.xn--hlo-bma.aç-éièè.example.com");
assert!(idna_res.is_ok());
assert_eq!(
idna_res.unwrap(),
"ns1.xn--hlo-bma.xn--a-i-2lahae.example.com"
);
} }

42
acme_common/src/tests/jws_signature_algorithm.rs

@ -4,59 +4,59 @@ const TEST_DATA: &'static [u8] = &[72, 101, 108, 108, 111, 32, 119, 111, 114, 10
#[test] #[test]
fn test_rs256_sign_rsa2048() { fn test_rs256_sign_rsa2048() {
let k = gen_keypair(KeyType::Rsa2048).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA).unwrap();
let k = gen_keypair(KeyType::Rsa2048).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA).unwrap();
} }
#[test] #[test]
fn test_rs256_sign_rsa4096() { fn test_rs256_sign_rsa4096() {
let k = gen_keypair(KeyType::Rsa4096).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA).unwrap();
let k = gen_keypair(KeyType::Rsa4096).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA).unwrap();
} }
#[test] #[test]
fn test_rs256_sign_ecdsa() { fn test_rs256_sign_ecdsa() {
let k = gen_keypair(KeyType::EcdsaP256).unwrap();
let res = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA);
assert!(res.is_err());
let k = gen_keypair(KeyType::EcdsaP256).unwrap();
let res = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA);
assert!(res.is_err());
} }
#[test] #[test]
fn test_es256_sign_p256() { fn test_es256_sign_p256() {
let k = gen_keypair(KeyType::EcdsaP256).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Es256, TEST_DATA).unwrap();
let k = gen_keypair(KeyType::EcdsaP256).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Es256, TEST_DATA).unwrap();
} }
#[test] #[test]
fn test_es256_sign_p384() { fn test_es256_sign_p384() {
let k = gen_keypair(KeyType::EcdsaP384).unwrap();
let res = k.sign(&JwsSignatureAlgorithm::Es256, TEST_DATA);
assert!(res.is_err());
let k = gen_keypair(KeyType::EcdsaP384).unwrap();
let res = k.sign(&JwsSignatureAlgorithm::Es256, TEST_DATA);
assert!(res.is_err());
} }
#[test] #[test]
fn test_es384_sign_p384() { fn test_es384_sign_p384() {
let k = gen_keypair(KeyType::EcdsaP384).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Es384, TEST_DATA).unwrap();
let k = gen_keypair(KeyType::EcdsaP384).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Es384, TEST_DATA).unwrap();
} }
#[test] #[test]
fn test_es384_sign_p256() { fn test_es384_sign_p256() {
let k = gen_keypair(KeyType::EcdsaP256).unwrap();
let res = k.sign(&JwsSignatureAlgorithm::Es384, TEST_DATA);
assert!(res.is_err());
let k = gen_keypair(KeyType::EcdsaP256).unwrap();
let res = k.sign(&JwsSignatureAlgorithm::Es384, TEST_DATA);
assert!(res.is_err());
} }
#[cfg(ed25519)] #[cfg(ed25519)]
#[test] #[test]
fn test_ed25519_sign() { fn test_ed25519_sign() {
let k = gen_keypair(KeyType::Ed25519).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Ed25519, TEST_DATA).unwrap();
let k = gen_keypair(KeyType::Ed25519).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Ed25519, TEST_DATA).unwrap();
} }
#[cfg(ed448)] #[cfg(ed448)]
#[test] #[test]
fn test_ed448_sign() { fn test_ed448_sign() {
let k = gen_keypair(KeyType::Ed448).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Ed448, TEST_DATA).unwrap();
let k = gen_keypair(KeyType::Ed448).unwrap();
let _ = k.sign(&JwsSignatureAlgorithm::Ed448, TEST_DATA).unwrap();
} }

146
acmed/build.rs

@ -8,123 +8,123 @@ use std::io::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
macro_rules! set_rustc_env_var { macro_rules! set_rustc_env_var {
($name: expr, $value: expr) => {{
println!("cargo:rustc-env={}={}", $name, $value);
}};
($name: expr, $value: expr) => {{
println!("cargo:rustc-env={}={}", $name, $value);
}};
} }
macro_rules! set_env_var_if_absent { macro_rules! set_env_var_if_absent {
($name: expr, $default_value: expr) => {{
if let Err(_) = env::var($name) {
set_rustc_env_var!($name, $default_value);
}
}};
($name: expr, $default_value: expr) => {{
if let Err(_) = env::var($name) {
set_rustc_env_var!($name, $default_value);
}
}};
} }
macro_rules! set_specific_path_if_absent { macro_rules! set_specific_path_if_absent {
($env_name: expr, $env_default: expr, $with_dir: expr, $name: expr, $default_value: expr) => {{
let prefix = env::var($env_name).unwrap_or(String::from($env_default));
let mut value = PathBuf::new();
value.push(prefix);
if ($with_dir) {
value.push("acmed");
}
value.push($default_value);
set_env_var_if_absent!($name, value.to_str().unwrap());
}};
($env_name: expr, $env_default: expr, $with_dir: expr, $name: expr, $default_value: expr) => {{
let prefix = env::var($env_name).unwrap_or(String::from($env_default));
let mut value = PathBuf::new();
value.push(prefix);
if ($with_dir) {
value.push("acmed");
}
value.push($default_value);
set_env_var_if_absent!($name, value.to_str().unwrap());
}};
} }
macro_rules! set_data_path_if_absent { macro_rules! set_data_path_if_absent {
($name: expr, $default_value: expr) => {{
set_specific_path_if_absent!("VARLIBDIR", "/var/lib", true, $name, $default_value);
}};
($name: expr, $default_value: expr) => {{
set_specific_path_if_absent!("VARLIBDIR", "/var/lib", true, $name, $default_value);
}};
} }
macro_rules! set_cfg_path_if_absent { macro_rules! set_cfg_path_if_absent {
($name: expr, $default_value: expr) => {{
set_specific_path_if_absent!("SYSCONFDIR", "/etc", true, $name, $default_value);
}};
($name: expr, $default_value: expr) => {{
set_specific_path_if_absent!("SYSCONFDIR", "/etc", true, $name, $default_value);
}};
} }
macro_rules! set_runstate_path_if_absent { macro_rules! set_runstate_path_if_absent {
($name: expr, $default_value: expr) => {{
set_specific_path_if_absent!("RUNSTATEDIR", "/run", false, $name, $default_value);
}};
($name: expr, $default_value: expr) => {{
set_specific_path_if_absent!("RUNSTATEDIR", "/run", false, $name, $default_value);
}};
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Lock { pub struct Lock {
package: Vec<Package>,
package: Vec<Package>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
struct Package { struct Package {
name: String,
version: String,
name: String,
version: String,
} }
struct Error; struct Error;
impl From<std::io::Error> for Error { impl From<std::io::Error> for Error {
fn from(_error: std::io::Error) -> Self {
Error {}
}
fn from(_error: std::io::Error) -> Self {
Error {}
}
} }
impl From<toml::de::Error> for Error { impl From<toml::de::Error> for Error {
fn from(_error: toml::de::Error) -> Self {
Error {}
}
fn from(_error: toml::de::Error) -> Self {
Error {}
}
} }
fn get_lock() -> Result<Lock, Error> { fn get_lock() -> Result<Lock, Error> {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop();
path.push("Cargo.lock");
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let ret: Lock = toml::from_str(&contents)?;
Ok(ret)
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop();
path.push("Cargo.lock");
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let ret: Lock = toml::from_str(&contents)?;
Ok(ret)
} }
fn set_lock() { fn set_lock() {
let lock = match get_lock() {
Ok(l) => l,
Err(_) => {
return;
}
};
for p in lock.package.iter() {
if p.name == "attohttpc" {
let agent = format!("{}/{}", p.name, p.version);
set_rustc_env_var!("ACMED_HTTP_LIB_AGENT", agent);
set_rustc_env_var!("ACMED_HTTP_LIB_NAME", p.name);
set_rustc_env_var!("ACMED_HTTP_LIB_VERSION", p.version);
return;
}
}
let lock = match get_lock() {
Ok(l) => l,
Err(_) => {
return;
}
};
for p in lock.package.iter() {
if p.name == "attohttpc" {
let agent = format!("{}/{}", p.name, p.version);
set_rustc_env_var!("ACMED_HTTP_LIB_AGENT", agent);
set_rustc_env_var!("ACMED_HTTP_LIB_NAME", p.name);
set_rustc_env_var!("ACMED_HTTP_LIB_VERSION", p.version);
return;
}
}
} }
fn set_target() { fn set_target() {
if let Ok(target) = env::var("TARGET") {
set_rustc_env_var!("ACMED_TARGET", target);
};
if let Ok(target) = env::var("TARGET") {
set_rustc_env_var!("ACMED_TARGET", target);
};
} }
fn set_default_values() { fn set_default_values() {
set_data_path_if_absent!("ACMED_DEFAULT_ACCOUNTS_DIR", "accounts");
set_data_path_if_absent!("ACMED_DEFAULT_CERT_DIR", "certs");
set_env_var_if_absent!(
"ACMED_DEFAULT_CERT_FORMAT",
"{ name }_{ key_type }.{ file_type }.{ ext }"
);
set_cfg_path_if_absent!("ACMED_DEFAULT_CONFIG_FILE", "acmed.toml");
set_runstate_path_if_absent!("ACMED_DEFAULT_PID_FILE", "acmed.pid");
set_data_path_if_absent!("ACMED_DEFAULT_ACCOUNTS_DIR", "accounts");
set_data_path_if_absent!("ACMED_DEFAULT_CERT_DIR", "certs");
set_env_var_if_absent!(
"ACMED_DEFAULT_CERT_FORMAT",
"{ name }_{ key_type }.{ file_type }.{ ext }"
);
set_cfg_path_if_absent!("ACMED_DEFAULT_CONFIG_FILE", "acmed.toml");
set_runstate_path_if_absent!("ACMED_DEFAULT_PID_FILE", "acmed.pid");
} }
fn main() { fn main() {
set_target();
set_lock();
set_default_values();
set_target();
set_lock();
set_default_values();
} }

518
acmed/src/account.rs

@ -14,309 +14,309 @@ mod storage;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ExternalAccount { pub struct ExternalAccount {
pub identifier: String,
pub key: Vec<u8>,
pub signature_algorithm: JwsSignatureAlgorithm,
pub identifier: String,
pub key: Vec<u8>,
pub signature_algorithm: JwsSignatureAlgorithm,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum AccountContactType { pub enum AccountContactType {
Mailfrom,
Mailfrom,
} }
impl FromStr for AccountContactType { impl FromStr for AccountContactType {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"mailfrom" => Ok(AccountContactType::Mailfrom),
_ => Err(format!("{}: unknown contact type.", s).into()),
}
}
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"mailfrom" => Ok(AccountContactType::Mailfrom),
_ => Err(format!("{}: unknown contact type.", s).into()),
}
}
} }
impl fmt::Display for AccountContactType { impl fmt::Display for AccountContactType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
AccountContactType::Mailfrom => "mailfrom",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
AccountContactType::Mailfrom => "mailfrom",
};
write!(f, "{}", s)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct AccountKey { pub struct AccountKey {
pub creation_date: SystemTime,
pub key: KeyPair,
pub signature_algorithm: JwsSignatureAlgorithm,
pub creation_date: SystemTime,
pub key: KeyPair,
pub signature_algorithm: JwsSignatureAlgorithm,
} }
impl AccountKey { impl AccountKey {
fn new(key_type: KeyType, signature_algorithm: JwsSignatureAlgorithm) -> Result<Self, Error> {
Ok(AccountKey {
creation_date: SystemTime::now(),
key: gen_keypair(key_type)?,
signature_algorithm,
})
}
fn new(key_type: KeyType, signature_algorithm: JwsSignatureAlgorithm) -> Result<Self, Error> {
Ok(AccountKey {
creation_date: SystemTime::now(),
key: gen_keypair(key_type)?,
signature_algorithm,
})
}
} }
#[derive(Clone, Debug, Hash)] #[derive(Clone, Debug, Hash)]
pub struct AccountEndpoint { pub struct AccountEndpoint {
pub creation_date: SystemTime,
pub account_url: String,
pub orders_url: String,
pub key_hash: Vec<u8>,
pub contacts_hash: Vec<u8>,
pub external_account_hash: Vec<u8>,
pub creation_date: SystemTime,
pub account_url: String,
pub orders_url: String,
pub key_hash: Vec<u8>,
pub contacts_hash: Vec<u8>,
pub external_account_hash: Vec<u8>,
} }
impl AccountEndpoint { impl AccountEndpoint {
pub fn new() -> Self {
AccountEndpoint {
creation_date: SystemTime::UNIX_EPOCH,
account_url: String::new(),
orders_url: String::new(),
key_hash: Vec::new(),
contacts_hash: Vec::new(),
external_account_hash: Vec::new(),
}
}
pub fn new() -> Self {
AccountEndpoint {
creation_date: SystemTime::UNIX_EPOCH,
account_url: String::new(),
orders_url: String::new(),
key_hash: Vec::new(),
contacts_hash: Vec::new(),
external_account_hash: Vec::new(),
}
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Account { pub struct Account {
pub name: String,
pub endpoints: HashMap<String, AccountEndpoint>,
pub contacts: Vec<contact::AccountContact>,
pub current_key: AccountKey,
pub past_keys: Vec<AccountKey>,
pub file_manager: FileManager,
pub external_account: Option<ExternalAccount>,
pub name: String,
pub endpoints: HashMap<String, AccountEndpoint>,
pub contacts: Vec<contact::AccountContact>,
pub current_key: AccountKey,
pub past_keys: Vec<AccountKey>,
pub file_manager: FileManager,
pub external_account: Option<ExternalAccount>,
} }
impl HasLogger for Account { impl HasLogger for Account {
fn warn(&self, msg: &str) {
log::warn!("account \"{}\": {}", &self.name, msg);
}
fn warn(&self, msg: &str) {
log::warn!("account \"{}\": {}", &self.name, msg);
}
fn info(&self, msg: &str) {
log::info!("account \"{}\": {}", &self.name, msg);
}
fn info(&self, msg: &str) {
log::info!("account \"{}\": {}", &self.name, msg);
}
fn debug(&self, msg: &str) {
log::debug!("account \"{}\": {}", &self.name, msg);
}
fn debug(&self, msg: &str) {
log::debug!("account \"{}\": {}", &self.name, msg);
}
fn trace(&self, msg: &str) {
log::trace!("account \"{}\": {}", &self.name, msg);
}
fn trace(&self, msg: &str) {
log::trace!("account \"{}\": {}", &self.name, msg);
}
} }
impl Account { impl Account {
pub fn get_endpoint_mut(&mut self, endpoint_name: &str) -> Result<&mut AccountEndpoint, Error> {
match self.endpoints.get_mut(endpoint_name) {
Some(ep) => Ok(ep),
None => {
let msg = format!(
"\"{}\": unknown endpoint for account \"{}\"",
endpoint_name, self.name
);
Err(msg.into())
}
}
}
pub fn get_endpoint(&self, endpoint_name: &str) -> Result<&AccountEndpoint, Error> {
match self.endpoints.get(endpoint_name) {
Some(ep) => Ok(ep),
None => {
let msg = format!(
"\"{}\": unknown endpoint for account \"{}\"",
endpoint_name, self.name
);
Err(msg.into())
}
}
}
pub fn get_past_key(&self, key_hash: &[u8]) -> Result<&AccountKey, Error> {
let key_hash = key_hash.to_vec();
for key in &self.past_keys {
let past_key_hash = hash_key(key)?;
if past_key_hash == key_hash {
return Ok(key);
}
}
Err("key not found".into())
}
pub fn load(
file_manager: &FileManager,
name: &str,
contacts: &[(String, String)],
key_type: &Option<String>,
signature_algorithm: &Option<String>,
external_account: &Option<ExternalAccount>,
) -> Result<Self, Error> {
let contacts = contacts
.iter()
.map(|(k, v)| contact::AccountContact::new(k, v))
.collect::<Result<Vec<contact::AccountContact>, Error>>()?;
let key_type = match key_type {
Some(kt) => kt.parse()?,
None => crate::DEFAULT_ACCOUNT_KEY_TYPE,
};
let signature_algorithm = match signature_algorithm {
Some(sa) => sa.parse()?,
None => key_type.get_default_signature_alg(),
};
key_type.check_alg_compatibility(&signature_algorithm)?;
let account = match storage::fetch(file_manager, name)? {
Some(mut a) => {
a.update_keys(key_type, signature_algorithm)?;
a.contacts = contacts;
a.external_account = external_account.to_owned();
a
}
None => {
let account = Account {
name: name.to_string(),
endpoints: HashMap::new(),
contacts,
current_key: AccountKey::new(key_type, signature_algorithm)?,
past_keys: Vec::new(),
file_manager: file_manager.clone(),
external_account: external_account.to_owned(),
};
account.debug("initializing a new account");
account
}
};
Ok(account)
}
pub fn add_endpoint_name(&mut self, endpoint_name: &str) {
self.endpoints
.entry(endpoint_name.to_string())
.or_insert_with(AccountEndpoint::new);
}
pub fn synchronize(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> {
let acc_ep = self.get_endpoint(&endpoint.name)?;
if !acc_ep.account_url.is_empty() {
if let Some(ec) = &self.external_account {
let external_account_hash = hash_external_account(ec);
if external_account_hash != acc_ep.external_account_hash {
let msg = format!(
"external account changed on endpoint \"{}\"",
&endpoint.name
);
self.info(&msg);
register_account(endpoint, self)?;
return Ok(());
}
}
let ct_hash = hash_contacts(&self.contacts);
let key_hash = hash_key(&self.current_key)?;
let contacts_changed = ct_hash != acc_ep.contacts_hash;
let key_changed = key_hash != acc_ep.key_hash;
if contacts_changed {
update_account_contacts(endpoint, self)?;
}
if key_changed {
update_account_key(endpoint, self)?;
}
} else {
register_account(endpoint, self)?;
}
Ok(())
}
pub fn register(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> {
register_account(endpoint, self)
}
pub fn save(&self) -> Result<(), Error> {
storage::save(&self.file_manager, self)
}
pub fn set_account_url(&mut self, endpoint_name: &str, account_url: &str) -> Result<(), Error> {
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.account_url = account_url.to_string();
Ok(())
}
pub fn set_orders_url(&mut self, endpoint_name: &str, orders_url: &str) -> Result<(), Error> {
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.orders_url = orders_url.to_string();
Ok(())
}
pub fn update_key_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
let key = self.current_key.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.key_hash = hash_key(&key)?;
Ok(())
}
pub fn update_contacts_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
let ct = self.contacts.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.contacts_hash = hash_contacts(&ct);
Ok(())
}
pub fn update_external_account_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
if let Some(ec) = &self.external_account {
let ec = ec.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.external_account_hash = hash_external_account(&ec);
}
Ok(())
}
fn update_keys(
&mut self,
key_type: KeyType,
signature_algorithm: JwsSignatureAlgorithm,
) -> Result<(), Error> {
if self.current_key.key.key_type != key_type
|| self.current_key.signature_algorithm != signature_algorithm
{
self.debug("account key has been changed in the configuration, creating a new one...");
self.past_keys.push(self.current_key.to_owned());
self.current_key = AccountKey::new(key_type, signature_algorithm)?;
self.save()?;
let msg = format!(
"new {} account key created, using {} as signing algorithm",
key_type, signature_algorithm
);
self.info(&msg);
} else {
self.trace("account key is up to date");
}
Ok(())
}
pub fn get_endpoint_mut(&mut self, endpoint_name: &str) -> Result<&mut AccountEndpoint, Error> {
match self.endpoints.get_mut(endpoint_name) {
Some(ep) => Ok(ep),
None => {
let msg = format!(
"\"{}\": unknown endpoint for account \"{}\"",
endpoint_name, self.name
);
Err(msg.into())
}
}
}
pub fn get_endpoint(&self, endpoint_name: &str) -> Result<&AccountEndpoint, Error> {
match self.endpoints.get(endpoint_name) {
Some(ep) => Ok(ep),
None => {
let msg = format!(
"\"{}\": unknown endpoint for account \"{}\"",
endpoint_name, self.name
);
Err(msg.into())
}
}
}
pub fn get_past_key(&self, key_hash: &[u8]) -> Result<&AccountKey, Error> {
let key_hash = key_hash.to_vec();
for key in &self.past_keys {
let past_key_hash = hash_key(key)?;
if past_key_hash == key_hash {
return Ok(key);
}
}
Err("key not found".into())
}
pub fn load(
file_manager: &FileManager,
name: &str,
contacts: &[(String, String)],
key_type: &Option<String>,
signature_algorithm: &Option<String>,
external_account: &Option<ExternalAccount>,
) -> Result<Self, Error> {
let contacts = contacts
.iter()
.map(|(k, v)| contact::AccountContact::new(k, v))
.collect::<Result<Vec<contact::AccountContact>, Error>>()?;
let key_type = match key_type {
Some(kt) => kt.parse()?,
None => crate::DEFAULT_ACCOUNT_KEY_TYPE,
};
let signature_algorithm = match signature_algorithm {
Some(sa) => sa.parse()?,
None => key_type.get_default_signature_alg(),
};
key_type.check_alg_compatibility(&signature_algorithm)?;
let account = match storage::fetch(file_manager, name)? {
Some(mut a) => {
a.update_keys(key_type, signature_algorithm)?;
a.contacts = contacts;
a.external_account = external_account.to_owned();
a
}
None => {
let account = Account {
name: name.to_string(),
endpoints: HashMap::new(),
contacts,
current_key: AccountKey::new(key_type, signature_algorithm)?,
past_keys: Vec::new(),
file_manager: file_manager.clone(),
external_account: external_account.to_owned(),
};
account.debug("initializing a new account");
account
}
};
Ok(account)
}
pub fn add_endpoint_name(&mut self, endpoint_name: &str) {
self.endpoints
.entry(endpoint_name.to_string())
.or_insert_with(AccountEndpoint::new);
}
pub fn synchronize(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> {
let acc_ep = self.get_endpoint(&endpoint.name)?;
if !acc_ep.account_url.is_empty() {
if let Some(ec) = &self.external_account {
let external_account_hash = hash_external_account(ec);
if external_account_hash != acc_ep.external_account_hash {
let msg = format!(
"external account changed on endpoint \"{}\"",
&endpoint.name
);
self.info(&msg);
register_account(endpoint, self)?;
return Ok(());
}
}
let ct_hash = hash_contacts(&self.contacts);
let key_hash = hash_key(&self.current_key)?;
let contacts_changed = ct_hash != acc_ep.contacts_hash;
let key_changed = key_hash != acc_ep.key_hash;
if contacts_changed {
update_account_contacts(endpoint, self)?;
}
if key_changed {
update_account_key(endpoint, self)?;
}
} else {
register_account(endpoint, self)?;
}
Ok(())
}
pub fn register(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> {
register_account(endpoint, self)
}
pub fn save(&self) -> Result<(), Error> {
storage::save(&self.file_manager, self)
}
pub fn set_account_url(&mut self, endpoint_name: &str, account_url: &str) -> Result<(), Error> {
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.account_url = account_url.to_string();
Ok(())
}
pub fn set_orders_url(&mut self, endpoint_name: &str, orders_url: &str) -> Result<(), Error> {
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.orders_url = orders_url.to_string();
Ok(())
}
pub fn update_key_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
let key = self.current_key.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.key_hash = hash_key(&key)?;
Ok(())
}
pub fn update_contacts_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
let ct = self.contacts.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.contacts_hash = hash_contacts(&ct);
Ok(())
}
pub fn update_external_account_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
if let Some(ec) = &self.external_account {
let ec = ec.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.external_account_hash = hash_external_account(&ec);
}
Ok(())
}
fn update_keys(
&mut self,
key_type: KeyType,
signature_algorithm: JwsSignatureAlgorithm,
) -> Result<(), Error> {
if self.current_key.key.key_type != key_type
|| self.current_key.signature_algorithm != signature_algorithm
{
self.debug("account key has been changed in the configuration, creating a new one...");
self.past_keys.push(self.current_key.to_owned());
self.current_key = AccountKey::new(key_type, signature_algorithm)?;
self.save()?;
let msg = format!(
"new {} account key created, using {} as signing algorithm",
key_type, signature_algorithm
);
self.info(&msg);
} else {
self.trace("account key is up to date");
}
Ok(())
}
} }
fn hash_contacts(contacts: &[contact::AccountContact]) -> Vec<u8> { fn hash_contacts(contacts: &[contact::AccountContact]) -> Vec<u8> {
let msg = contacts
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join("")
.into_bytes();
HashFunction::Sha256.hash(&msg)
let msg = contacts
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join("")
.into_bytes();
HashFunction::Sha256.hash(&msg)
} }
fn hash_key(key: &AccountKey) -> Result<Vec<u8>, Error> { fn hash_key(key: &AccountKey) -> Result<Vec<u8>, Error> {
let pem = key.key.public_key_to_pem()?;
Ok(HashFunction::Sha256.hash(&pem))
let pem = key.key.public_key_to_pem()?;
Ok(HashFunction::Sha256.hash(&pem))
} }
fn hash_external_account(ec: &ExternalAccount) -> Vec<u8> { fn hash_external_account(ec: &ExternalAccount) -> Vec<u8> {
let mut msg = ec.key.clone();
msg.extend(ec.identifier.as_bytes());
HashFunction::Sha256.hash(&msg)
let mut msg = ec.key.clone();
msg.extend(ec.identifier.as_bytes());
HashFunction::Sha256.hash(&msg)
} }

130
acmed/src/account/contact.rs

@ -3,10 +3,10 @@ use std::fmt;
use std::str::FromStr; use std::str::FromStr;
fn clean_mailto(value: &str) -> Result<String, Error> { fn clean_mailto(value: &str) -> Result<String, Error> {
// TODO: implement a simple RFC 6068 parser
// - no "hfields"
// - max one "addr-spec" in the "to" component
Ok(value.to_string())
// TODO: implement a simple RFC 6068 parser
// - no "hfields"
// - max one "addr-spec" in the "to" component
Ok(value.to_string())
} }
// TODO: implement other URI shemes // TODO: implement other URI shemes
@ -19,92 +19,92 @@ fn clean_mailto(value: &str) -> Result<String, Error> {
// - P4: sip, sips // - P4: sip, sips
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ContactType { pub enum ContactType {
Mailto,
Mailto,
} }
impl ContactType { impl ContactType {
pub fn clean_value(&self, value: &str) -> Result<String, Error> {
match self {
ContactType::Mailto => clean_mailto(value),
}
}
pub fn clean_value(&self, value: &str) -> Result<String, Error> {
match self {
ContactType::Mailto => clean_mailto(value),
}
}
} }
impl FromStr for ContactType { impl FromStr for ContactType {
type Err = Error;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"mailto" => Ok(ContactType::Mailto),
_ => Err(format!("{}: unknown contact type.", s).into()),
}
}
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"mailto" => Ok(ContactType::Mailto),
_ => Err(format!("{}: unknown contact type.", s).into()),
}
}
} }
impl fmt::Display for ContactType { impl fmt::Display for ContactType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
ContactType::Mailto => "mailto",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
ContactType::Mailto => "mailto",
};
write!(f, "{}", s)
}
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AccountContact { pub struct AccountContact {
pub contact_type: ContactType,
pub value: String,
pub contact_type: ContactType,
pub value: String,
} }
impl AccountContact { impl AccountContact {
pub fn new(contact_type: &str, value: &str) -> Result<Self, Error> {
let contact_type: ContactType = contact_type.parse()?;
let value = contact_type.clean_value(value)?;
Ok(AccountContact {
contact_type,
value,
})
}
pub fn new(contact_type: &str, value: &str) -> Result<Self, Error> {
let contact_type: ContactType = contact_type.parse()?;
let value = contact_type.clean_value(value)?;
Ok(AccountContact {
contact_type,
value,
})
}
} }
impl fmt::Display for AccountContact { impl fmt::Display for AccountContact {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.contact_type, self.value)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.contact_type, self.value)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use super::*;
#[test]
fn test_account_contact_eq() {
let c1 = AccountContact::new("mailto", "derp.derpson@example.com").unwrap();
let c2 = AccountContact::new("mailto", "derp.derpson@example.com").unwrap();
let c3 = AccountContact::new("mailto", "derp@example.com").unwrap();
assert_eq!(c1, c2);
assert_eq!(c2, c1);
assert_ne!(c1, c3);
assert_ne!(c2, c3);
}
#[test]
fn test_account_contact_eq() {
let c1 = AccountContact::new("mailto", "derp.derpson@example.com").unwrap();
let c2 = AccountContact::new("mailto", "derp.derpson@example.com").unwrap();
let c3 = AccountContact::new("mailto", "derp@example.com").unwrap();
assert_eq!(c1, c2);
assert_eq!(c2, c1);
assert_ne!(c1, c3);
assert_ne!(c2, c3);
}
#[test]
fn test_account_contact_in_vec() {
let contacts = vec![
AccountContact::new("mailto", "derp.derpson@example.com").unwrap(),
AccountContact::new("mailto", "derp@example.com").unwrap(),
];
let c = AccountContact::new("mailto", "derp@example.com").unwrap();
assert!(contacts.contains(&c));
}
#[test]
fn test_account_contact_in_vec() {
let contacts = vec![
AccountContact::new("mailto", "derp.derpson@example.com").unwrap(),
AccountContact::new("mailto", "derp@example.com").unwrap(),
];
let c = AccountContact::new("mailto", "derp@example.com").unwrap();
assert!(contacts.contains(&c));
}
#[test]
fn test_account_contact_not_in_vec() {
let contacts = vec![
AccountContact::new("mailto", "derp.derpson@example.com").unwrap(),
AccountContact::new("mailto", "derp@example.com").unwrap(),
];
let c = AccountContact::new("mailto", "derpina@example.com").unwrap();
assert!(!contacts.contains(&c));
}
#[test]
fn test_account_contact_not_in_vec() {
let contacts = vec![
AccountContact::new("mailto", "derp.derpson@example.com").unwrap(),
AccountContact::new("mailto", "derp@example.com").unwrap(),
];
let c = AccountContact::new("mailto", "derpina@example.com").unwrap();
assert!(!contacts.contains(&c));
}
} }

280
acmed/src/account/storage.rs

@ -9,180 +9,180 @@ use std::time::SystemTime;
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct ExternalAccountStorage { pub struct ExternalAccountStorage {
pub identifier: String,
pub key: Vec<u8>,
pub signature_algorithm: String,
pub identifier: String,
pub key: Vec<u8>,
pub signature_algorithm: String,
} }
impl ExternalAccountStorage { impl ExternalAccountStorage {
fn new(external_account: &ExternalAccount) -> Self {
ExternalAccountStorage {
identifier: external_account.identifier.to_owned(),
key: external_account.key.to_owned(),
signature_algorithm: external_account.signature_algorithm.to_string(),
}
}
fn new(external_account: &ExternalAccount) -> Self {
ExternalAccountStorage {
identifier: external_account.identifier.to_owned(),
key: external_account.key.to_owned(),
signature_algorithm: external_account.signature_algorithm.to_string(),
}
}
fn to_generic(&self) -> Result<ExternalAccount, Error> {
Ok(ExternalAccount {
identifier: self.identifier.to_owned(),
key: self.key.to_owned(),
signature_algorithm: self.signature_algorithm.parse()?,
})
}
fn to_generic(&self) -> Result<ExternalAccount, Error> {
Ok(ExternalAccount {
identifier: self.identifier.to_owned(),
key: self.key.to_owned(),
signature_algorithm: self.signature_algorithm.parse()?,
})
}
} }
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
struct AccountKeyStorage { struct AccountKeyStorage {
creation_date: SystemTime,
key: Vec<u8>,
signature_algorithm: String,
creation_date: SystemTime,
key: Vec<u8>,
signature_algorithm: String,
} }
impl AccountKeyStorage { impl AccountKeyStorage {
fn new(key: &AccountKey) -> Result<Self, Error> {
Ok(AccountKeyStorage {
creation_date: key.creation_date,
key: key.key.private_key_to_der()?,
signature_algorithm: key.signature_algorithm.to_string(),
})
}
fn new(key: &AccountKey) -> Result<Self, Error> {
Ok(AccountKeyStorage {
creation_date: key.creation_date,
key: key.key.private_key_to_der()?,
signature_algorithm: key.signature_algorithm.to_string(),
})
}
fn to_generic(&self) -> Result<AccountKey, Error> {
Ok(AccountKey {
creation_date: self.creation_date,
key: KeyPair::from_der(&self.key)?,
signature_algorithm: self.signature_algorithm.parse()?,
})
}
fn to_generic(&self) -> Result<AccountKey, Error> {
Ok(AccountKey {
creation_date: self.creation_date,
key: KeyPair::from_der(&self.key)?,
signature_algorithm: self.signature_algorithm.parse()?,
})
}
} }
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
struct AccountEndpointStorage { struct AccountEndpointStorage {
creation_date: SystemTime,
account_url: String,
orders_url: String,
key_hash: Vec<u8>,
contacts_hash: Vec<u8>,
external_account_hash: Vec<u8>,
creation_date: SystemTime,
account_url: String,
orders_url: String,
key_hash: Vec<u8>,
contacts_hash: Vec<u8>,
external_account_hash: Vec<u8>,
} }
impl AccountEndpointStorage { impl AccountEndpointStorage {
fn new(account_endpoint: &AccountEndpoint) -> Self {
AccountEndpointStorage {
creation_date: account_endpoint.creation_date,
account_url: account_endpoint.account_url.clone(),
orders_url: account_endpoint.orders_url.clone(),
key_hash: account_endpoint.key_hash.clone(),
contacts_hash: account_endpoint.contacts_hash.clone(),
external_account_hash: account_endpoint.external_account_hash.clone(),
}
}
fn new(account_endpoint: &AccountEndpoint) -> Self {
AccountEndpointStorage {
creation_date: account_endpoint.creation_date,
account_url: account_endpoint.account_url.clone(),
orders_url: account_endpoint.orders_url.clone(),
key_hash: account_endpoint.key_hash.clone(),
contacts_hash: account_endpoint.contacts_hash.clone(),
external_account_hash: account_endpoint.external_account_hash.clone(),
}
}
fn to_generic(&self) -> AccountEndpoint {
AccountEndpoint {
creation_date: self.creation_date,
account_url: self.account_url.clone(),
orders_url: self.orders_url.clone(),
key_hash: self.key_hash.clone(),
contacts_hash: self.contacts_hash.clone(),
external_account_hash: self.external_account_hash.clone(),
}
}
fn to_generic(&self) -> AccountEndpoint {
AccountEndpoint {
creation_date: self.creation_date,
account_url: self.account_url.clone(),
orders_url: self.orders_url.clone(),
key_hash: self.key_hash.clone(),
contacts_hash: self.contacts_hash.clone(),
external_account_hash: self.external_account_hash.clone(),
}
}
} }
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
struct AccountStorage { struct AccountStorage {
name: String,
endpoints: HashMap<String, AccountEndpointStorage>,
contacts: Vec<(String, String)>,
current_key: AccountKeyStorage,
past_keys: Vec<AccountKeyStorage>,
external_account: Option<ExternalAccountStorage>,
name: String,
endpoints: HashMap<String, AccountEndpointStorage>,
contacts: Vec<(String, String)>,
current_key: AccountKeyStorage,
past_keys: Vec<AccountKeyStorage>,
external_account: Option<ExternalAccountStorage>,
} }
fn do_fetch(file_manager: &FileManager, name: &str) -> Result<Option<Account>, Error> { fn do_fetch(file_manager: &FileManager, name: &str) -> Result<Option<Account>, Error> {
if account_files_exists(file_manager) {
let data = get_account_data(file_manager)?;
let obj: AccountStorage = bincode::deserialize(&data[..])
.map_err(|e| Error::from(&e.to_string()).prefix(name))?;
let endpoints = obj
.endpoints
.iter()
.map(|(k, v)| (k.clone(), v.to_generic()))
.collect();
let contacts = obj
.contacts
.iter()
.map(|(t, v)| AccountContact::new(t, v))
.collect::<Result<Vec<AccountContact>, Error>>()?;
let current_key = obj.current_key.to_generic()?;
let past_keys = obj
.past_keys
.iter()
.map(|k| k.to_generic())
.collect::<Result<Vec<AccountKey>, Error>>()?;
let external_account = match obj.external_account {
Some(a) => Some(a.to_generic()?),
None => None,
};
Ok(Some(Account {
name: obj.name,
endpoints,
contacts,
current_key,
past_keys,
file_manager: file_manager.clone(),
external_account,
}))
} else {
Ok(None)
}
if account_files_exists(file_manager) {
let data = get_account_data(file_manager)?;
let obj: AccountStorage = bincode::deserialize(&data[..])
.map_err(|e| Error::from(&e.to_string()).prefix(name))?;
let endpoints = obj
.endpoints
.iter()
.map(|(k, v)| (k.clone(), v.to_generic()))
.collect();
let contacts = obj
.contacts
.iter()
.map(|(t, v)| AccountContact::new(t, v))
.collect::<Result<Vec<AccountContact>, Error>>()?;
let current_key = obj.current_key.to_generic()?;
let past_keys = obj
.past_keys
.iter()
.map(|k| k.to_generic())
.collect::<Result<Vec<AccountKey>, Error>>()?;
let external_account = match obj.external_account {
Some(a) => Some(a.to_generic()?),
None => None,
};
Ok(Some(Account {
name: obj.name,
endpoints,
contacts,
current_key,
past_keys,
file_manager: file_manager.clone(),
external_account,
}))
} else {
Ok(None)
}
} }
fn do_save(file_manager: &FileManager, account: &Account) -> Result<(), Error> { fn do_save(file_manager: &FileManager, account: &Account) -> Result<(), Error> {
let endpoints: HashMap<String, AccountEndpointStorage> = account
.endpoints
.iter()
.map(|(k, v)| (k.to_owned(), AccountEndpointStorage::new(v)))
.collect();
let contacts: Vec<(String, String)> = account
.contacts
.iter()
.map(|c| (c.contact_type.to_string(), c.value.to_owned()))
.collect();
let past_keys = account
.past_keys
.iter()
.map(AccountKeyStorage::new)
.collect::<Result<Vec<AccountKeyStorage>, Error>>()?;
let external_account = account
.external_account
.as_ref()
.map(ExternalAccountStorage::new);
let account_storage = AccountStorage {
name: account.name.to_owned(),
endpoints,
contacts,
current_key: AccountKeyStorage::new(&account.current_key)?,
past_keys,
external_account,
};
let encoded: Vec<u8> = bincode::serialize(&account_storage)
.map_err(|e| Error::from(&e.to_string()).prefix(&account.name))?;
set_account_data(file_manager, &encoded)
let endpoints: HashMap<String, AccountEndpointStorage> = account
.endpoints
.iter()
.map(|(k, v)| (k.to_owned(), AccountEndpointStorage::new(v)))
.collect();
let contacts: Vec<(String, String)> = account
.contacts
.iter()
.map(|c| (c.contact_type.to_string(), c.value.to_owned()))
.collect();
let past_keys = account
.past_keys
.iter()
.map(AccountKeyStorage::new)
.collect::<Result<Vec<AccountKeyStorage>, Error>>()?;
let external_account = account
.external_account
.as_ref()
.map(ExternalAccountStorage::new);
let account_storage = AccountStorage {
name: account.name.to_owned(),
endpoints,
contacts,
current_key: AccountKeyStorage::new(&account.current_key)?,
past_keys,
external_account,
};
let encoded: Vec<u8> = bincode::serialize(&account_storage)
.map_err(|e| Error::from(&e.to_string()).prefix(&account.name))?;
set_account_data(file_manager, &encoded)
} }
pub fn fetch(file_manager: &FileManager, name: &str) -> Result<Option<Account>, Error> { pub fn fetch(file_manager: &FileManager, name: &str) -> Result<Option<Account>, Error> {
do_fetch(file_manager, name).map_err(|_| {
format!(
"account \"{}\": unable to load account file: file may be corrupted",
name
)
.into()
})
do_fetch(file_manager, name).map_err(|_| {
format!(
"account \"{}\": unable to load account file: file may be corrupted",
name
)
.into()
})
} }
pub fn save(file_manager: &FileManager, account: &Account) -> Result<(), Error> { pub fn save(file_manager: &FileManager, account: &Account) -> Result<(), Error> {
do_save(file_manager, account).map_err(|e| format!("unable to save account file: {}", e).into())
do_save(file_manager, account).map_err(|e| format!("unable to save account file: {}", e).into())
} }

384
acmed/src/acme_proto.rs

@ -1,6 +1,6 @@
use crate::account::Account; use crate::account::Account;
use crate::acme_proto::structs::{ use crate::acme_proto::structs::{
AcmeError, ApiError, Authorization, AuthorizationStatus, NewOrder, Order, OrderStatus,
AcmeError, ApiError, Authorization, AuthorizationStatus, NewOrder, Order, OrderStatus,
}; };
use crate::certificate::Certificate; use crate::certificate::Certificate;
use crate::endpoint::Endpoint; use crate::endpoint::Endpoint;
@ -21,216 +21,216 @@ pub mod structs;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum Challenge { pub enum Challenge {
Http01,
Dns01,
TlsAlpn01,
Http01,
Dns01,
TlsAlpn01,
} }
impl Challenge { impl Challenge {
pub fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"http-01" => Ok(Challenge::Http01),
"dns-01" => Ok(Challenge::Dns01),
"tls-alpn-01" => Ok(Challenge::TlsAlpn01),
_ => Err(format!("{}: unknown challenge.", s).into()),
}
}
pub fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"http-01" => Ok(Challenge::Http01),
"dns-01" => Ok(Challenge::Dns01),
"tls-alpn-01" => Ok(Challenge::TlsAlpn01),
_ => Err(format!("{}: unknown challenge.", s).into()),
}
}
} }
impl fmt::Display for Challenge { impl fmt::Display for Challenge {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Challenge::Http01 => "http-01",
Challenge::Dns01 => "dns-01",
Challenge::TlsAlpn01 => "tls-alpn-01",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Challenge::Http01 => "http-01",
Challenge::Dns01 => "dns-01",
Challenge::TlsAlpn01 => "tls-alpn-01",
};
write!(f, "{}", s)
}
} }
impl PartialEq<structs::Challenge> for Challenge { impl PartialEq<structs::Challenge> for Challenge {
fn eq(&self, other: &structs::Challenge) -> bool {
matches!(
(self, other),
(Challenge::Http01, structs::Challenge::Http01(_))
| (Challenge::Dns01, structs::Challenge::Dns01(_))
| (Challenge::TlsAlpn01, structs::Challenge::TlsAlpn01(_))
)
}
fn eq(&self, other: &structs::Challenge) -> bool {
matches!(
(self, other),
(Challenge::Http01, structs::Challenge::Http01(_))
| (Challenge::Dns01, structs::Challenge::Dns01(_))
| (Challenge::TlsAlpn01, structs::Challenge::TlsAlpn01(_))
)
}
} }
#[macro_export] #[macro_export]
macro_rules! set_data_builder { macro_rules! set_data_builder {
($account: ident, $endpoint_name: ident, $data: expr) => {
|n: &str, url: &str| {
encode_kid(
&$account.current_key.key,
&$account.current_key.signature_algorithm,
&($account.get_endpoint(&$endpoint_name)?.account_url),
$data,
url,
n,
)
}
};
($account: ident, $endpoint_name: ident, $data: expr) => {
|n: &str, url: &str| {
encode_kid(
&$account.current_key.key,
&$account.current_key.signature_algorithm,
&($account.get_endpoint(&$endpoint_name)?.account_url),
$data,
url,
n,
)
}
};
} }
#[macro_export] #[macro_export]
macro_rules! set_empty_data_builder { macro_rules! set_empty_data_builder {
($account: ident, $endpoint_name: ident) => {
set_data_builder!($account, $endpoint_name, b"")
};
($account: ident, $endpoint_name: ident) => {
set_data_builder!($account, $endpoint_name, b"")
};
} }
pub fn request_certificate( pub fn request_certificate(
cert: &Certificate,
endpoint: &mut Endpoint,
account: &mut Account,
cert: &Certificate,
endpoint: &mut Endpoint,
account: &mut Account,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut hook_datas = vec![];
let endpoint_name = endpoint.name.clone();
// Refresh the directory
http::refresh_directory(endpoint).map_err(HttpError::in_err)?;
// Synchronize the account
account.synchronize(endpoint)?;
// Create a new order
let mut new_reg = false;
let (order, order_url) = loop {
let new_order = NewOrder::new(&cert.identifiers);
let new_order = serde_json::to_string(&new_order)?;
let data_builder = set_data_builder!(account, endpoint_name, new_order.as_bytes());
match http::new_order(endpoint, &data_builder) {
Ok((order, order_url)) => {
if let Some(e) = order.get_error() {
cert.warn(&e.prefix("Error").message);
}
break (order, order_url);
}
Err(e) => {
if !new_reg && e.is_acme_err(AcmeError::AccountDoesNotExist) {
account.register(endpoint)?;
new_reg = true;
} else {
return Err(HttpError::in_err(e));
}
}
};
};
// Begin iter over authorizations
for auth_url in order.authorizations.iter() {
// Fetch the authorization
let data_builder = set_empty_data_builder!(account, endpoint_name);
let auth = http::get_authorization(endpoint, &data_builder, auth_url)
.map_err(HttpError::in_err)?;
if let Some(e) = auth.get_error() {
cert.warn(&e.prefix("error").message);
}
if auth.status == AuthorizationStatus::Valid {
continue;
}
if auth.status != AuthorizationStatus::Pending {
let msg = format!(
"{}: authorization status is {}",
auth.identifier, auth.status
);
return Err(msg.into());
}
// Fetch the associated challenges
let current_identifier = cert.get_identifier_from_str(&auth.identifier.value)?;
let current_challenge = current_identifier.challenge;
for challenge in auth.challenges.iter() {
if current_challenge == *challenge {
let proof = challenge.get_proof(&account.current_key.key)?;
let file_name = challenge.get_file_name();
let identifier = auth.identifier.value.to_owned();
// Call the challenge hook in order to complete it
let mut data = cert.call_challenge_hooks(&file_name, &proof, &identifier)?;
data.0.is_clean_hook = true;
hook_datas.push(data);
// Tell the server the challenge has been completed
let chall_url = challenge.get_url();
let data_builder = set_data_builder!(account, endpoint_name, b"{}");
http::post_jose_no_response(endpoint, &data_builder, &chall_url)
.map_err(HttpError::in_err)?;
}
}
// Pool the authorization in order to see whether or not it is valid
let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |a: &Authorization| a.status == AuthorizationStatus::Valid;
let _ = http::pool_authorization(endpoint, &data_builder, &break_fn, auth_url)
.map_err(HttpError::in_err)?;
for (data, hook_type) in hook_datas.iter() {
cert.call_challenge_hooks_clean(data, (*hook_type).to_owned())?;
}
hook_datas.clear();
}
// End iter over authorizations
// Pool the order in order to see whether or not it is ready
let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |o: &Order| o.status == OrderStatus::Ready;
let order = http::pool_order(endpoint, &data_builder, &break_fn, &order_url)
.map_err(HttpError::in_err)?;
// Finalize the order by sending the CSR
let key_pair = certificate::get_key_pair(cert)?;
let domains: Vec<String> = cert
.identifiers
.iter()
.filter(|e| e.id_type == IdentifierType::Dns)
.map(|e| e.value.to_owned())
.collect();
let ips: Vec<String> = cert
.identifiers
.iter()
.filter(|e| e.id_type == IdentifierType::Ip)
.map(|e| e.value.to_owned())
.collect();
let csr = Csr::new(
&key_pair,
cert.csr_digest,
domains.as_slice(),
ips.as_slice(),
&cert.subject_attributes,
)?;
cert.trace(&format!("new CSR:\n{}", csr.to_pem()?));
let csr = json!({
"csr": csr.to_der_base64()?,
});
let csr = csr.to_string();
let data_builder = set_data_builder!(account, endpoint_name, csr.as_bytes());
let order = http::finalize_order(endpoint, &data_builder, &order.finalize)
.map_err(HttpError::in_err)?;
if let Some(e) = order.get_error() {
cert.warn(&e.prefix("error").message);
}
// Pool the order in order to see whether or not it is valid
let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |o: &Order| o.status == OrderStatus::Valid;
let order = http::pool_order(endpoint, &data_builder, &break_fn, &order_url)
.map_err(HttpError::in_err)?;
// Download the certificate
let crt_url = order
.certificate
.ok_or_else(|| Error::from("no certificate available for download"))?;
let data_builder = set_empty_data_builder!(account, endpoint_name);
let crt =
http::get_certificate(endpoint, &data_builder, &crt_url).map_err(HttpError::in_err)?;
storage::write_certificate(&cert.file_manager, crt.as_bytes())?;
cert.info(&format!(
"certificate renewed (identifiers: {})",
cert.identifier_list()
));
Ok(())
let mut hook_datas = vec![];
let endpoint_name = endpoint.name.clone();
// Refresh the directory
http::refresh_directory(endpoint).map_err(HttpError::in_err)?;
// Synchronize the account
account.synchronize(endpoint)?;
// Create a new order
let mut new_reg = false;
let (order, order_url) = loop {
let new_order = NewOrder::new(&cert.identifiers);
let new_order = serde_json::to_string(&new_order)?;
let data_builder = set_data_builder!(account, endpoint_name, new_order.as_bytes());
match http::new_order(endpoint, &data_builder) {
Ok((order, order_url)) => {
if let Some(e) = order.get_error() {
cert.warn(&e.prefix("Error").message);
}
break (order, order_url);
}
Err(e) => {
if !new_reg && e.is_acme_err(AcmeError::AccountDoesNotExist) {
account.register(endpoint)?;
new_reg = true;
} else {
return Err(HttpError::in_err(e));
}
}
};
};
// Begin iter over authorizations
for auth_url in order.authorizations.iter() {
// Fetch the authorization
let data_builder = set_empty_data_builder!(account, endpoint_name);
let auth = http::get_authorization(endpoint, &data_builder, auth_url)
.map_err(HttpError::in_err)?;
if let Some(e) = auth.get_error() {
cert.warn(&e.prefix("error").message);
}
if auth.status == AuthorizationStatus::Valid {
continue;
}
if auth.status != AuthorizationStatus::Pending {
let msg = format!(
"{}: authorization status is {}",
auth.identifier, auth.status
);
return Err(msg.into());
}
// Fetch the associated challenges
let current_identifier = cert.get_identifier_from_str(&auth.identifier.value)?;
let current_challenge = current_identifier.challenge;
for challenge in auth.challenges.iter() {
if current_challenge == *challenge {
let proof = challenge.get_proof(&account.current_key.key)?;
let file_name = challenge.get_file_name();
let identifier = auth.identifier.value.to_owned();
// Call the challenge hook in order to complete it
let mut data = cert.call_challenge_hooks(&file_name, &proof, &identifier)?;
data.0.is_clean_hook = true;
hook_datas.push(data);
// Tell the server the challenge has been completed
let chall_url = challenge.get_url();
let data_builder = set_data_builder!(account, endpoint_name, b"{}");
http::post_jose_no_response(endpoint, &data_builder, &chall_url)
.map_err(HttpError::in_err)?;
}
}
// Pool the authorization in order to see whether or not it is valid
let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |a: &Authorization| a.status == AuthorizationStatus::Valid;
let _ = http::pool_authorization(endpoint, &data_builder, &break_fn, auth_url)
.map_err(HttpError::in_err)?;
for (data, hook_type) in hook_datas.iter() {
cert.call_challenge_hooks_clean(data, (*hook_type).to_owned())?;
}
hook_datas.clear();
}
// End iter over authorizations
// Pool the order in order to see whether or not it is ready
let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |o: &Order| o.status == OrderStatus::Ready;
let order = http::pool_order(endpoint, &data_builder, &break_fn, &order_url)
.map_err(HttpError::in_err)?;
// Finalize the order by sending the CSR
let key_pair = certificate::get_key_pair(cert)?;
let domains: Vec<String> = cert
.identifiers
.iter()
.filter(|e| e.id_type == IdentifierType::Dns)
.map(|e| e.value.to_owned())
.collect();
let ips: Vec<String> = cert
.identifiers
.iter()
.filter(|e| e.id_type == IdentifierType::Ip)
.map(|e| e.value.to_owned())
.collect();
let csr = Csr::new(
&key_pair,
cert.csr_digest,
domains.as_slice(),
ips.as_slice(),
&cert.subject_attributes,
)?;
cert.trace(&format!("new CSR:\n{}", csr.to_pem()?));
let csr = json!({
"csr": csr.to_der_base64()?,
});
let csr = csr.to_string();
let data_builder = set_data_builder!(account, endpoint_name, csr.as_bytes());
let order = http::finalize_order(endpoint, &data_builder, &order.finalize)
.map_err(HttpError::in_err)?;
if let Some(e) = order.get_error() {
cert.warn(&e.prefix("error").message);
}
// Pool the order in order to see whether or not it is valid
let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |o: &Order| o.status == OrderStatus::Valid;
let order = http::pool_order(endpoint, &data_builder, &break_fn, &order_url)
.map_err(HttpError::in_err)?;
// Download the certificate
let crt_url = order
.certificate
.ok_or_else(|| Error::from("no certificate available for download"))?;
let data_builder = set_empty_data_builder!(account, endpoint_name);
let crt =
http::get_certificate(endpoint, &data_builder, &crt_url).map_err(HttpError::in_err)?;
storage::write_certificate(&cert.file_manager, crt.as_bytes())?;
cert.info(&format!(
"certificate renewed (identifiers: {})",
cert.identifier_list()
));
Ok(())
} }

246
acmed/src/acme_proto/account.rs

@ -9,141 +9,141 @@ use crate::set_data_builder;
use acme_common::error::Error; use acme_common::error::Error;
macro_rules! create_account_if_does_not_exist { macro_rules! create_account_if_does_not_exist {
($e: expr, $endpoint: ident, $account: ident) => {
match $e {
Ok(r) => Ok(r),
Err(he) => match he {
HttpError::ApiError(ref e) => match e.get_acme_type() {
AcmeError::AccountDoesNotExist => {
let msg = format!(
"account has been dropped by endpoint \"{}\"",
$endpoint.name
);
$account.debug(&msg);
return register_account($endpoint, $account);
}
_ => Err(HttpError::in_err(he.to_owned())),
},
HttpError::GenericError(e) => Err(e),
},
}
};
($e: expr, $endpoint: ident, $account: ident) => {
match $e {
Ok(r) => Ok(r),
Err(he) => match he {
HttpError::ApiError(ref e) => match e.get_acme_type() {
AcmeError::AccountDoesNotExist => {
let msg = format!(
"account has been dropped by endpoint \"{}\"",
$endpoint.name
);
$account.debug(&msg);
return register_account($endpoint, $account);
}
_ => Err(HttpError::in_err(he.to_owned())),
},
HttpError::GenericError(e) => Err(e),
},
}
};
} }
pub fn register_account(endpoint: &mut Endpoint, account: &mut BaseAccount) -> Result<(), Error> { pub fn register_account(endpoint: &mut Endpoint, account: &mut BaseAccount) -> Result<(), Error> {
account.debug(&format!(
"creating account on endpoint \"{}\"...",
&endpoint.name
));
let account_struct = Account::new(account, endpoint)?;
let account_struct = serde_json::to_string(&account_struct)?;
let acc_ref = &account_struct;
let kp_ref = &account.current_key.key;
let signature_algorithm = &account.current_key.signature_algorithm;
let data_builder = |n: &str, url: &str| {
encode_jwk(
kp_ref,
signature_algorithm,
acc_ref.as_bytes(),
url,
Some(n.to_string()),
)
};
let (acc_rep, account_url) =
http::new_account(endpoint, &data_builder).map_err(HttpError::in_err)?;
account.set_account_url(&endpoint.name, &account_url)?;
let orders_url = match acc_rep.orders {
Some(url) => url,
None => {
let msg = format!(
account.debug(&format!(
"creating account on endpoint \"{}\"...",
&endpoint.name
));
let account_struct = Account::new(account, endpoint)?;
let account_struct = serde_json::to_string(&account_struct)?;
let acc_ref = &account_struct;
let kp_ref = &account.current_key.key;
let signature_algorithm = &account.current_key.signature_algorithm;
let data_builder = |n: &str, url: &str| {
encode_jwk(
kp_ref,
signature_algorithm,
acc_ref.as_bytes(),
url,
Some(n.to_string()),
)
};
let (acc_rep, account_url) =
http::new_account(endpoint, &data_builder).map_err(HttpError::in_err)?;
account.set_account_url(&endpoint.name, &account_url)?;
let orders_url = match acc_rep.orders {
Some(url) => url,
None => {
let msg = format!(
"endpoint \"{}\": account \"{}\": the server has not provided an order URL upon account creation", "endpoint \"{}\": account \"{}\": the server has not provided an order URL upon account creation",
&endpoint.name, &endpoint.name,
&account.name &account.name
); );
account.warn(&msg);
String::new()
}
};
account.set_orders_url(&endpoint.name, &orders_url)?;
account.update_key_hash(&endpoint.name)?;
account.update_contacts_hash(&endpoint.name)?;
account.update_external_account_hash(&endpoint.name)?;
account.save()?;
account.info(&format!(
"account created on endpoint \"{}\"",
&endpoint.name
));
Ok(())
account.warn(&msg);
String::new()
}
};
account.set_orders_url(&endpoint.name, &orders_url)?;
account.update_key_hash(&endpoint.name)?;
account.update_contacts_hash(&endpoint.name)?;
account.update_external_account_hash(&endpoint.name)?;
account.save()?;
account.info(&format!(
"account created on endpoint \"{}\"",
&endpoint.name
));
Ok(())
} }
pub fn update_account_contacts( pub fn update_account_contacts(
endpoint: &mut Endpoint,
account: &mut BaseAccount,
endpoint: &mut Endpoint,
account: &mut BaseAccount,
) -> Result<(), Error> { ) -> Result<(), Error> {
let endpoint_name = endpoint.name.clone();
account.debug(&format!(
"updating account contacts on endpoint \"{}\"...",
&endpoint_name
));
let new_contacts: Vec<String> = account.contacts.iter().map(|c| c.to_string()).collect();
let acc_up_struct = AccountUpdate::new(&new_contacts);
let acc_up_struct = serde_json::to_string(&acc_up_struct)?;
let data_builder = set_data_builder!(account, endpoint_name, acc_up_struct.as_bytes());
let url = account.get_endpoint(&endpoint_name)?.account_url.clone();
create_account_if_does_not_exist!(
http::post_jose_no_response(endpoint, &data_builder, &url),
endpoint,
account
)?;
account.update_contacts_hash(&endpoint_name)?;
account.save()?;
account.info(&format!(
"account contacts updated on endpoint \"{}\"",
&endpoint_name
));
Ok(())
let endpoint_name = endpoint.name.clone();
account.debug(&format!(
"updating account contacts on endpoint \"{}\"...",
&endpoint_name
));
let new_contacts: Vec<String> = account.contacts.iter().map(|c| c.to_string()).collect();
let acc_up_struct = AccountUpdate::new(&new_contacts);
let acc_up_struct = serde_json::to_string(&acc_up_struct)?;
let data_builder = set_data_builder!(account, endpoint_name, acc_up_struct.as_bytes());
let url = account.get_endpoint(&endpoint_name)?.account_url.clone();
create_account_if_does_not_exist!(
http::post_jose_no_response(endpoint, &data_builder, &url),
endpoint,
account
)?;
account.update_contacts_hash(&endpoint_name)?;
account.save()?;
account.info(&format!(
"account contacts updated on endpoint \"{}\"",
&endpoint_name
));
Ok(())
} }
pub fn update_account_key(endpoint: &mut Endpoint, account: &mut BaseAccount) -> Result<(), Error> { pub fn update_account_key(endpoint: &mut Endpoint, account: &mut BaseAccount) -> Result<(), Error> {
let endpoint_name = endpoint.name.clone();
account.debug(&format!(
"updating account key on endpoint \"{}\"...",
&endpoint_name
));
let url = endpoint.dir.key_change.clone();
let ep = account.get_endpoint(&endpoint_name)?;
let old_account_key = account.get_past_key(&ep.key_hash)?;
let old_key = &old_account_key.key;
let account_url = account.get_endpoint(&endpoint_name)?.account_url.clone();
let rollover_struct = AccountKeyRollover::new(&account_url, old_key)?;
let rollover_struct = serde_json::to_string(&rollover_struct)?;
let rollover_payload = encode_jwk(
&account.current_key.key,
&account.current_key.signature_algorithm,
rollover_struct.as_bytes(),
&url,
None,
)?;
let data_builder = |n: &str, url: &str| {
encode_kid(
old_key,
&old_account_key.signature_algorithm,
&account_url,
rollover_payload.as_bytes(),
url,
n,
)
};
create_account_if_does_not_exist!(
http::post_jose_no_response(endpoint, &data_builder, &url),
endpoint,
account
)?;
account.update_key_hash(&endpoint_name)?;
account.save()?;
account.info(&format!(
"account key updated on endpoint \"{}\"",
&endpoint_name
));
Ok(())
let endpoint_name = endpoint.name.clone();
account.debug(&format!(
"updating account key on endpoint \"{}\"...",
&endpoint_name
));
let url = endpoint.dir.key_change.clone();
let ep = account.get_endpoint(&endpoint_name)?;
let old_account_key = account.get_past_key(&ep.key_hash)?;
let old_key = &old_account_key.key;
let account_url = account.get_endpoint(&endpoint_name)?.account_url.clone();
let rollover_struct = AccountKeyRollover::new(&account_url, old_key)?;
let rollover_struct = serde_json::to_string(&rollover_struct)?;
let rollover_payload = encode_jwk(
&account.current_key.key,
&account.current_key.signature_algorithm,
rollover_struct.as_bytes(),
&url,
None,
)?;
let data_builder = |n: &str, url: &str| {
encode_kid(
old_key,
&old_account_key.signature_algorithm,
&account_url,
rollover_payload.as_bytes(),
url,
n,
)
};
create_account_if_does_not_exist!(
http::post_jose_no_response(endpoint, &data_builder, &url),
endpoint,
account
)?;
account.update_key_hash(&endpoint_name)?;
account.save()?;
account.info(&format!(
"account key updated on endpoint \"{}\"",
&endpoint_name
));
Ok(())
} }

24
acmed/src/acme_proto/certificate.rs

@ -4,22 +4,22 @@ use acme_common::crypto::{gen_keypair, KeyPair};
use acme_common::error::Error; use acme_common::error::Error;
fn gen_key_pair(cert: &Certificate) -> Result<KeyPair, Error> { fn gen_key_pair(cert: &Certificate) -> Result<KeyPair, Error> {
let key_pair = gen_keypair(cert.key_type)?;
storage::set_keypair(&cert.file_manager, &key_pair)?;
Ok(key_pair)
let key_pair = gen_keypair(cert.key_type)?;
storage::set_keypair(&cert.file_manager, &key_pair)?;
Ok(key_pair)
} }
fn read_key_pair(cert: &Certificate) -> Result<KeyPair, Error> { fn read_key_pair(cert: &Certificate) -> Result<KeyPair, Error> {
storage::get_keypair(&cert.file_manager)
storage::get_keypair(&cert.file_manager)
} }
pub fn get_key_pair(cert: &Certificate) -> Result<KeyPair, Error> { pub fn get_key_pair(cert: &Certificate) -> Result<KeyPair, Error> {
if cert.kp_reuse {
match read_key_pair(cert) {
Ok(key_pair) => Ok(key_pair),
Err(_) => gen_key_pair(cert),
}
} else {
gen_key_pair(cert)
}
if cert.kp_reuse {
match read_key_pair(cert) {
Ok(key_pair) => Ok(key_pair),
Err(_) => gen_key_pair(cert),
}
} else {
gen_key_pair(cert)
}
} }

178
acmed/src/acme_proto/http.rs

@ -5,144 +5,144 @@ use acme_common::error::Error;
use std::{thread, time}; use std::{thread, time};
macro_rules! pool_object { macro_rules! pool_object {
($obj_type: ty, $obj_name: expr, $endpoint: expr, $url: expr, $data_builder: expr, $break: expr) => {{
for _ in 0..crate::DEFAULT_POOL_NB_TRIES {
thread::sleep(time::Duration::from_secs(crate::DEFAULT_POOL_WAIT_SEC));
let response = http::post_jose($endpoint, $url, $data_builder)?;
let obj = response.json::<$obj_type>()?;
if $break(&obj) {
return Ok(obj);
}
}
let msg = format!("{} pooling failed on {}", $obj_name, $url);
Err(msg.into())
}};
($obj_type: ty, $obj_name: expr, $endpoint: expr, $url: expr, $data_builder: expr, $break: expr) => {{
for _ in 0..crate::DEFAULT_POOL_NB_TRIES {
thread::sleep(time::Duration::from_secs(crate::DEFAULT_POOL_WAIT_SEC));
let response = http::post_jose($endpoint, $url, $data_builder)?;
let obj = response.json::<$obj_type>()?;
if $break(&obj) {
return Ok(obj);
}
}
let msg = format!("{} pooling failed on {}", $obj_name, $url);
Err(msg.into())
}};
} }
pub fn refresh_directory(endpoint: &mut Endpoint) -> Result<(), http::HttpError> { pub fn refresh_directory(endpoint: &mut Endpoint) -> Result<(), http::HttpError> {
let url = endpoint.url.clone();
let response = http::get(endpoint, &url)?;
endpoint.dir = response.json::<Directory>()?;
Ok(())
let url = endpoint.url.clone();
let response = http::get(endpoint, &url)?;
endpoint.dir = response.json::<Directory>()?;
Ok(())
} }
pub fn post_jose_no_response<F>( pub fn post_jose_no_response<F>(
endpoint: &mut Endpoint,
data_builder: &F,
url: &str,
endpoint: &mut Endpoint,
data_builder: &F,
url: &str,
) -> Result<(), http::HttpError> ) -> Result<(), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
F: Fn(&str, &str) -> Result<String, Error>,
{ {
let _ = http::post_jose(endpoint, url, data_builder)?;
Ok(())
let _ = http::post_jose(endpoint, url, data_builder)?;
Ok(())
} }
pub fn new_account<F>( pub fn new_account<F>(
endpoint: &mut Endpoint,
data_builder: &F,
endpoint: &mut Endpoint,
data_builder: &F,
) -> Result<(AccountResponse, String), http::HttpError> ) -> Result<(AccountResponse, String), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
F: Fn(&str, &str) -> Result<String, Error>,
{ {
let url = endpoint.dir.new_account.clone();
let response = http::post_jose(endpoint, &url, data_builder)?;
let acc_uri = response
.get_header(http::HEADER_LOCATION)
.ok_or_else(|| Error::from("no account location found"))?;
let acc_resp = response.json::<AccountResponse>()?;
Ok((acc_resp, acc_uri))
let url = endpoint.dir.new_account.clone();
let response = http::post_jose(endpoint, &url, data_builder)?;
let acc_uri = response
.get_header(http::HEADER_LOCATION)
.ok_or_else(|| Error::from("no account location found"))?;
let acc_resp = response.json::<AccountResponse>()?;
Ok((acc_resp, acc_uri))
} }
pub fn new_order<F>( pub fn new_order<F>(
endpoint: &mut Endpoint,
data_builder: &F,
endpoint: &mut Endpoint,
data_builder: &F,
) -> Result<(Order, String), http::HttpError> ) -> Result<(Order, String), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
F: Fn(&str, &str) -> Result<String, Error>,
{ {
let url = endpoint.dir.new_order.clone();
let response = http::post_jose(endpoint, &url, data_builder)?;
let order_uri = response
.get_header(http::HEADER_LOCATION)
.ok_or_else(|| Error::from("no account location found"))?;
let order_resp = response.json::<Order>()?;
Ok((order_resp, order_uri))
let url = endpoint.dir.new_order.clone();
let response = http::post_jose(endpoint, &url, data_builder)?;
let order_uri = response
.get_header(http::HEADER_LOCATION)
.ok_or_else(|| Error::from("no account location found"))?;
let order_resp = response.json::<Order>()?;
Ok((order_resp, order_uri))
} }
pub fn get_authorization<F>( pub fn get_authorization<F>(
endpoint: &mut Endpoint,
data_builder: &F,
url: &str,
endpoint: &mut Endpoint,
data_builder: &F,
url: &str,
) -> Result<Authorization, http::HttpError> ) -> Result<Authorization, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
F: Fn(&str, &str) -> Result<String, Error>,
{ {
let response = http::post_jose(endpoint, url, data_builder)?;
let auth = response.json::<Authorization>()?;
Ok(auth)
let response = http::post_jose(endpoint, url, data_builder)?;
let auth = response.json::<Authorization>()?;
Ok(auth)
} }
pub fn pool_authorization<F, S>( pub fn pool_authorization<F, S>(
endpoint: &mut Endpoint,
data_builder: &F,
break_fn: &S,
url: &str,
endpoint: &mut Endpoint,
data_builder: &F,
break_fn: &S,
url: &str,
) -> Result<Authorization, http::HttpError> ) -> Result<Authorization, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
S: Fn(&Authorization) -> bool,
F: Fn(&str, &str) -> Result<String, Error>,
S: Fn(&Authorization) -> bool,
{ {
pool_object!(
Authorization,
"authorization",
endpoint,
url,
data_builder,
break_fn
)
pool_object!(
Authorization,
"authorization",
endpoint,
url,
data_builder,
break_fn
)
} }
pub fn pool_order<F, S>( pub fn pool_order<F, S>(
endpoint: &mut Endpoint,
data_builder: &F,
break_fn: &S,
url: &str,
endpoint: &mut Endpoint,
data_builder: &F,
break_fn: &S,
url: &str,
) -> Result<Order, http::HttpError> ) -> Result<Order, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
S: Fn(&Order) -> bool,
F: Fn(&str, &str) -> Result<String, Error>,
S: Fn(&Order) -> bool,
{ {
pool_object!(Order, "order", endpoint, url, data_builder, break_fn)
pool_object!(Order, "order", endpoint, url, data_builder, break_fn)
} }
pub fn finalize_order<F>( pub fn finalize_order<F>(
endpoint: &mut Endpoint,
data_builder: &F,
url: &str,
endpoint: &mut Endpoint,
data_builder: &F,
url: &str,
) -> Result<Order, http::HttpError> ) -> Result<Order, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
F: Fn(&str, &str) -> Result<String, Error>,
{ {
let response = http::post_jose(endpoint, url, data_builder)?;
let order = response.json::<Order>()?;
Ok(order)
let response = http::post_jose(endpoint, url, data_builder)?;
let order = response.json::<Order>()?;
Ok(order)
} }
pub fn get_certificate<F>( pub fn get_certificate<F>(
endpoint: &mut Endpoint,
data_builder: &F,
url: &str,
endpoint: &mut Endpoint,
data_builder: &F,
url: &str,
) -> Result<String, http::HttpError> ) -> Result<String, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
F: Fn(&str, &str) -> Result<String, Error>,
{ {
let response = http::post(
endpoint,
url,
data_builder,
http::CONTENT_TYPE_JOSE,
http::CONTENT_TYPE_PEM,
)?;
Ok(response.body)
let response = http::post(
endpoint,
url,
data_builder,
http::CONTENT_TYPE_JOSE,
http::CONTENT_TYPE_PEM,
)?;
Ok(response.body)
} }

20
acmed/src/acme_proto/structs.rs

@ -1,15 +1,15 @@
#[macro_export] #[macro_export]
macro_rules! deserialize_from_str { macro_rules! deserialize_from_str {
($t: ty) => {
impl FromStr for $t {
type Err = Error;
($t: ty) => {
impl FromStr for $t {
type Err = Error;
fn from_str(data: &str) -> Result<Self, Self::Err> {
let res = serde_json::from_str(data)?;
Ok(res)
}
}
};
fn from_str(data: &str) -> Result<Self, Self::Err> {
let res = serde_json::from_str(data)?;
Ok(res)
}
}
};
} }
mod account; mod account;
@ -19,7 +19,7 @@ mod error;
mod order; mod order;
pub use account::{ pub use account::{
Account, AccountDeactivation, AccountKeyRollover, AccountResponse, AccountUpdate,
Account, AccountDeactivation, AccountKeyRollover, AccountResponse, AccountUpdate,
}; };
pub use authorization::{Authorization, AuthorizationStatus, Challenge}; pub use authorization::{Authorization, AuthorizationStatus, Challenge};
pub use deserialize_from_str; pub use deserialize_from_str;

272
acmed/src/acme_proto/structs/account.rs

@ -9,136 +9,136 @@ use std::str::FromStr;
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Account { pub struct Account {
pub contact: Vec<String>,
pub terms_of_service_agreed: bool,
pub only_return_existing: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub external_account_binding: Option<Value>,
pub contact: Vec<String>,
pub terms_of_service_agreed: bool,
pub only_return_existing: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub external_account_binding: Option<Value>,
} }
impl Account { impl Account {
pub fn new(account: &crate::account::Account, endpoint: &Endpoint) -> Result<Self, Error> {
let external_account_binding = match &account.external_account {
Some(a) => {
let k_ref = &a.key;
let signature_algorithm = &a.signature_algorithm;
let kid = &a.identifier;
let payload = account.current_key.key.jwk_public_key()?;
let payload = serde_json::to_string(&payload)?;
let data = encode_kid_mac(
k_ref,
signature_algorithm,
kid,
payload.as_bytes(),
&endpoint.dir.new_account,
)?;
let data: Value = serde_json::from_str(&data)?;
Some(data)
}
None => None,
};
Ok(Account {
contact: account.contacts.iter().map(|e| e.to_string()).collect(),
terms_of_service_agreed: endpoint.tos_agreed,
only_return_existing: false,
external_account_binding,
})
}
pub fn new(account: &crate::account::Account, endpoint: &Endpoint) -> Result<Self, Error> {
let external_account_binding = match &account.external_account {
Some(a) => {
let k_ref = &a.key;
let signature_algorithm = &a.signature_algorithm;
let kid = &a.identifier;
let payload = account.current_key.key.jwk_public_key()?;
let payload = serde_json::to_string(&payload)?;
let data = encode_kid_mac(
k_ref,
signature_algorithm,
kid,
payload.as_bytes(),
&endpoint.dir.new_account,
)?;
let data: Value = serde_json::from_str(&data)?;
Some(data)
}
None => None,
};
Ok(Account {
contact: account.contacts.iter().map(|e| e.to_string()).collect(),
terms_of_service_agreed: endpoint.tos_agreed,
only_return_existing: false,
external_account_binding,
})
}
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AccountResponse { pub struct AccountResponse {
pub status: String,
pub contact: Option<Vec<String>>,
pub terms_of_service_agreed: Option<bool>,
pub external_account_binding: Option<Value>,
pub orders: Option<String>,
pub status: String,
pub contact: Option<Vec<String>>,
pub terms_of_service_agreed: Option<bool>,
pub external_account_binding: Option<Value>,
pub orders: Option<String>,
} }
deserialize_from_str!(AccountResponse); deserialize_from_str!(AccountResponse);
#[derive(Serialize)] #[derive(Serialize)]
pub struct AccountUpdate { pub struct AccountUpdate {
pub contact: Vec<String>,
pub contact: Vec<String>,
} }
impl AccountUpdate { impl AccountUpdate {
pub fn new(contact: &[String]) -> Self {
AccountUpdate {
contact: contact.into(),
}
}
pub fn new(contact: &[String]) -> Self {
AccountUpdate {
contact: contact.into(),
}
}
} }
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AccountKeyRollover { pub struct AccountKeyRollover {
pub account: String,
pub old_key: Value,
pub account: String,
pub old_key: Value,
} }
impl AccountKeyRollover { impl AccountKeyRollover {
pub fn new(account_str: &str, old_key: &KeyPair) -> Result<Self, Error> {
Ok(AccountKeyRollover {
account: account_str.to_string(),
old_key: old_key.jwk_public_key()?,
})
}
pub fn new(account_str: &str, old_key: &KeyPair) -> Result<Self, Error> {
Ok(AccountKeyRollover {
account: account_str.to_string(),
old_key: old_key.jwk_public_key()?,
})
}
} }
// TODO: implement account deactivation // TODO: implement account deactivation
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Serialize)] #[derive(Serialize)]
pub struct AccountDeactivation { pub struct AccountDeactivation {
pub status: String,
pub status: String,
} }
impl AccountDeactivation { impl AccountDeactivation {
#[allow(dead_code)]
pub fn new() -> Self {
AccountDeactivation {
status: "deactivated".into(),
}
}
#[allow(dead_code)]
pub fn new() -> Self {
AccountDeactivation {
status: "deactivated".into(),
}
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use super::*;
#[test]
fn test_account_new() {
let emails = vec![
"mailto:derp@example.com".to_string(),
"mailto:derp.derpson@example.com".to_string(),
];
let a = Account {
contact: emails,
terms_of_service_agreed: true,
only_return_existing: false,
external_account_binding: None,
};
assert_eq!(a.contact.len(), 2);
assert_eq!(a.terms_of_service_agreed, true);
assert_eq!(a.only_return_existing, false);
let a_str = serde_json::to_string(&a);
assert!(a_str.is_ok());
let a_str = a_str.unwrap();
assert!(a_str.starts_with("{"));
assert!(a_str.ends_with("}"));
assert!(a_str.contains("\"contact\""));
assert!(a_str.contains("\"mailto:derp@example.com\""));
assert!(a_str.contains("\"mailto:derp.derpson@example.com\""));
assert!(a_str.contains("\"termsOfServiceAgreed\""));
assert!(a_str.contains("\"onlyReturnExisting\""));
assert!(a_str.contains("true"));
assert!(a_str.contains("false"));
}
#[test]
fn test_account_new() {
let emails = vec![
"mailto:derp@example.com".to_string(),
"mailto:derp.derpson@example.com".to_string(),
];
let a = Account {
contact: emails,
terms_of_service_agreed: true,
only_return_existing: false,
external_account_binding: None,
};
assert_eq!(a.contact.len(), 2);
assert_eq!(a.terms_of_service_agreed, true);
assert_eq!(a.only_return_existing, false);
let a_str = serde_json::to_string(&a);
assert!(a_str.is_ok());
let a_str = a_str.unwrap();
assert!(a_str.starts_with("{"));
assert!(a_str.ends_with("}"));
assert!(a_str.contains("\"contact\""));
assert!(a_str.contains("\"mailto:derp@example.com\""));
assert!(a_str.contains("\"mailto:derp.derpson@example.com\""));
assert!(a_str.contains("\"termsOfServiceAgreed\""));
assert!(a_str.contains("\"onlyReturnExisting\""));
assert!(a_str.contains("true"));
assert!(a_str.contains("false"));
}
#[test]
fn test_account_response() {
let data = "{
#[test]
fn test_account_response() {
let data = "{
\"status\": \"valid\", \"status\": \"valid\",
\"contact\": [ \"contact\": [
\"mailto:cert-admin@example.org\", \"mailto:cert-admin@example.org\",
@ -147,52 +147,52 @@ mod tests {
\"termsOfServiceAgreed\": true, \"termsOfServiceAgreed\": true,
\"orders\": \"https://example.com/acme/orders/rzGoeA\" \"orders\": \"https://example.com/acme/orders/rzGoeA\"
}"; }";
let account_resp = AccountResponse::from_str(data);
assert!(account_resp.is_ok());
let account_resp = account_resp.unwrap();
assert_eq!(account_resp.status, "valid");
assert!(account_resp.contact.is_some());
let contacts = account_resp.contact.unwrap();
assert_eq!(contacts.len(), 2);
assert_eq!(contacts[0], "mailto:cert-admin@example.org");
assert_eq!(contacts[1], "mailto:admin@example.org");
assert!(account_resp.external_account_binding.is_none());
assert!(account_resp.terms_of_service_agreed.is_some());
assert!(account_resp.terms_of_service_agreed.unwrap());
assert_eq!(
account_resp.orders,
Some("https://example.com/acme/orders/rzGoeA".into())
);
}
let account_resp = AccountResponse::from_str(data);
assert!(account_resp.is_ok());
let account_resp = account_resp.unwrap();
assert_eq!(account_resp.status, "valid");
assert!(account_resp.contact.is_some());
let contacts = account_resp.contact.unwrap();
assert_eq!(contacts.len(), 2);
assert_eq!(contacts[0], "mailto:cert-admin@example.org");
assert_eq!(contacts[1], "mailto:admin@example.org");
assert!(account_resp.external_account_binding.is_none());
assert!(account_resp.terms_of_service_agreed.is_some());
assert!(account_resp.terms_of_service_agreed.unwrap());
assert_eq!(
account_resp.orders,
Some("https://example.com/acme/orders/rzGoeA".into())
);
}
#[test]
fn test_account_update() {
let emails = vec![
"mailto:derp@example.com".to_string(),
"mailto:derp.derpson@example.com".to_string(),
];
let au = AccountUpdate::new(&emails);
assert_eq!(au.contact.len(), 2);
let au_str = serde_json::to_string(&au);
assert!(au_str.is_ok());
let au_str = au_str.unwrap();
assert!(au_str.starts_with("{"));
assert!(au_str.ends_with("}"));
assert!(au_str.contains("\"contact\""));
assert!(au_str.contains("\"mailto:derp@example.com\""));
assert!(au_str.contains("\"mailto:derp.derpson@example.com\""));
}
#[test]
fn test_account_update() {
let emails = vec![
"mailto:derp@example.com".to_string(),
"mailto:derp.derpson@example.com".to_string(),
];
let au = AccountUpdate::new(&emails);
assert_eq!(au.contact.len(), 2);
let au_str = serde_json::to_string(&au);
assert!(au_str.is_ok());
let au_str = au_str.unwrap();
assert!(au_str.starts_with("{"));
assert!(au_str.ends_with("}"));
assert!(au_str.contains("\"contact\""));
assert!(au_str.contains("\"mailto:derp@example.com\""));
assert!(au_str.contains("\"mailto:derp.derpson@example.com\""));
}
#[test]
fn test_account_deactivation() {
let ad = AccountDeactivation::new();
assert_eq!(ad.status, "deactivated");
let ad_str = serde_json::to_string(&ad);
assert!(ad_str.is_ok());
let ad_str = ad_str.unwrap();
assert!(ad_str.starts_with("{"));
assert!(ad_str.ends_with("}"));
assert!(ad_str.contains("\"status\""));
assert!(ad_str.contains("\"deactivated\""));
}
#[test]
fn test_account_deactivation() {
let ad = AccountDeactivation::new();
assert_eq!(ad.status, "deactivated");
let ad_str = serde_json::to_string(&ad);
assert!(ad_str.is_ok());
let ad_str = ad_str.unwrap();
assert!(ad_str.starts_with("{"));
assert!(ad_str.ends_with("}"));
assert!(ad_str.contains("\"status\""));
assert!(ad_str.contains("\"deactivated\""));
}
} }

440
acmed/src/acme_proto/structs/authorization.rs

@ -13,174 +13,174 @@ const DER_STRUCT_NAME: &str = "DER";
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Authorization { pub struct Authorization {
pub identifier: Identifier,
pub status: AuthorizationStatus,
pub expires: Option<String>,
pub challenges: Vec<Challenge>,
pub wildcard: Option<bool>,
pub identifier: Identifier,
pub status: AuthorizationStatus,
pub expires: Option<String>,
pub challenges: Vec<Challenge>,
pub wildcard: Option<bool>,
} }
impl FromStr for Authorization { impl FromStr for Authorization {
type Err = Error;
type Err = Error;
fn from_str(data: &str) -> Result<Self, Self::Err> {
let mut res: Self = serde_json::from_str(data)?;
res.challenges.retain(|c| *c != Challenge::Unknown);
Ok(res)
}
fn from_str(data: &str) -> Result<Self, Self::Err> {
let mut res: Self = serde_json::from_str(data)?;
res.challenges.retain(|c| *c != Challenge::Unknown);
Ok(res)
}
} }
impl ApiError for Authorization { impl ApiError for Authorization {
fn get_error(&self) -> Option<Error> {
for challenge in self.challenges.iter() {
let err = challenge.get_error();
if err.is_some() {
return err;
}
}
None
}
fn get_error(&self) -> Option<Error> {
for challenge in self.challenges.iter() {
let err = challenge.get_error();
if err.is_some() {
return err;
}
}
None
}
} }
#[derive(Debug, PartialEq, Eq, Deserialize)] #[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum AuthorizationStatus { pub enum AuthorizationStatus {
Pending,
Valid,
Invalid,
Deactivated,
Expired,
Revoked,
Pending,
Valid,
Invalid,
Deactivated,
Expired,
Revoked,
} }
impl fmt::Display for AuthorizationStatus { impl fmt::Display for AuthorizationStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
AuthorizationStatus::Pending => "pending",
AuthorizationStatus::Valid => "valid",
AuthorizationStatus::Invalid => "invalid",
AuthorizationStatus::Deactivated => "deactivated",
AuthorizationStatus::Expired => "expired",
AuthorizationStatus::Revoked => "revoked",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
AuthorizationStatus::Pending => "pending",
AuthorizationStatus::Valid => "valid",
AuthorizationStatus::Invalid => "invalid",
AuthorizationStatus::Deactivated => "deactivated",
AuthorizationStatus::Expired => "expired",
AuthorizationStatus::Revoked => "revoked",
};
write!(f, "{}", s)
}
} }
#[derive(PartialEq, Deserialize)] #[derive(PartialEq, Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum Challenge { pub enum Challenge {
#[serde(rename = "http-01")]
Http01(TokenChallenge),
#[serde(rename = "dns-01")]
Dns01(TokenChallenge),
#[serde(rename = "tls-alpn-01")]
TlsAlpn01(TokenChallenge),
#[serde(other)]
Unknown,
#[serde(rename = "http-01")]
Http01(TokenChallenge),
#[serde(rename = "dns-01")]
Dns01(TokenChallenge),
#[serde(rename = "tls-alpn-01")]
TlsAlpn01(TokenChallenge),
#[serde(other)]
Unknown,
} }
deserialize_from_str!(Challenge); deserialize_from_str!(Challenge);
impl Challenge { impl Challenge {
pub fn get_url(&self) -> String {
match self {
Challenge::Http01(tc) | Challenge::Dns01(tc) | Challenge::TlsAlpn01(tc) => {
tc.url.to_owned()
}
Challenge::Unknown => String::new(),
}
}
pub fn get_url(&self) -> String {
match self {
Challenge::Http01(tc) | Challenge::Dns01(tc) | Challenge::TlsAlpn01(tc) => {
tc.url.to_owned()
}
Challenge::Unknown => String::new(),
}
}
pub fn get_proof(&self, key_pair: &KeyPair) -> Result<String, Error> {
match self {
Challenge::Http01(tc) => tc.key_authorization(key_pair),
Challenge::Dns01(tc) => {
let ka = tc.key_authorization(key_pair)?;
let a = HashFunction::Sha256.hash(ka.as_bytes());
let a = b64_encode(&a);
Ok(a)
}
Challenge::TlsAlpn01(tc) => {
let acme_ext_name = format!("{}.{}", ACME_OID, ID_PE_ACME_ID);
let ka = tc.key_authorization(key_pair)?;
let proof = HashFunction::Sha256.hash(ka.as_bytes());
let proof_str = proof
.iter()
.map(|e| format!("{:02x}", e))
.collect::<Vec<String>>()
.join(":");
let value = format!(
"critical,{}:{:02x}:{:02x}:{}",
DER_STRUCT_NAME,
DER_OCTET_STRING_ID,
proof.len(),
proof_str
);
let acme_ext = format!("{}={}", acme_ext_name, value);
Ok(acme_ext)
}
Challenge::Unknown => Ok(String::new()),
}
}
pub fn get_proof(&self, key_pair: &KeyPair) -> Result<String, Error> {
match self {
Challenge::Http01(tc) => tc.key_authorization(key_pair),
Challenge::Dns01(tc) => {
let ka = tc.key_authorization(key_pair)?;
let a = HashFunction::Sha256.hash(ka.as_bytes());
let a = b64_encode(&a);
Ok(a)
}
Challenge::TlsAlpn01(tc) => {
let acme_ext_name = format!("{}.{}", ACME_OID, ID_PE_ACME_ID);
let ka = tc.key_authorization(key_pair)?;
let proof = HashFunction::Sha256.hash(ka.as_bytes());
let proof_str = proof
.iter()
.map(|e| format!("{:02x}", e))
.collect::<Vec<String>>()
.join(":");
let value = format!(
"critical,{}:{:02x}:{:02x}:{}",
DER_STRUCT_NAME,
DER_OCTET_STRING_ID,
proof.len(),
proof_str
);
let acme_ext = format!("{}={}", acme_ext_name, value);
Ok(acme_ext)
}
Challenge::Unknown => Ok(String::new()),
}
}
pub fn get_file_name(&self) -> String {
match self {
Challenge::Http01(tc) => tc.token.to_owned(),
Challenge::Dns01(_) | Challenge::TlsAlpn01(_) => String::new(),
Challenge::Unknown => String::new(),
}
}
pub fn get_file_name(&self) -> String {
match self {
Challenge::Http01(tc) => tc.token.to_owned(),
Challenge::Dns01(_) | Challenge::TlsAlpn01(_) => String::new(),
Challenge::Unknown => String::new(),
}
}
} }
impl ApiError for Challenge { impl ApiError for Challenge {
fn get_error(&self) -> Option<Error> {
match self {
Challenge::Http01(tc) | Challenge::Dns01(tc) | Challenge::TlsAlpn01(tc) => {
tc.error.to_owned().map(Error::from)
}
Challenge::Unknown => None,
}
}
fn get_error(&self) -> Option<Error> {
match self {
Challenge::Http01(tc) | Challenge::Dns01(tc) | Challenge::TlsAlpn01(tc) => {
tc.error.to_owned().map(Error::from)
}
Challenge::Unknown => None,
}
}
} }
#[derive(PartialEq, Deserialize)] #[derive(PartialEq, Deserialize)]
pub struct TokenChallenge { pub struct TokenChallenge {
pub url: String,
pub status: Option<ChallengeStatus>,
pub validated: Option<String>,
pub error: Option<HttpApiError>,
pub token: String,
pub url: String,
pub status: Option<ChallengeStatus>,
pub validated: Option<String>,
pub error: Option<HttpApiError>,
pub token: String,
} }
impl TokenChallenge { impl TokenChallenge {
fn key_authorization(&self, key_pair: &KeyPair) -> Result<String, Error> {
let thumbprint = key_pair.jwk_public_key_thumbprint()?;
let thumbprint = HashFunction::Sha256.hash(thumbprint.to_string().as_bytes());
let thumbprint = b64_encode(&thumbprint);
let auth = format!("{}.{}", self.token, thumbprint);
Ok(auth)
}
fn key_authorization(&self, key_pair: &KeyPair) -> Result<String, Error> {
let thumbprint = key_pair.jwk_public_key_thumbprint()?;
let thumbprint = HashFunction::Sha256.hash(thumbprint.to_string().as_bytes());
let thumbprint = b64_encode(&thumbprint);
let auth = format!("{}.{}", self.token, thumbprint);
Ok(auth)
}
} }
#[derive(Debug, PartialEq, Eq, Deserialize)] #[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum ChallengeStatus { pub enum ChallengeStatus {
Pending,
Processing,
Valid,
Invalid,
Pending,
Processing,
Valid,
Invalid,
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Authorization, AuthorizationStatus, Challenge, ChallengeStatus};
use crate::identifier::IdentifierType;
use std::str::FromStr;
use super::{Authorization, AuthorizationStatus, Challenge, ChallengeStatus};
use crate::identifier::IdentifierType;
use std::str::FromStr;
#[test]
fn test_authorization() {
let data = "{
#[test]
fn test_authorization() {
let data = "{
\"status\": \"pending\", \"status\": \"pending\",
\"identifier\": { \"identifier\": {
\"type\": \"dns\", \"type\": \"dns\",
@ -188,19 +188,19 @@ mod tests {
}, },
\"challenges\": [] \"challenges\": []
}"; }";
let a = Authorization::from_str(data);
assert!(a.is_ok());
let a = a.unwrap();
assert_eq!(a.status, AuthorizationStatus::Pending);
assert!(a.challenges.is_empty());
let i = a.identifier;
assert_eq!(i.id_type, IdentifierType::Dns);
assert_eq!(i.value, "example.com".to_string());
}
let a = Authorization::from_str(data);
assert!(a.is_ok());
let a = a.unwrap();
assert_eq!(a.status, AuthorizationStatus::Pending);
assert!(a.challenges.is_empty());
let i = a.identifier;
assert_eq!(i.id_type, IdentifierType::Dns);
assert_eq!(i.value, "example.com".to_string());
}
#[test]
fn test_authorization_challenge() {
let data = "{
#[test]
fn test_authorization_challenge() {
let data = "{
\"status\": \"pending\", \"status\": \"pending\",
\"identifier\": { \"identifier\": {
\"type\": \"dns\", \"type\": \"dns\",
@ -215,19 +215,19 @@ mod tests {
} }
] ]
}"; }";
let a = Authorization::from_str(data);
assert!(a.is_ok());
let a = a.unwrap();
assert_eq!(a.status, AuthorizationStatus::Pending);
assert_eq!(a.challenges.len(), 1);
let i = a.identifier;
assert_eq!(i.id_type, IdentifierType::Dns);
assert_eq!(i.value, "example.com".to_string());
}
let a = Authorization::from_str(data);
assert!(a.is_ok());
let a = a.unwrap();
assert_eq!(a.status, AuthorizationStatus::Pending);
assert_eq!(a.challenges.len(), 1);
let i = a.identifier;
assert_eq!(i.id_type, IdentifierType::Dns);
assert_eq!(i.value, "example.com".to_string());
}
#[test]
fn test_authorization_unknown_challenge() {
let data = "{
#[test]
fn test_authorization_unknown_challenge() {
let data = "{
\"status\": \"pending\", \"status\": \"pending\",
\"identifier\": { \"identifier\": {
\"type\": \"dns\", \"type\": \"dns\",
@ -242,19 +242,19 @@ mod tests {
} }
] ]
}"; }";
let a = Authorization::from_str(data);
assert!(a.is_ok());
let a = a.unwrap();
assert_eq!(a.status, AuthorizationStatus::Pending);
assert!(a.challenges.is_empty());
let i = a.identifier;
assert_eq!(i.id_type, IdentifierType::Dns);
assert_eq!(i.value, "example.com".to_string());
}
let a = Authorization::from_str(data);
assert!(a.is_ok());
let a = a.unwrap();
assert_eq!(a.status, AuthorizationStatus::Pending);
assert!(a.challenges.is_empty());
let i = a.identifier;
assert_eq!(i.id_type, IdentifierType::Dns);
assert_eq!(i.value, "example.com".to_string());
}
#[test]
fn test_invalid_authorization() {
let data = "{
#[test]
fn test_invalid_authorization() {
let data = "{
\"status\": \"pending\", \"status\": \"pending\",
\"identifier\": { \"identifier\": {
\"type\": \"foo\", \"type\": \"foo\",
@ -262,85 +262,85 @@ mod tests {
}, },
\"challenges\": [] \"challenges\": []
}"; }";
let a = Authorization::from_str(data);
assert!(a.is_err());
}
let a = Authorization::from_str(data);
assert!(a.is_err());
}
#[test]
fn test_http01_challenge() {
let data = "{
#[test]
fn test_http01_challenge() {
let data = "{
\"type\": \"http-01\", \"type\": \"http-01\",
\"url\": \"https://example.com/acme/chall/prV_B7yEyA4\", \"url\": \"https://example.com/acme/chall/prV_B7yEyA4\",
\"status\": \"pending\", \"status\": \"pending\",
\"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\" \"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\"
}"; }";
let challenge = Challenge::from_str(data);
assert!(challenge.is_ok());
let challenge = challenge.unwrap();
let c = match challenge {
Challenge::Http01(c) => c,
_ => {
assert!(false);
return;
}
};
assert_eq!(
c.url,
"https://example.com/acme/chall/prV_B7yEyA4".to_string()
);
assert_eq!(c.status, Some(ChallengeStatus::Pending));
assert_eq!(
c.token,
"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0".to_string()
);
assert!(c.validated.is_none());
assert!(c.error.is_none());
}
let challenge = Challenge::from_str(data);
assert!(challenge.is_ok());
let challenge = challenge.unwrap();
let c = match challenge {
Challenge::Http01(c) => c,
_ => {
assert!(false);
return;
}
};
assert_eq!(
c.url,
"https://example.com/acme/chall/prV_B7yEyA4".to_string()
);
assert_eq!(c.status, Some(ChallengeStatus::Pending));
assert_eq!(
c.token,
"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0".to_string()
);
assert!(c.validated.is_none());
assert!(c.error.is_none());
}
#[test]
fn test_dns01_challenge() {
let data = "{
#[test]
fn test_dns01_challenge() {
let data = "{
\"type\": \"http-01\", \"type\": \"http-01\",
\"url\": \"https://example.com/acme/chall/prV_B7yEyA4\", \"url\": \"https://example.com/acme/chall/prV_B7yEyA4\",
\"status\": \"valid\", \"status\": \"valid\",
\"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\" \"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\"
}"; }";
let challenge = Challenge::from_str(data);
assert!(challenge.is_ok());
let challenge = challenge.unwrap();
let c = match challenge {
Challenge::Http01(c) => c,
_ => {
assert!(false);
return;
}
};
assert_eq!(
c.url,
"https://example.com/acme/chall/prV_B7yEyA4".to_string()
);
assert_eq!(c.status, Some(ChallengeStatus::Valid));
assert_eq!(
c.token,
"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0".to_string()
);
assert!(c.validated.is_none());
assert!(c.error.is_none());
}
let challenge = Challenge::from_str(data);
assert!(challenge.is_ok());
let challenge = challenge.unwrap();
let c = match challenge {
Challenge::Http01(c) => c,
_ => {
assert!(false);
return;
}
};
assert_eq!(
c.url,
"https://example.com/acme/chall/prV_B7yEyA4".to_string()
);
assert_eq!(c.status, Some(ChallengeStatus::Valid));
assert_eq!(
c.token,
"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0".to_string()
);
assert!(c.validated.is_none());
assert!(c.error.is_none());
}
#[test]
fn test_unknown_challenge_type() {
let data = "{
#[test]
fn test_unknown_challenge_type() {
let data = "{
\"type\": \"invalid-01\", \"type\": \"invalid-01\",
\"url\": \"https://example.com/acme/chall/prV_B7yEyA4\", \"url\": \"https://example.com/acme/chall/prV_B7yEyA4\",
\"status\": \"pending\", \"status\": \"pending\",
\"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\" \"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\"
}"; }";
let challenge = Challenge::from_str(data);
assert!(challenge.is_ok());
match challenge.unwrap() {
Challenge::Unknown => assert!(true),
_ => assert!(false),
}
}
let challenge = Challenge::from_str(data);
assert!(challenge.is_ok());
match challenge.unwrap() {
Challenge::Unknown => assert!(true),
_ => assert!(false),
}
}
} }

178
acmed/src/acme_proto/structs/directory.rs

@ -5,34 +5,34 @@ use std::str::FromStr;
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DirectoryMeta { pub struct DirectoryMeta {
pub terms_of_service: Option<String>,
pub website: Option<String>,
pub caa_identities: Option<Vec<String>>,
pub external_account_required: Option<bool>,
pub terms_of_service: Option<String>,
pub website: Option<String>,
pub caa_identities: Option<Vec<String>>,
pub external_account_required: Option<bool>,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Directory { pub struct Directory {
pub meta: Option<DirectoryMeta>,
pub new_nonce: String,
pub new_account: String,
pub new_order: String,
pub new_authz: Option<String>,
pub revoke_cert: String,
pub key_change: String,
pub meta: Option<DirectoryMeta>,
pub new_nonce: String,
pub new_account: String,
pub new_order: String,
pub new_authz: Option<String>,
pub revoke_cert: String,
pub key_change: String,
} }
deserialize_from_str!(Directory); deserialize_from_str!(Directory);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Directory;
use std::str::FromStr;
use super::Directory;
use std::str::FromStr;
#[test]
fn test_directory() {
let data = "{
#[test]
fn test_directory() {
let data = "{
\"newAccount\": \"https://example.org/acme/new-acct\", \"newAccount\": \"https://example.org/acme/new-acct\",
\"newNonce\": \"https://example.org/acme/new-nonce\", \"newNonce\": \"https://example.org/acme/new-nonce\",
\"newOrder\": \"https://example.org/acme/new-order\", \"newOrder\": \"https://example.org/acme/new-order\",
@ -40,51 +40,51 @@ mod tests {
\"newAuthz\": \"https://example.org/acme/new-authz\", \"newAuthz\": \"https://example.org/acme/new-authz\",
\"keyChange\": \"https://example.org/acme/key-change\" \"keyChange\": \"https://example.org/acme/key-change\"
}"; }";
let parsed_dir = Directory::from_str(data);
assert!(parsed_dir.is_ok());
let parsed_dir = parsed_dir.unwrap();
assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce");
assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct");
assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order");
assert_eq!(
parsed_dir.new_authz,
Some("https://example.org/acme/new-authz".to_string())
);
assert_eq!(
parsed_dir.revoke_cert,
"https://example.org/acme/revoke-cert"
);
assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change");
assert!(parsed_dir.meta.is_none());
}
let parsed_dir = Directory::from_str(data);
assert!(parsed_dir.is_ok());
let parsed_dir = parsed_dir.unwrap();
assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce");
assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct");
assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order");
assert_eq!(
parsed_dir.new_authz,
Some("https://example.org/acme/new-authz".to_string())
);
assert_eq!(
parsed_dir.revoke_cert,
"https://example.org/acme/revoke-cert"
);
assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change");
assert!(parsed_dir.meta.is_none());
}
#[test]
fn test_directory_no_authz() {
let data = "{
#[test]
fn test_directory_no_authz() {
let data = "{
\"newAccount\": \"https://example.org/acme/new-acct\", \"newAccount\": \"https://example.org/acme/new-acct\",
\"newNonce\": \"https://example.org/acme/new-nonce\", \"newNonce\": \"https://example.org/acme/new-nonce\",
\"newOrder\": \"https://example.org/acme/new-order\", \"newOrder\": \"https://example.org/acme/new-order\",
\"revokeCert\": \"https://example.org/acme/revoke-cert\", \"revokeCert\": \"https://example.org/acme/revoke-cert\",
\"keyChange\": \"https://example.org/acme/key-change\" \"keyChange\": \"https://example.org/acme/key-change\"
}"; }";
let parsed_dir = Directory::from_str(data);
assert!(parsed_dir.is_ok());
let parsed_dir = parsed_dir.unwrap();
assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce");
assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct");
assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order");
assert!(parsed_dir.new_authz.is_none());
assert_eq!(
parsed_dir.revoke_cert,
"https://example.org/acme/revoke-cert"
);
assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change");
assert!(parsed_dir.meta.is_none());
}
let parsed_dir = Directory::from_str(data);
assert!(parsed_dir.is_ok());
let parsed_dir = parsed_dir.unwrap();
assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce");
assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct");
assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order");
assert!(parsed_dir.new_authz.is_none());
assert_eq!(
parsed_dir.revoke_cert,
"https://example.org/acme/revoke-cert"
);
assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change");
assert!(parsed_dir.meta.is_none());
}
#[test]
fn test_directory_meta() {
let data = "{
#[test]
fn test_directory_meta() {
let data = "{
\"keyChange\": \"https://example.org/acme/key-change\", \"keyChange\": \"https://example.org/acme/key-change\",
\"meta\": { \"meta\": {
\"caaIdentities\": [ \"caaIdentities\": [
@ -98,26 +98,26 @@ mod tests {
\"newOrder\": \"https://example.org/acme/new-order\", \"newOrder\": \"https://example.org/acme/new-order\",
\"revokeCert\": \"https://example.org/acme/revoke-cert\" \"revokeCert\": \"https://example.org/acme/revoke-cert\"
}"; }";
let parsed_dir = Directory::from_str(&data);
assert!(parsed_dir.is_ok());
let parsed_dir = parsed_dir.unwrap();
assert!(parsed_dir.meta.is_some());
let meta = parsed_dir.meta.unwrap();
assert_eq!(
meta.terms_of_service,
Some("https://example.org/documents/tos.pdf".to_string())
);
assert_eq!(meta.website, Some("https://example.org/".to_string()));
assert!(meta.caa_identities.is_some());
let caa_identities = meta.caa_identities.unwrap();
assert_eq!(caa_identities.len(), 1);
assert_eq!(caa_identities.first(), Some(&"example.org".to_string()));
assert!(meta.external_account_required.is_none());
}
let parsed_dir = Directory::from_str(&data);
assert!(parsed_dir.is_ok());
let parsed_dir = parsed_dir.unwrap();
assert!(parsed_dir.meta.is_some());
let meta = parsed_dir.meta.unwrap();
assert_eq!(
meta.terms_of_service,
Some("https://example.org/documents/tos.pdf".to_string())
);
assert_eq!(meta.website, Some("https://example.org/".to_string()));
assert!(meta.caa_identities.is_some());
let caa_identities = meta.caa_identities.unwrap();
assert_eq!(caa_identities.len(), 1);
assert_eq!(caa_identities.first(), Some(&"example.org".to_string()));
assert!(meta.external_account_required.is_none());
}
#[test]
fn test_directory_extra_fields() {
let data = "{
#[test]
fn test_directory_extra_fields() {
let data = "{
\"foo\": \"bar\", \"foo\": \"bar\",
\"keyChange\": \"https://example.org/acme/key-change\", \"keyChange\": \"https://example.org/acme/key-change\",
\"newAccount\": \"https://example.org/acme/new-acct\", \"newAccount\": \"https://example.org/acme/new-acct\",
@ -127,21 +127,21 @@ mod tests {
\"newOrder\": \"https://example.org/acme/new-order\", \"newOrder\": \"https://example.org/acme/new-order\",
\"revokeCert\": \"https://example.org/acme/revoke-cert\" \"revokeCert\": \"https://example.org/acme/revoke-cert\"
}"; }";
let parsed_dir = Directory::from_str(&data);
assert!(parsed_dir.is_ok());
let parsed_dir = parsed_dir.unwrap();
assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce");
assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct");
assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order");
assert_eq!(
parsed_dir.new_authz,
Some("https://example.org/acme/new-authz".to_string())
);
assert_eq!(
parsed_dir.revoke_cert,
"https://example.org/acme/revoke-cert"
);
assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change");
assert!(parsed_dir.meta.is_none());
}
let parsed_dir = Directory::from_str(&data);
assert!(parsed_dir.is_ok());
let parsed_dir = parsed_dir.unwrap();
assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce");
assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct");
assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order");
assert_eq!(
parsed_dir.new_authz,
Some("https://example.org/acme/new-authz".to_string())
);
assert_eq!(
parsed_dir.revoke_cert,
"https://example.org/acme/revoke-cert"
);
assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change");
assert!(parsed_dir.meta.is_none());
}
} }

210
acmed/src/acme_proto/structs/error.rs

@ -4,75 +4,75 @@ use std::fmt;
use std::str::FromStr; use std::str::FromStr;
pub trait ApiError { pub trait ApiError {
fn get_error(&self) -> Option<Error>;
fn get_error(&self) -> Option<Error>;
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum AcmeError { pub enum AcmeError {
AccountDoesNotExist,
AlreadyRevoked,
BadCSR,
BadNonce,
BadPublicKey,
BadRevocationReason,
BadSignatureAlgorithm,
Caa,
Compound,
Connection,
Dns,
ExternalAccountRequired,
IncorrectResponse,
InvalidContact,
Malformed,
OrderNotReady,
RateLimited,
RejectedIdentifier,
ServerInternal,
Tls,
Unauthorized,
UnsupportedContact,
UnsupportedIdentifier,
UserActionRequired,
Unknown,
AccountDoesNotExist,
AlreadyRevoked,
BadCSR,
BadNonce,
BadPublicKey,
BadRevocationReason,
BadSignatureAlgorithm,
Caa,
Compound,
Connection,
Dns,
ExternalAccountRequired,
IncorrectResponse,
InvalidContact,
Malformed,
OrderNotReady,
RateLimited,
RejectedIdentifier,
ServerInternal,
Tls,
Unauthorized,
UnsupportedContact,
UnsupportedIdentifier,
UserActionRequired,
Unknown,
} }
impl From<String> for AcmeError { impl From<String> for AcmeError {
fn from(error: String) -> Self {
match error.as_str() {
"urn:ietf:params:acme:error:accountDoesNotExist" => AcmeError::AccountDoesNotExist,
"urn:ietf:params:acme:error:alreadyRevoked" => AcmeError::AlreadyRevoked,
"urn:ietf:params:acme:error:badCSR" => AcmeError::BadCSR,
"urn:ietf:params:acme:error:badNonce" => AcmeError::BadNonce,
"urn:ietf:params:acme:error:badPublicKey" => AcmeError::BadPublicKey,
"urn:ietf:params:acme:error:badRevocationReason" => AcmeError::BadRevocationReason,
"urn:ietf:params:acme:error:badSignatureAlgorithm" => AcmeError::BadSignatureAlgorithm,
"urn:ietf:params:acme:error:caa" => AcmeError::Caa,
"urn:ietf:params:acme:error:compound" => AcmeError::Compound,
"urn:ietf:params:acme:error:connection" => AcmeError::Connection,
"urn:ietf:params:acme:error:dns" => AcmeError::Dns,
"urn:ietf:params:acme:error:externalAccountRequired" => {
AcmeError::ExternalAccountRequired
}
"urn:ietf:params:acme:error:incorrectResponse" => AcmeError::IncorrectResponse,
"urn:ietf:params:acme:error:invalidContact" => AcmeError::InvalidContact,
"urn:ietf:params:acme:error:malformed" => AcmeError::Malformed,
"urn:ietf:params:acme:error:orderNotReady" => AcmeError::OrderNotReady,
"urn:ietf:params:acme:error:rateLimited" => AcmeError::RateLimited,
"urn:ietf:params:acme:error:rejectedIdentifier" => AcmeError::RejectedIdentifier,
"urn:ietf:params:acme:error:serverInternal" => AcmeError::ServerInternal,
"urn:ietf:params:acme:error:tls" => AcmeError::Tls,
"urn:ietf:params:acme:error:unauthorized" => AcmeError::Unauthorized,
"urn:ietf:params:acme:error:unsupportedContact" => AcmeError::UnsupportedContact,
"urn:ietf:params:acme:error:unsupportedIdentifier" => AcmeError::UnsupportedIdentifier,
"urn:ietf:params:acme:error:userActionRequired" => AcmeError::UserActionRequired,
_ => AcmeError::Unknown,
}
}
fn from(error: String) -> Self {
match error.as_str() {
"urn:ietf:params:acme:error:accountDoesNotExist" => AcmeError::AccountDoesNotExist,
"urn:ietf:params:acme:error:alreadyRevoked" => AcmeError::AlreadyRevoked,
"urn:ietf:params:acme:error:badCSR" => AcmeError::BadCSR,
"urn:ietf:params:acme:error:badNonce" => AcmeError::BadNonce,
"urn:ietf:params:acme:error:badPublicKey" => AcmeError::BadPublicKey,
"urn:ietf:params:acme:error:badRevocationReason" => AcmeError::BadRevocationReason,
"urn:ietf:params:acme:error:badSignatureAlgorithm" => AcmeError::BadSignatureAlgorithm,
"urn:ietf:params:acme:error:caa" => AcmeError::Caa,
"urn:ietf:params:acme:error:compound" => AcmeError::Compound,
"urn:ietf:params:acme:error:connection" => AcmeError::Connection,
"urn:ietf:params:acme:error:dns" => AcmeError::Dns,
"urn:ietf:params:acme:error:externalAccountRequired" => {
AcmeError::ExternalAccountRequired
}
"urn:ietf:params:acme:error:incorrectResponse" => AcmeError::IncorrectResponse,
"urn:ietf:params:acme:error:invalidContact" => AcmeError::InvalidContact,
"urn:ietf:params:acme:error:malformed" => AcmeError::Malformed,
"urn:ietf:params:acme:error:orderNotReady" => AcmeError::OrderNotReady,
"urn:ietf:params:acme:error:rateLimited" => AcmeError::RateLimited,
"urn:ietf:params:acme:error:rejectedIdentifier" => AcmeError::RejectedIdentifier,
"urn:ietf:params:acme:error:serverInternal" => AcmeError::ServerInternal,
"urn:ietf:params:acme:error:tls" => AcmeError::Tls,
"urn:ietf:params:acme:error:unauthorized" => AcmeError::Unauthorized,
"urn:ietf:params:acme:error:unsupportedContact" => AcmeError::UnsupportedContact,
"urn:ietf:params:acme:error:unsupportedIdentifier" => AcmeError::UnsupportedIdentifier,
"urn:ietf:params:acme:error:userActionRequired" => AcmeError::UserActionRequired,
_ => AcmeError::Unknown,
}
}
} }
impl fmt::Display for AcmeError { impl fmt::Display for AcmeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match self {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match self {
AcmeError::AccountDoesNotExist => "the request specified an account that does not exist", AcmeError::AccountDoesNotExist => "the request specified an account that does not exist",
AcmeError::AlreadyRevoked => "the request specified a certificate to be revoked that has already been revoked", AcmeError::AlreadyRevoked => "the request specified a certificate to be revoked that has already been revoked",
AcmeError::BadCSR => "the CSR is unacceptable (e.g., due to a short key)", AcmeError::BadCSR => "the CSR is unacceptable (e.g., due to a short key)",
@ -99,75 +99,75 @@ impl fmt::Display for AcmeError {
AcmeError::UserActionRequired => "visit the \"instance\" URL and take actions specified there", AcmeError::UserActionRequired => "visit the \"instance\" URL and take actions specified there",
AcmeError::Unknown => "unknown error", AcmeError::Unknown => "unknown error",
}; };
write!(f, "{}", msg)
}
write!(f, "{}", msg)
}
} }
impl AcmeError { impl AcmeError {
pub fn is_recoverable(&self) -> bool {
*self == AcmeError::BadNonce
|| *self == AcmeError::Connection
|| *self == AcmeError::Dns
|| *self == AcmeError::Malformed
|| *self == AcmeError::RateLimited
|| *self == AcmeError::ServerInternal
|| *self == AcmeError::Tls
}
pub fn is_recoverable(&self) -> bool {
*self == AcmeError::BadNonce
|| *self == AcmeError::Connection
|| *self == AcmeError::Dns
|| *self == AcmeError::Malformed
|| *self == AcmeError::RateLimited
|| *self == AcmeError::ServerInternal
|| *self == AcmeError::Tls
}
} }
impl From<Error> for AcmeError { impl From<Error> for AcmeError {
fn from(_error: Error) -> Self {
AcmeError::Unknown
}
fn from(_error: Error) -> Self {
AcmeError::Unknown
}
} }
impl From<AcmeError> for Error { impl From<AcmeError> for Error {
fn from(error: AcmeError) -> Self {
error.to_string().into()
}
fn from(error: AcmeError) -> Self {
error.to_string().into()
}
} }
#[derive(Clone, Debug, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct HttpApiError { pub struct HttpApiError {
#[serde(rename = "type")]
error_type: Option<String>,
// title: Option<String>,
status: Option<usize>,
detail: Option<String>,
// instance: Option<String>,
// TODO: implement subproblems
#[serde(rename = "type")]
error_type: Option<String>,
// title: Option<String>,
status: Option<usize>,
detail: Option<String>,
// instance: Option<String>,
// TODO: implement subproblems
} }
crate::acme_proto::structs::deserialize_from_str!(HttpApiError); crate::acme_proto::structs::deserialize_from_str!(HttpApiError);
impl fmt::Display for HttpApiError { impl fmt::Display for HttpApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = self
.detail
.to_owned()
.unwrap_or_else(|| self.get_acme_type().to_string());
let msg = match self.status {
Some(s) => format!("status {}: {}", s, msg),
None => msg,
};
write!(f, "{}", msg)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = self
.detail
.to_owned()
.unwrap_or_else(|| self.get_acme_type().to_string());
let msg = match self.status {
Some(s) => format!("status {}: {}", s, msg),
None => msg,
};
write!(f, "{}", msg)
}
} }
impl HttpApiError { impl HttpApiError {
pub fn get_type(&self) -> String {
self.error_type
.to_owned()
.unwrap_or_else(|| String::from("about:blank"))
}
pub fn get_type(&self) -> String {
self.error_type
.to_owned()
.unwrap_or_else(|| String::from("about:blank"))
}
pub fn get_acme_type(&self) -> AcmeError {
self.get_type().into()
}
pub fn get_acme_type(&self) -> AcmeError {
self.get_type().into()
}
} }
impl From<HttpApiError> for Error { impl From<HttpApiError> for Error {
fn from(error: HttpApiError) -> Self {
error.to_string().into()
}
fn from(error: HttpApiError) -> Self {
error.to_string().into()
}
} }

160
acmed/src/acme_proto/structs/order.rs

@ -8,41 +8,41 @@ use std::str::FromStr;
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct NewOrder { pub struct NewOrder {
pub identifiers: Vec<Identifier>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_after: Option<String>,
pub identifiers: Vec<Identifier>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_after: Option<String>,
} }
impl NewOrder { impl NewOrder {
pub fn new(identifiers: &[identifier::Identifier]) -> Self {
NewOrder {
identifiers: identifiers.iter().map(Identifier::from_generic).collect(),
not_before: None,
not_after: None,
}
}
pub fn new(identifiers: &[identifier::Identifier]) -> Self {
NewOrder {
identifiers: identifiers.iter().map(Identifier::from_generic).collect(),
not_before: None,
not_after: None,
}
}
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Order { pub struct Order {
pub status: OrderStatus,
pub expires: Option<String>,
pub identifiers: Vec<Identifier>,
pub not_before: Option<String>,
pub not_after: Option<String>,
pub error: Option<HttpApiError>,
pub authorizations: Vec<String>,
pub finalize: String,
pub certificate: Option<String>,
pub status: OrderStatus,
pub expires: Option<String>,
pub identifiers: Vec<Identifier>,
pub not_before: Option<String>,
pub not_after: Option<String>,
pub error: Option<HttpApiError>,
pub authorizations: Vec<String>,
pub finalize: String,
pub certificate: Option<String>,
} }
impl ApiError for Order { impl ApiError for Order {
fn get_error(&self) -> Option<Error> {
self.error.to_owned().map(Error::from)
}
fn get_error(&self) -> Option<Error> {
self.error.to_owned().map(Error::from)
}
} }
deserialize_from_str!(Order); deserialize_from_str!(Order);
@ -50,82 +50,82 @@ deserialize_from_str!(Order);
#[derive(Debug, PartialEq, Eq, Deserialize)] #[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum OrderStatus { pub enum OrderStatus {
Pending,
Ready,
Processing,
Valid,
Invalid,
Pending,
Ready,
Processing,
Valid,
Invalid,
} }
impl fmt::Display for OrderStatus { impl fmt::Display for OrderStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
OrderStatus::Pending => "pending",
OrderStatus::Ready => "ready",
OrderStatus::Processing => "processing",
OrderStatus::Valid => "valid",
OrderStatus::Invalid => "invalid",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
OrderStatus::Pending => "pending",
OrderStatus::Ready => "ready",
OrderStatus::Processing => "processing",
OrderStatus::Valid => "valid",
OrderStatus::Invalid => "invalid",
};
write!(f, "{}", s)
}
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Identifier { pub struct Identifier {
#[serde(rename = "type")]
pub id_type: IdentifierType,
pub value: String,
#[serde(rename = "type")]
pub id_type: IdentifierType,
pub value: String,
} }
impl Identifier { impl Identifier {
pub fn from_generic(id: &identifier::Identifier) -> Self {
Identifier {
id_type: id.id_type.to_owned(),
value: id.value.to_owned(),
}
}
pub fn from_generic(id: &identifier::Identifier) -> Self {
Identifier {
id_type: id.id_type.to_owned(),
value: id.value.to_owned(),
}
}
} }
deserialize_from_str!(Identifier); deserialize_from_str!(Identifier);
impl fmt::Display for Identifier { impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.id_type, self.value)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.id_type, self.value)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Identifier, IdentifierType};
use std::str::FromStr;
use super::{Identifier, IdentifierType};
use std::str::FromStr;
#[test]
fn id_serialize() {
let reference = "{\"type\":\"dns\",\"value\":\"test.example.org\"}";
let id = Identifier {
id_type: IdentifierType::Dns,
value: "test.example.org".to_string(),
};
let id_json = serde_json::to_string(&id);
assert!(id_json.is_ok());
let id_json = id_json.unwrap();
assert_eq!(id_json, reference.to_string());
}
#[test]
fn id_serialize() {
let reference = "{\"type\":\"dns\",\"value\":\"test.example.org\"}";
let id = Identifier {
id_type: IdentifierType::Dns,
value: "test.example.org".to_string(),
};
let id_json = serde_json::to_string(&id);
assert!(id_json.is_ok());
let id_json = id_json.unwrap();
assert_eq!(id_json, reference.to_string());
}
#[test]
fn id_deserialize_valid() {
let id_str = "{\"type\":\"dns\",\"value\":\"test.example.org\"}";
let id = Identifier::from_str(id_str);
assert!(id.is_ok());
let id = id.unwrap();
assert_eq!(id.id_type, IdentifierType::Dns);
assert_eq!(id.value, "test.example.org".to_string());
}
#[test]
fn id_deserialize_valid() {
let id_str = "{\"type\":\"dns\",\"value\":\"test.example.org\"}";
let id = Identifier::from_str(id_str);
assert!(id.is_ok());
let id = id.unwrap();
assert_eq!(id.id_type, IdentifierType::Dns);
assert_eq!(id.value, "test.example.org".to_string());
}
#[test]
fn id_deserialize_invalid_type() {
let id_str = "{\"type\":\"trololo\",\"value\":\"test.example.org\"}";
let id = Identifier::from_str(id_str);
assert!(id.is_err());
}
#[test]
fn id_deserialize_invalid_type() {
let id_str = "{\"type\":\"trololo\",\"value\":\"test.example.org\"}";
let id = Identifier::from_str(id_str);
assert!(id.is_err());
}
} }

342
acmed/src/certificate.rs

@ -12,187 +12,187 @@ use std::time::Duration;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Certificate { pub struct Certificate {
pub account_name: String,
pub identifiers: Vec<Identifier>,
pub subject_attributes: HashMap<SubjectAttribute, String>,
pub key_type: KeyType,
pub csr_digest: HashFunction,
pub kp_reuse: bool,
pub endpoint_name: String,
pub hooks: Vec<Hook>,
pub crt_name: String,
pub env: HashMap<String, String>,
pub renew_delay: Duration,
pub file_manager: FileManager,
pub account_name: String,
pub identifiers: Vec<Identifier>,
pub subject_attributes: HashMap<SubjectAttribute, String>,
pub key_type: KeyType,
pub csr_digest: HashFunction,
pub kp_reuse: bool,
pub endpoint_name: String,
pub hooks: Vec<Hook>,
pub crt_name: String,
pub env: HashMap<String, String>,
pub renew_delay: Duration,
pub file_manager: FileManager,
} }
impl fmt::Display for Certificate { impl fmt::Display for Certificate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.get_id())
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.get_id())
}
} }
impl HasLogger for Certificate { impl HasLogger for Certificate {
fn warn(&self, msg: &str) {
warn!("certificate \"{}\": {}", &self, msg);
}
fn warn(&self, msg: &str) {
warn!("certificate \"{}\": {}", &self, msg);
}
fn info(&self, msg: &str) {
info!("certificate \"{}\": {}", &self, msg);
}
fn info(&self, msg: &str) {
info!("certificate \"{}\": {}", &self, msg);
}
fn debug(&self, msg: &str) {
debug!("certificate \"{}\": {}", &self, msg);
}
fn debug(&self, msg: &str) {
debug!("certificate \"{}\": {}", &self, msg);
}
fn trace(&self, msg: &str) {
trace!("certificate \"{}\": {}", &self, msg);
}
fn trace(&self, msg: &str) {
trace!("certificate \"{}\": {}", &self, msg);
}
} }
impl Certificate { impl Certificate {
pub fn get_id(&self) -> String {
format!("{}_{}", self.crt_name, self.key_type)
}
pub fn get_identifier_from_str(&self, identifier: &str) -> Result<Identifier, Error> {
let identifier = identifier.to_string();
for d in self.identifiers.iter() {
let val = match d.id_type {
// strip wildcards from domain before matching
IdentifierType::Dns => d.value.trim_start_matches("*.").to_string(),
IdentifierType::Ip => d.value.to_owned(),
};
if identifier == val {
return Ok(d.clone());
}
}
Err(format!("{}: identifier not found", identifier).into())
}
fn is_expiring(&self, cert: &X509Certificate) -> Result<bool, Error> {
let expires_in = cert.expires_in()?;
self.debug(&format!(
"certificate expires in {} days ({} days delay)",
expires_in.as_secs() / 86400,
self.renew_delay.as_secs() / 86400,
));
Ok(expires_in <= self.renew_delay)
}
fn has_missing_identifiers(&self, cert: &X509Certificate) -> bool {
let cert_names = cert.subject_alt_names();
let req_names = self
.identifiers
.iter()
.map(|v| v.value.to_owned())
.collect::<HashSet<String>>();
let has_miss = req_names.difference(&cert_names).count() != 0;
if has_miss {
let domains = req_names
.difference(&cert_names)
.map(std::borrow::ToOwned::to_owned)
.collect::<Vec<String>>()
.join(", ");
self.debug(&format!(
"the certificate does not include the following domains: {}",
domains
));
}
has_miss
}
/// Return a comma-separated list of the domains this certificate is valid for.
pub fn identifier_list(&self) -> String {
self.identifiers
.iter()
.map(|d| d.value.as_str())
.collect::<Vec<&str>>()
.join(",")
}
pub fn should_renew(&self) -> Result<bool, Error> {
self.debug(&format!(
"checking for renewal (identifiers: {})",
self.identifier_list()
));
if !certificate_files_exists(&self.file_manager) {
self.debug("certificate does not exist: requesting one");
return Ok(true);
}
let cert = get_certificate(&self.file_manager)?;
let renew_ident = self.has_missing_identifiers(&cert);
if renew_ident {
self.debug("the current certificate doesn't include all the required identifiers");
}
let renew_exp = self.is_expiring(&cert)?;
if renew_exp {
self.debug("the certificate is expiring");
}
let renew = renew_ident || renew_exp;
if renew {
self.debug("the certificate will be renewed now");
} else {
self.debug("the certificate will not be renewed now");
}
Ok(renew)
}
pub fn call_challenge_hooks(
&self,
file_name: &str,
proof: &str,
identifier: &str,
) -> Result<(ChallengeHookData, HookType), Error> {
let identifier = self.get_identifier_from_str(identifier)?;
let mut hook_data = ChallengeHookData {
challenge: identifier.challenge.to_string(),
identifier: identifier.value.to_owned(),
identifier_tls_alpn: identifier.get_tls_alpn_name().unwrap_or_default(),
file_name: file_name.to_string(),
proof: proof.to_string(),
is_clean_hook: false,
env: HashMap::new(),
};
hook_data.set_env(&self.env);
hook_data.set_env(&identifier.env);
let hook_type = match identifier.challenge {
Challenge::Http01 => (HookType::ChallengeHttp01, HookType::ChallengeHttp01Clean),
Challenge::Dns01 => (HookType::ChallengeDns01, HookType::ChallengeDns01Clean),
Challenge::TlsAlpn01 => (
HookType::ChallengeTlsAlpn01,
HookType::ChallengeTlsAlpn01Clean,
),
};
hooks::call(self, &self.hooks, &hook_data, hook_type.0)?;
Ok((hook_data, hook_type.1))
}
pub fn call_challenge_hooks_clean(
&self,
data: &ChallengeHookData,
hook_type: HookType,
) -> Result<(), Error> {
hooks::call(self, &self.hooks, data, hook_type)
}
pub fn call_post_operation_hooks(&self, status: &str, is_success: bool) -> Result<(), Error> {
let identifiers = self
.identifiers
.iter()
.map(|d| d.value.to_owned())
.collect::<Vec<String>>();
let mut hook_data = PostOperationHookData {
identifiers,
key_type: self.key_type.to_string(),
status: status.to_string(),
is_success,
env: HashMap::new(),
};
hook_data.set_env(&self.env);
hooks::call(self, &self.hooks, &hook_data, HookType::PostOperation)?;
Ok(())
}
pub fn get_id(&self) -> String {
format!("{}_{}", self.crt_name, self.key_type)
}
pub fn get_identifier_from_str(&self, identifier: &str) -> Result<Identifier, Error> {
let identifier = identifier.to_string();
for d in self.identifiers.iter() {
let val = match d.id_type {
// strip wildcards from domain before matching
IdentifierType::Dns => d.value.trim_start_matches("*.").to_string(),
IdentifierType::Ip => d.value.to_owned(),
};
if identifier == val {
return Ok(d.clone());
}
}
Err(format!("{}: identifier not found", identifier).into())
}
fn is_expiring(&self, cert: &X509Certificate) -> Result<bool, Error> {
let expires_in = cert.expires_in()?;
self.debug(&format!(
"certificate expires in {} days ({} days delay)",
expires_in.as_secs() / 86400,
self.renew_delay.as_secs() / 86400,
));
Ok(expires_in <= self.renew_delay)
}
fn has_missing_identifiers(&self, cert: &X509Certificate) -> bool {
let cert_names = cert.subject_alt_names();
let req_names = self
.identifiers
.iter()
.map(|v| v.value.to_owned())
.collect::<HashSet<String>>();
let has_miss = req_names.difference(&cert_names).count() != 0;
if has_miss {
let domains = req_names
.difference(&cert_names)
.map(std::borrow::ToOwned::to_owned)
.collect::<Vec<String>>()
.join(", ");
self.debug(&format!(
"the certificate does not include the following domains: {}",
domains
));
}
has_miss
}
/// Return a comma-separated list of the domains this certificate is valid for.
pub fn identifier_list(&self) -> String {
self.identifiers
.iter()
.map(|d| d.value.as_str())
.collect::<Vec<&str>>()
.join(",")
}
pub fn should_renew(&self) -> Result<bool, Error> {
self.debug(&format!(
"checking for renewal (identifiers: {})",
self.identifier_list()
));
if !certificate_files_exists(&self.file_manager) {
self.debug("certificate does not exist: requesting one");
return Ok(true);
}
let cert = get_certificate(&self.file_manager)?;
let renew_ident = self.has_missing_identifiers(&cert);
if renew_ident {
self.debug("the current certificate doesn't include all the required identifiers");
}
let renew_exp = self.is_expiring(&cert)?;
if renew_exp {
self.debug("the certificate is expiring");
}
let renew = renew_ident || renew_exp;
if renew {
self.debug("the certificate will be renewed now");
} else {
self.debug("the certificate will not be renewed now");
}
Ok(renew)
}
pub fn call_challenge_hooks(
&self,
file_name: &str,
proof: &str,
identifier: &str,
) -> Result<(ChallengeHookData, HookType), Error> {
let identifier = self.get_identifier_from_str(identifier)?;
let mut hook_data = ChallengeHookData {
challenge: identifier.challenge.to_string(),
identifier: identifier.value.to_owned(),
identifier_tls_alpn: identifier.get_tls_alpn_name().unwrap_or_default(),
file_name: file_name.to_string(),
proof: proof.to_string(),
is_clean_hook: false,
env: HashMap::new(),
};
hook_data.set_env(&self.env);
hook_data.set_env(&identifier.env);
let hook_type = match identifier.challenge {
Challenge::Http01 => (HookType::ChallengeHttp01, HookType::ChallengeHttp01Clean),
Challenge::Dns01 => (HookType::ChallengeDns01, HookType::ChallengeDns01Clean),
Challenge::TlsAlpn01 => (
HookType::ChallengeTlsAlpn01,
HookType::ChallengeTlsAlpn01Clean,
),
};
hooks::call(self, &self.hooks, &hook_data, hook_type.0)?;
Ok((hook_data, hook_type.1))
}
pub fn call_challenge_hooks_clean(
&self,
data: &ChallengeHookData,
hook_type: HookType,
) -> Result<(), Error> {
hooks::call(self, &self.hooks, data, hook_type)
}
pub fn call_post_operation_hooks(&self, status: &str, is_success: bool) -> Result<(), Error> {
let identifiers = self
.identifiers
.iter()
.map(|d| d.value.to_owned())
.collect::<Vec<String>>();
let mut hook_data = PostOperationHookData {
identifiers,
key_type: self.key_type.to_string(),
status: status.to_string(),
is_success,
env: HashMap::new(),
};
hook_data.set_env(&self.env);
hooks::call(self, &self.hooks, &hook_data, HookType::PostOperation)?;
Ok(())
}
} }

1216
acmed/src/config.rs
File diff suppressed because it is too large
View File

58
acmed/src/duration.rs

@ -7,45 +7,45 @@ use nom::IResult;
use std::time::Duration; use std::time::Duration;
fn is_duration_chr(c: char) -> bool { fn is_duration_chr(c: char) -> bool {
c == 's' || c == 'm' || c == 'h' || c == 'd' || c == 'w'
c == 's' || c == 'm' || c == 'h' || c == 'd' || c == 'w'
} }
fn get_multiplicator(input: &str) -> IResult<&str, u64> { fn get_multiplicator(input: &str) -> IResult<&str, u64> {
let (input, nb) = take_while_m_n(1, 1, is_duration_chr)(input)?;
let mult = match nb.chars().next() {
Some('s') => 1,
Some('m') => 60,
Some('h') => 3_600,
Some('d') => 86_400,
Some('w') => 604_800,
_ => 0,
};
Ok((input, mult))
let (input, nb) = take_while_m_n(1, 1, is_duration_chr)(input)?;
let mult = match nb.chars().next() {
Some('s') => 1,
Some('m') => 60,
Some('h') => 3_600,
Some('d') => 86_400,
Some('w') => 604_800,
_ => 0,
};
Ok((input, mult))
} }
fn get_duration_part(input: &str) -> IResult<&str, Duration> { fn get_duration_part(input: &str) -> IResult<&str, Duration> {
let (input, nb) = map_res(digit1, |s: &str| s.parse::<u64>())(input)?;
let (input, mult) = get_multiplicator(input)?;
Ok((input, Duration::from_secs(nb * mult)))
let (input, nb) = map_res(digit1, |s: &str| s.parse::<u64>())(input)?;
let (input, mult) = get_multiplicator(input)?;
Ok((input, Duration::from_secs(nb * mult)))
} }
fn get_duration(input: &str) -> IResult<&str, Duration> { fn get_duration(input: &str) -> IResult<&str, Duration> {
fold_many1(
get_duration_part,
|| Duration::new(0, 0),
|mut acc: Duration, item| {
acc += item;
acc
},
)(input)
fold_many1(
get_duration_part,
|| Duration::new(0, 0),
|mut acc: Duration, item| {
acc += item;
acc
},
)(input)
} }
pub fn parse_duration(input: &str) -> Result<Duration, Error> { pub fn parse_duration(input: &str) -> Result<Duration, Error> {
match get_duration(input) {
Ok((r, d)) => match r.len() {
0 => Ok(d),
_ => Err(format!("{}: invalid duration", input).into()),
},
Err(_) => Err(format!("{}: invalid duration", input).into()),
}
match get_duration(input) {
Ok((r, d)) => match r.len() {
0 => Ok(d),
_ => Err(format!("{}: invalid duration", input).into()),
},
Err(_) => Err(format!("{}: invalid duration", input).into()),
}
} }

198
acmed/src/endpoint.rs

@ -7,117 +7,117 @@ use std::time::{Duration, Instant};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Endpoint { pub struct Endpoint {
pub name: String,
pub url: String,
pub tos_agreed: bool,
pub nonce: Option<String>,
pub rl: RateLimit,
pub dir: Directory,
pub root_certificates: Vec<String>,
pub name: String,
pub url: String,
pub tos_agreed: bool,
pub nonce: Option<String>,
pub rl: RateLimit,
pub dir: Directory,
pub root_certificates: Vec<String>,
} }
impl Endpoint { impl Endpoint {
pub fn new(
name: &str,
url: &str,
tos_agreed: bool,
limits: &[(usize, String)],
root_certs: &[String],
) -> Result<Self, Error> {
Ok(Self {
name: name.to_string(),
url: url.to_string(),
tos_agreed,
nonce: None,
rl: RateLimit::new(limits)?,
dir: Directory {
meta: None,
new_nonce: String::new(),
new_account: String::new(),
new_order: String::new(),
new_authz: None,
revoke_cert: String::new(),
key_change: String::new(),
},
root_certificates: root_certs.to_vec(),
})
}
pub fn new(
name: &str,
url: &str,
tos_agreed: bool,
limits: &[(usize, String)],
root_certs: &[String],
) -> Result<Self, Error> {
Ok(Self {
name: name.to_string(),
url: url.to_string(),
tos_agreed,
nonce: None,
rl: RateLimit::new(limits)?,
dir: Directory {
meta: None,
new_nonce: String::new(),
new_account: String::new(),
new_order: String::new(),
new_authz: None,
revoke_cert: String::new(),
key_change: String::new(),
},
root_certificates: root_certs.to_vec(),
})
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RateLimit { pub struct RateLimit {
limits: Vec<(usize, Duration)>,
query_log: Vec<Instant>,
limits: Vec<(usize, Duration)>,
query_log: Vec<Instant>,
} }
impl RateLimit { impl RateLimit {
pub fn new(raw_limits: &[(usize, String)]) -> Result<Self, Error> {
let mut limits = vec![];
for (nb, raw_duration) in raw_limits.iter() {
let parsed_duration = parse_duration(raw_duration)?;
limits.push((*nb, parsed_duration));
}
limits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
limits.reverse();
Ok(Self {
limits,
query_log: vec![],
})
}
pub fn new(raw_limits: &[(usize, String)]) -> Result<Self, Error> {
let mut limits = vec![];
for (nb, raw_duration) in raw_limits.iter() {
let parsed_duration = parse_duration(raw_duration)?;
limits.push((*nb, parsed_duration));
}
limits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
limits.reverse();
Ok(Self {
limits,
query_log: vec![],
})
}
pub fn block_until_allowed(&mut self) {
if self.limits.is_empty() {
return;
}
let sleep_duration = self.get_sleep_duration();
loop {
self.prune_log();
if self.request_allowed() {
self.query_log.push(Instant::now());
return;
}
// TODO: find a better sleep duration
thread::sleep(sleep_duration);
}
}
pub fn block_until_allowed(&mut self) {
if self.limits.is_empty() {
return;
}
let sleep_duration = self.get_sleep_duration();
loop {
self.prune_log();
if self.request_allowed() {
self.query_log.push(Instant::now());
return;
}
// TODO: find a better sleep duration
thread::sleep(sleep_duration);
}
}
fn get_sleep_duration(&self) -> Duration {
let (nb_req, min_duration) = match self.limits.last() {
Some((n, d)) => (*n as u64, *d),
None => {
return Duration::from_millis(0);
}
};
let nb_mili = match min_duration.as_secs() {
0 | 1 => crate::MIN_RATE_LIMIT_SLEEP_MILISEC,
n => {
let a = n * 200 / nb_req;
let a = cmp::min(a, crate::MAX_RATE_LIMIT_SLEEP_MILISEC);
cmp::max(a, crate::MIN_RATE_LIMIT_SLEEP_MILISEC)
}
};
Duration::from_millis(nb_mili)
}
fn get_sleep_duration(&self) -> Duration {
let (nb_req, min_duration) = match self.limits.last() {
Some((n, d)) => (*n as u64, *d),
None => {
return Duration::from_millis(0);
}
};
let nb_mili = match min_duration.as_secs() {
0 | 1 => crate::MIN_RATE_LIMIT_SLEEP_MILISEC,
n => {
let a = n * 200 / nb_req;
let a = cmp::min(a, crate::MAX_RATE_LIMIT_SLEEP_MILISEC);
cmp::max(a, crate::MIN_RATE_LIMIT_SLEEP_MILISEC)
}
};
Duration::from_millis(nb_mili)
}
fn request_allowed(&self) -> bool {
for (max_allowed, duration) in self.limits.iter() {
let max_date = Instant::now() - *duration;
let nb_req = self
.query_log
.iter()
.filter(move |x| **x > max_date)
.count();
if nb_req >= *max_allowed {
return false;
}
}
true
}
fn request_allowed(&self) -> bool {
for (max_allowed, duration) in self.limits.iter() {
let max_date = Instant::now() - *duration;
let nb_req = self
.query_log
.iter()
.filter(move |x| **x > max_date)
.count();
if nb_req >= *max_allowed {
return false;
}
}
true
}
fn prune_log(&mut self) {
if let Some((_, max_limit)) = self.limits.first() {
let prune_date = Instant::now() - *max_limit;
self.query_log.retain(move |&d| d > prune_date);
}
}
fn prune_log(&mut self) {
if let Some((_, max_limit)) = self.limits.first() {
let prune_date = Instant::now() - *max_limit;
self.query_log.retain(move |&d| d > prune_date);
}
}
} }

294
acmed/src/hooks.rs

@ -13,201 +13,201 @@ use std::process::{Command, Stdio};
use std::{env, fmt}; use std::{env, fmt};
pub trait HookEnvData { pub trait HookEnvData {
fn set_env(&mut self, env: &HashMap<String, String>);
fn get_env(&self) -> Iter<String, String>;
fn set_env(&mut self, env: &HashMap<String, String>);
fn get_env(&self) -> Iter<String, String>;
} }
fn deref<F, G>(t: (&F, &G)) -> (F, G) fn deref<F, G>(t: (&F, &G)) -> (F, G)
where where
F: Clone,
G: Clone,
F: Clone,
G: Clone,
{ {
((*(t.0)).to_owned(), (*(t.1)).to_owned())
((*(t.0)).to_owned(), (*(t.1)).to_owned())
} }
macro_rules! imple_hook_data_env { macro_rules! imple_hook_data_env {
($t: ty) => {
impl HookEnvData for $t {
fn set_env(&mut self, env: &HashMap<String, String>) {
for (key, value) in env::vars().chain(env.iter().map(deref)) {
self.env.insert(key, value);
}
}
fn get_env(&self) -> Iter<String, String> {
self.env.iter()
}
}
};
($t: ty) => {
impl HookEnvData for $t {
fn set_env(&mut self, env: &HashMap<String, String>) {
for (key, value) in env::vars().chain(env.iter().map(deref)) {
self.env.insert(key, value);
}
}
fn get_env(&self) -> Iter<String, String> {
self.env.iter()
}
}
};
} }
#[derive(Clone, Serialize)] #[derive(Clone, Serialize)]
pub struct PostOperationHookData { pub struct PostOperationHookData {
pub identifiers: Vec<String>,
pub key_type: String,
pub status: String,
pub is_success: bool,
pub env: HashMap<String, String>,
pub identifiers: Vec<String>,
pub key_type: String,
pub status: String,
pub is_success: bool,
pub env: HashMap<String, String>,
} }
imple_hook_data_env!(PostOperationHookData); imple_hook_data_env!(PostOperationHookData);
#[derive(Clone, Serialize)] #[derive(Clone, Serialize)]
pub struct ChallengeHookData { pub struct ChallengeHookData {
pub identifier: String,
pub identifier_tls_alpn: String,
pub challenge: String,
pub file_name: String,
pub proof: String,
pub is_clean_hook: bool,
pub env: HashMap<String, String>,
pub identifier: String,
pub identifier_tls_alpn: String,
pub challenge: String,
pub file_name: String,
pub proof: String,
pub is_clean_hook: bool,
pub env: HashMap<String, String>,
} }
imple_hook_data_env!(ChallengeHookData); imple_hook_data_env!(ChallengeHookData);
#[derive(Clone, Serialize)] #[derive(Clone, Serialize)]
pub struct FileStorageHookData { pub struct FileStorageHookData {
// TODO: add the current operation (create/edit)
pub file_name: String,
pub file_directory: String,
pub file_path: PathBuf,
pub env: HashMap<String, String>,
// TODO: add the current operation (create/edit)
pub file_name: String,
pub file_directory: String,
pub file_path: PathBuf,
pub env: HashMap<String, String>,
} }
imple_hook_data_env!(FileStorageHookData); imple_hook_data_env!(FileStorageHookData);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum HookStdin { pub enum HookStdin {
File(String),
Str(String),
None,
File(String),
Str(String),
None,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Hook { pub struct Hook {
pub name: String,
pub hook_type: HashSet<HookType>,
pub cmd: String,
pub args: Option<Vec<String>>,
pub stdin: HookStdin,
pub stdout: Option<String>,
pub stderr: Option<String>,
pub allow_failure: bool,
pub name: String,
pub hook_type: HashSet<HookType>,
pub cmd: String,
pub args: Option<Vec<String>>,
pub stdin: HookStdin,
pub stdout: Option<String>,
pub stderr: Option<String>,
pub allow_failure: bool,
} }
impl fmt::Display for Hook { impl fmt::Display for Hook {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)
}
} }
macro_rules! get_hook_output { macro_rules! get_hook_output {
($logger: expr, $out: expr, $data: expr, $hook_name: expr, $out_name: expr) => {{
match $out {
Some(path) => {
let path = render_template(path, $data)?;
$logger.trace(&format!(
"hook \"{}\": {}: {}",
$hook_name, $out_name, &path
));
let file = File::create(&path)?;
Stdio::from(file)
}
None => Stdio::null(),
}
}};
($logger: expr, $out: expr, $data: expr, $hook_name: expr, $out_name: expr) => {{
match $out {
Some(path) => {
let path = render_template(path, $data)?;
$logger.trace(&format!(
"hook \"{}\": {}: {}",
$hook_name, $out_name, &path
));
let file = File::create(&path)?;
Stdio::from(file)
}
None => Stdio::null(),
}
}};
} }
fn call_single<L, T>(logger: &L, data: &T, hook: &Hook) -> Result<(), Error> fn call_single<L, T>(logger: &L, data: &T, hook: &Hook) -> Result<(), Error>
where where
L: HasLogger,
T: Clone + HookEnvData + Serialize,
L: HasLogger,
T: Clone + HookEnvData + Serialize,
{ {
logger.debug(&format!("calling hook \"{}\"", hook.name));
let mut v = vec![];
let args = match &hook.args {
Some(lst) => {
for fmt in lst.iter() {
let s = render_template(fmt, &data)?;
v.push(s);
}
v.as_slice()
}
None => &[],
};
logger.trace(&format!("hook \"{}\": cmd: {}", hook.name, hook.cmd));
logger.trace(&format!("hook \"{}\": args: {:?}", hook.name, args));
let mut cmd = Command::new(&hook.cmd)
.envs(data.get_env())
.args(args)
.stdout(get_hook_output!(
logger,
&hook.stdout,
&data,
&hook.name,
"stdout"
))
.stderr(get_hook_output!(
logger,
&hook.stderr,
&data,
&hook.name,
"stderr"
))
.stdin(match &hook.stdin {
HookStdin::Str(_) | HookStdin::File(_) => Stdio::piped(),
HookStdin::None => Stdio::null(),
})
.spawn()?;
match &hook.stdin {
HookStdin::Str(s) => {
let data_in = render_template(s, &data)?;
logger.trace(&format!(
"hook \"{}\": string stdin: {}",
hook.name, &data_in
));
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
stdin.write_all(data_in.as_bytes())?;
}
HookStdin::File(f) => {
let file_name = render_template(f, &data)?;
logger.trace(&format!(
"hook \"{}\": file stdin: {}",
hook.name, &file_name
));
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
let file = File::open(&file_name).map_err(|e| Error::from(e).prefix(&file_name))?;
let buf_reader = BufReader::new(file);
for line in buf_reader.lines() {
let line = format!("{}\n", line?);
stdin.write_all(line.as_bytes())?;
}
}
HookStdin::None => {}
}
// TODO: add a timeout
let status = cmd.wait()?;
if !status.success() && !hook.allow_failure {
let msg = match status.code() {
Some(code) => format!("unrecoverable failure: code {}", code).into(),
None => "unrecoverable failure".into(),
};
return Err(msg);
}
match status.code() {
Some(code) => logger.debug(&format!("hook \"{}\": exited: code {}", hook.name, code)),
None => logger.debug(&format!("hook \"{}\": exited", hook.name)),
};
Ok(())
logger.debug(&format!("calling hook \"{}\"", hook.name));
let mut v = vec![];
let args = match &hook.args {
Some(lst) => {
for fmt in lst.iter() {
let s = render_template(fmt, &data)?;
v.push(s);
}
v.as_slice()
}
None => &[],
};
logger.trace(&format!("hook \"{}\": cmd: {}", hook.name, hook.cmd));
logger.trace(&format!("hook \"{}\": args: {:?}", hook.name, args));
let mut cmd = Command::new(&hook.cmd)
.envs(data.get_env())
.args(args)
.stdout(get_hook_output!(
logger,
&hook.stdout,
&data,
&hook.name,
"stdout"
))
.stderr(get_hook_output!(
logger,
&hook.stderr,
&data,
&hook.name,
"stderr"
))
.stdin(match &hook.stdin {
HookStdin::Str(_) | HookStdin::File(_) => Stdio::piped(),
HookStdin::None => Stdio::null(),
})
.spawn()?;
match &hook.stdin {
HookStdin::Str(s) => {
let data_in = render_template(s, &data)?;
logger.trace(&format!(
"hook \"{}\": string stdin: {}",
hook.name, &data_in
));
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
stdin.write_all(data_in.as_bytes())?;
}
HookStdin::File(f) => {
let file_name = render_template(f, &data)?;
logger.trace(&format!(
"hook \"{}\": file stdin: {}",
hook.name, &file_name
));
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
let file = File::open(&file_name).map_err(|e| Error::from(e).prefix(&file_name))?;
let buf_reader = BufReader::new(file);
for line in buf_reader.lines() {
let line = format!("{}\n", line?);
stdin.write_all(line.as_bytes())?;
}
}
HookStdin::None => {}
}
// TODO: add a timeout
let status = cmd.wait()?;
if !status.success() && !hook.allow_failure {
let msg = match status.code() {
Some(code) => format!("unrecoverable failure: code {}", code).into(),
None => "unrecoverable failure".into(),
};
return Err(msg);
}
match status.code() {
Some(code) => logger.debug(&format!("hook \"{}\": exited: code {}", hook.name, code)),
None => logger.debug(&format!("hook \"{}\": exited", hook.name)),
};
Ok(())
} }
pub fn call<L, T>(logger: &L, hooks: &[Hook], data: &T, hook_type: HookType) -> Result<(), Error> pub fn call<L, T>(logger: &L, hooks: &[Hook], data: &T, hook_type: HookType) -> Result<(), Error>
where where
L: HasLogger,
T: Clone + HookEnvData + Serialize,
L: HasLogger,
T: Clone + HookEnvData + Serialize,
{ {
for hook in hooks.iter().filter(|h| h.hook_type.contains(&hook_type)) {
call_single(logger, data, hook).map_err(|e| e.prefix(&hook.name))?;
}
Ok(())
for hook in hooks.iter().filter(|h| h.hook_type.contains(&hook_type)) {
call_single(logger, data, hook).map_err(|e| e.prefix(&hook.name))?;
}
Ok(())
} }

378
acmed/src/http.rs

@ -16,263 +16,263 @@ pub const HEADER_NONCE: &str = "Replay-Nonce";
pub const HEADER_LOCATION: &str = "Location"; pub const HEADER_LOCATION: &str = "Location";
pub struct ValidHttpResponse { pub struct ValidHttpResponse {
headers: attohttpc::header::HeaderMap,
pub body: String,
headers: attohttpc::header::HeaderMap,
pub body: String,
} }
impl ValidHttpResponse { impl ValidHttpResponse {
pub fn get_header(&self, name: &str) -> Option<String> {
match self.headers.get(name) {
Some(r) => match header_to_string(r) {
Ok(h) => Some(h),
Err(_) => None,
},
None => None,
}
}
pub fn get_header(&self, name: &str) -> Option<String> {
match self.headers.get(name) {
Some(r) => match header_to_string(r) {
Ok(h) => Some(h),
Err(_) => None,
},
None => None,
}
}
pub fn json<T>(&self) -> Result<T, Error>
where
T: serde::de::DeserializeOwned,
{
serde_json::from_str(&self.body).map_err(Error::from)
}
pub fn json<T>(&self) -> Result<T, Error>
where
T: serde::de::DeserializeOwned,
{
serde_json::from_str(&self.body).map_err(Error::from)
}
fn from_response(response: Response) -> Result<Self, Error> {
let (_status, headers, body) = response.split();
let body = body.text()?;
log::trace!("HTTP response headers: {:?}", headers);
log::trace!("HTTP response body: {}", body);
Ok(ValidHttpResponse { headers, body })
}
fn from_response(response: Response) -> Result<Self, Error> {
let (_status, headers, body) = response.split();
let body = body.text()?;
log::trace!("HTTP response headers: {:?}", headers);
log::trace!("HTTP response body: {}", body);
Ok(ValidHttpResponse { headers, body })
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum HttpError { pub enum HttpError {
ApiError(HttpApiError),
GenericError(Error),
ApiError(HttpApiError),
GenericError(Error),
} }
impl HttpError { impl HttpError {
pub fn in_err(error: HttpError) -> Error {
match error {
HttpError::ApiError(e) => e.to_string().into(),
HttpError::GenericError(e) => e,
}
}
pub fn in_err(error: HttpError) -> Error {
match error {
HttpError::ApiError(e) => e.to_string().into(),
HttpError::GenericError(e) => e,
}
}
pub fn is_acme_err(&self, acme_error: AcmeError) -> bool {
match self {
HttpError::ApiError(aerr) => aerr.get_acme_type() == acme_error,
HttpError::GenericError(_) => false,
}
}
pub fn is_acme_err(&self, acme_error: AcmeError) -> bool {
match self {
HttpError::ApiError(aerr) => aerr.get_acme_type() == acme_error,
HttpError::GenericError(_) => false,
}
}
} }
impl From<Error> for HttpError { impl From<Error> for HttpError {
fn from(error: Error) -> Self {
HttpError::GenericError(error)
}
fn from(error: Error) -> Self {
HttpError::GenericError(error)
}
} }
impl From<HttpApiError> for HttpError { impl From<HttpApiError> for HttpError {
fn from(error: HttpApiError) -> Self {
HttpError::ApiError(error)
}
fn from(error: HttpApiError) -> Self {
HttpError::ApiError(error)
}
} }
impl From<&str> for HttpError { impl From<&str> for HttpError {
fn from(error: &str) -> Self {
HttpError::GenericError(error.into())
}
fn from(error: &str) -> Self {
HttpError::GenericError(error.into())
}
} }
impl From<String> for HttpError { impl From<String> for HttpError {
fn from(error: String) -> Self {
HttpError::GenericError(error.into())
}
fn from(error: String) -> Self {
HttpError::GenericError(error.into())
}
} }
impl From<attohttpc::Error> for HttpError { impl From<attohttpc::Error> for HttpError {
fn from(error: attohttpc::Error) -> Self {
HttpError::GenericError(error.into())
}
fn from(error: attohttpc::Error) -> Self {
HttpError::GenericError(error.into())
}
} }
fn is_nonce(data: &str) -> bool { fn is_nonce(data: &str) -> bool {
!data.is_empty()
&& data
.bytes()
.all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
!data.is_empty()
&& data
.bytes()
.all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
} }
fn new_nonce(endpoint: &mut Endpoint) -> Result<(), HttpError> { fn new_nonce(endpoint: &mut Endpoint) -> Result<(), HttpError> {
rate_limit(endpoint);
let url = endpoint.dir.new_nonce.clone();
let _ = get(endpoint, &url)?;
Ok(())
rate_limit(endpoint);
let url = endpoint.dir.new_nonce.clone();
let _ = get(endpoint, &url)?;
Ok(())
} }
fn update_nonce(endpoint: &mut Endpoint, response: &Response) -> Result<(), Error> { fn update_nonce(endpoint: &mut Endpoint, response: &Response) -> Result<(), Error> {
if let Some(nonce) = response.headers().get(HEADER_NONCE) {
let nonce = header_to_string(nonce)?;
if !is_nonce(&nonce) {
let msg = format!("{}: invalid nonce.", &nonce);
return Err(msg.into());
}
endpoint.nonce = Some(nonce);
}
Ok(())
if let Some(nonce) = response.headers().get(HEADER_NONCE) {
let nonce = header_to_string(nonce)?;
if !is_nonce(&nonce) {
let msg = format!("{}: invalid nonce.", &nonce);
return Err(msg.into());
}
endpoint.nonce = Some(nonce);
}
Ok(())
} }
fn check_status(response: &Response) -> Result<(), Error> { fn check_status(response: &Response) -> Result<(), Error> {
if !response.is_success() {
let status = response.status();
let msg = format!("HTTP error: {}: {}", status.as_u16(), status.as_str());
return Err(msg.into());
}
Ok(())
if !response.is_success() {
let status = response.status();
let msg = format!("HTTP error: {}: {}", status.as_u16(), status.as_str());
return Err(msg.into());
}
Ok(())
} }
fn rate_limit(endpoint: &mut Endpoint) { fn rate_limit(endpoint: &mut Endpoint) {
endpoint.rl.block_until_allowed();
endpoint.rl.block_until_allowed();
} }
fn header_to_string(header_value: &header::HeaderValue) -> Result<String, Error> { fn header_to_string(header_value: &header::HeaderValue) -> Result<String, Error> {
let s = header_value
.to_str()
.map_err(|_| Error::from("invalid header format"))?;
Ok(s.to_string())
let s = header_value
.to_str()
.map_err(|_| Error::from("invalid header format"))?;
Ok(s.to_string())
} }
fn get_session(root_certs: &[String]) -> Result<Session, Error> { fn get_session(root_certs: &[String]) -> Result<Session, Error> {
let useragent = format!(
"{}/{} ({}) {}",
crate::APP_NAME,
crate::APP_VERSION,
env!("ACMED_TARGET"),
env!("ACMED_HTTP_LIB_AGENT")
);
// TODO: allow to change the language
let mut session = Session::new();
session.default_charset(Some(charsets::UTF_8));
session.try_header(header::ACCEPT_LANGUAGE, "en-US,en;q=0.5")?;
session.try_header(header::USER_AGENT, &useragent)?;
for crt_file in root_certs.iter() {
#[cfg(feature = "crypto_openssl")]
{
let mut buff = Vec::new();
File::open(crt_file)
.map_err(|e| Error::from(e).prefix(crt_file))?
.read_to_end(&mut buff)?;
let crt = X509Certificate::from_pem_native(&buff)?;
session.add_root_certificate(crt);
}
}
Ok(session)
let useragent = format!(
"{}/{} ({}) {}",
crate::APP_NAME,
crate::APP_VERSION,
env!("ACMED_TARGET"),
env!("ACMED_HTTP_LIB_AGENT")
);
// TODO: allow to change the language
let mut session = Session::new();
session.default_charset(Some(charsets::UTF_8));
session.try_header(header::ACCEPT_LANGUAGE, "en-US,en;q=0.5")?;
session.try_header(header::USER_AGENT, &useragent)?;
for crt_file in root_certs.iter() {
#[cfg(feature = "crypto_openssl")]
{
let mut buff = Vec::new();
File::open(crt_file)
.map_err(|e| Error::from(e).prefix(crt_file))?
.read_to_end(&mut buff)?;
let crt = X509Certificate::from_pem_native(&buff)?;
session.add_root_certificate(crt);
}
}
Ok(session)
} }
pub fn get(endpoint: &mut Endpoint, url: &str) -> Result<ValidHttpResponse, HttpError> { pub fn get(endpoint: &mut Endpoint, url: &str) -> Result<ValidHttpResponse, HttpError> {
let mut session = get_session(&endpoint.root_certificates)?;
session.try_header(header::ACCEPT, CONTENT_TYPE_JSON)?;
rate_limit(endpoint);
let response = session.get(url).send()?;
update_nonce(endpoint, &response)?;
check_status(&response)?;
ValidHttpResponse::from_response(response).map_err(HttpError::from)
let mut session = get_session(&endpoint.root_certificates)?;
session.try_header(header::ACCEPT, CONTENT_TYPE_JSON)?;
rate_limit(endpoint);
let response = session.get(url).send()?;
update_nonce(endpoint, &response)?;
check_status(&response)?;
ValidHttpResponse::from_response(response).map_err(HttpError::from)
} }
pub fn post<F>( pub fn post<F>(
endpoint: &mut Endpoint,
url: &str,
data_builder: &F,
content_type: &str,
accept: &str,
endpoint: &mut Endpoint,
url: &str,
data_builder: &F,
content_type: &str,
accept: &str,
) -> Result<ValidHttpResponse, HttpError> ) -> Result<ValidHttpResponse, HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
F: Fn(&str, &str) -> Result<String, Error>,
{ {
let mut session = get_session(&endpoint.root_certificates)?;
session.try_header(header::ACCEPT, accept)?;
session.try_header(header::CONTENT_TYPE, content_type)?;
if endpoint.nonce.is_none() {
let _ = new_nonce(endpoint);
}
for _ in 0..crate::DEFAULT_HTTP_FAIL_NB_RETRY {
let nonce = &endpoint.nonce.clone().unwrap_or_default();
let body = data_builder(nonce, url)?;
rate_limit(endpoint);
log::trace!("POST request body: {}", body);
let response = session.post(url).text(&body).send()?;
update_nonce(endpoint, &response)?;
match check_status(&response) {
Ok(_) => {
return ValidHttpResponse::from_response(response).map_err(HttpError::from);
}
Err(_) => {
let resp = ValidHttpResponse::from_response(response)?;
let api_err = resp.json::<HttpApiError>()?;
let acme_err = api_err.get_acme_type();
if !acme_err.is_recoverable() {
return Err(api_err.into());
}
}
}
thread::sleep(time::Duration::from_secs(crate::DEFAULT_HTTP_FAIL_WAIT_SEC));
}
Err("too much errors, will not retry".into())
let mut session = get_session(&endpoint.root_certificates)?;
session.try_header(header::ACCEPT, accept)?;
session.try_header(header::CONTENT_TYPE, content_type)?;
if endpoint.nonce.is_none() {
let _ = new_nonce(endpoint);
}
for _ in 0..crate::DEFAULT_HTTP_FAIL_NB_RETRY {
let nonce = &endpoint.nonce.clone().unwrap_or_default();
let body = data_builder(nonce, url)?;
rate_limit(endpoint);
log::trace!("POST request body: {}", body);
let response = session.post(url).text(&body).send()?;
update_nonce(endpoint, &response)?;
match check_status(&response) {
Ok(_) => {
return ValidHttpResponse::from_response(response).map_err(HttpError::from);
}
Err(_) => {
let resp = ValidHttpResponse::from_response(response)?;
let api_err = resp.json::<HttpApiError>()?;
let acme_err = api_err.get_acme_type();
if !acme_err.is_recoverable() {
return Err(api_err.into());
}
}
}
thread::sleep(time::Duration::from_secs(crate::DEFAULT_HTTP_FAIL_WAIT_SEC));
}
Err("too much errors, will not retry".into())
} }
pub fn post_jose<F>( pub fn post_jose<F>(
endpoint: &mut Endpoint,
url: &str,
data_builder: &F,
endpoint: &mut Endpoint,
url: &str,
data_builder: &F,
) -> Result<ValidHttpResponse, HttpError> ) -> Result<ValidHttpResponse, HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>,
F: Fn(&str, &str) -> Result<String, Error>,
{ {
post(
endpoint,
url,
data_builder,
CONTENT_TYPE_JOSE,
CONTENT_TYPE_JSON,
)
post(
endpoint,
url,
data_builder,
CONTENT_TYPE_JOSE,
CONTENT_TYPE_JSON,
)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::is_nonce;
use super::is_nonce;
#[test]
fn test_nonce_valid() {
let lst = [
"XFHw3qcgFNZAdw",
"XFHw3qcg-NZAdw",
"XFHw3qcg_NZAdw",
"XFHw3qcg-_ZAdw",
"a",
"1",
"-",
"_",
];
for n in lst.iter() {
assert!(is_nonce(n));
}
}
#[test]
fn test_nonce_valid() {
let lst = [
"XFHw3qcgFNZAdw",
"XFHw3qcg-NZAdw",
"XFHw3qcg_NZAdw",
"XFHw3qcg-_ZAdw",
"a",
"1",
"-",
"_",
];
for n in lst.iter() {
assert!(is_nonce(n));
}
}
#[test]
fn test_nonce_invalid() {
let lst = [
"",
"rdo9x8gS4K/mZg==",
"rdo9x8gS4K/mZg",
"rdo9x8gS4K+mZg",
"৬",
"京",
];
for n in lst.iter() {
assert!(!is_nonce(n));
}
}
#[test]
fn test_nonce_invalid() {
let lst = [
"",
"rdo9x8gS4K/mZg==",
"rdo9x8gS4K/mZg",
"rdo9x8gS4K+mZg",
"৬",
"京",
];
for n in lst.iter() {
assert!(!is_nonce(n));
}
}
} }

220
acmed/src/identifier.rs

@ -9,141 +9,141 @@ use std::str::FromStr;
// RFC 3596, section 2.5 // RFC 3596, section 2.5
fn u8_to_nibbles_string(value: &u8) -> String { 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)
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)] #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub enum IdentifierType { pub enum IdentifierType {
#[serde(rename = "dns")]
Dns,
#[serde(rename = "ip")]
Ip,
#[serde(rename = "dns")]
Dns,
#[serde(rename = "ip")]
Ip,
} }
impl IdentifierType { 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],
}
}
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 { 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)
}
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)] #[derive(Clone, Debug)]
pub struct Identifier { pub struct Identifier {
pub id_type: IdentifierType,
pub value: String,
pub challenge: Challenge,
pub env: HashMap<String, String>,
pub id_type: IdentifierType,
pub value: String,
pub challenge: Challenge,
pub env: HashMap<String, String>,
} }
impl Identifier { 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 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)
}
},
}
}
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 { impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {} ({})", self.id_type, self.value, self.challenge)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {} ({})", self.id_type, self.value, self.challenge)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use std::collections::HashMap;
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_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"
);
}
#[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"
);
}
} }

310
acmed/src/jws.rs

@ -6,186 +6,186 @@ use serde_json::value::Value;
#[derive(Serialize)] #[derive(Serialize)]
struct JwsData { struct JwsData {
protected: String,
payload: String,
signature: String,
protected: String,
payload: String,
signature: String,
} }
#[derive(Serialize)] #[derive(Serialize)]
struct JwsProtectedHeader { struct JwsProtectedHeader {
alg: String,
#[serde(skip_serializing_if = "Option::is_none")]
jwk: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
kid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>,
url: String,
alg: String,
#[serde(skip_serializing_if = "Option::is_none")]
jwk: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
kid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>,
url: String,
} }
fn get_jws_data( fn get_jws_data(
key_pair: &KeyPair,
sign_alg: &JwsSignatureAlgorithm,
protected: &str,
payload: &[u8],
key_pair: &KeyPair,
sign_alg: &JwsSignatureAlgorithm,
protected: &str,
payload: &[u8],
) -> Result<String, Error> { ) -> Result<String, Error> {
let protected = b64_encode(protected);
let payload = b64_encode(payload);
let signing_input = format!("{}.{}", protected, payload);
let signature = key_pair.sign(sign_alg, signing_input.as_bytes())?;
let signature = b64_encode(&signature);
let data = JwsData {
protected,
payload,
signature,
};
let str_data = serde_json::to_string(&data)?;
Ok(str_data)
let protected = b64_encode(protected);
let payload = b64_encode(payload);
let signing_input = format!("{}.{}", protected, payload);
let signature = key_pair.sign(sign_alg, signing_input.as_bytes())?;
let signature = b64_encode(&signature);
let data = JwsData {
protected,
payload,
signature,
};
let str_data = serde_json::to_string(&data)?;
Ok(str_data)
} }
pub fn encode_jwk( pub fn encode_jwk(
key_pair: &KeyPair,
sign_alg: &JwsSignatureAlgorithm,
payload: &[u8],
url: &str,
nonce: Option<String>,
key_pair: &KeyPair,
sign_alg: &JwsSignatureAlgorithm,
payload: &[u8],
url: &str,
nonce: Option<String>,
) -> Result<String, Error> { ) -> Result<String, Error> {
let protected = JwsProtectedHeader {
alg: sign_alg.to_string(),
jwk: Some(key_pair.jwk_public_key()?),
kid: None,
nonce,
url: url.into(),
};
let protected = serde_json::to_string(&protected)?;
get_jws_data(key_pair, sign_alg, &protected, payload)
let protected = JwsProtectedHeader {
alg: sign_alg.to_string(),
jwk: Some(key_pair.jwk_public_key()?),
kid: None,
nonce,
url: url.into(),
};
let protected = serde_json::to_string(&protected)?;
get_jws_data(key_pair, sign_alg, &protected, payload)
} }
pub fn encode_kid( pub fn encode_kid(
key_pair: &KeyPair,
sign_alg: &JwsSignatureAlgorithm,
key_id: &str,
payload: &[u8],
url: &str,
nonce: &str,
key_pair: &KeyPair,
sign_alg: &JwsSignatureAlgorithm,
key_id: &str,
payload: &[u8],
url: &str,
nonce: &str,
) -> Result<String, Error> { ) -> Result<String, Error> {
let protected = JwsProtectedHeader {
alg: sign_alg.to_string(),
jwk: None,
kid: Some(key_id.to_string()),
nonce: Some(nonce.into()),
url: url.into(),
};
let protected = serde_json::to_string(&protected)?;
get_jws_data(key_pair, sign_alg, &protected, payload)
let protected = JwsProtectedHeader {
alg: sign_alg.to_string(),
jwk: None,
kid: Some(key_id.to_string()),
nonce: Some(nonce.into()),
url: url.into(),
};
let protected = serde_json::to_string(&protected)?;
get_jws_data(key_pair, sign_alg, &protected, payload)
} }
pub fn encode_kid_mac( pub fn encode_kid_mac(
key: &[u8],
sign_alg: &JwsSignatureAlgorithm,
key_id: &str,
payload: &[u8],
url: &str,
key: &[u8],
sign_alg: &JwsSignatureAlgorithm,
key_id: &str,
payload: &[u8],
url: &str,
) -> Result<String, Error> { ) -> Result<String, Error> {
let protected = JwsProtectedHeader {
alg: sign_alg.to_string(),
jwk: None,
kid: Some(key_id.to_string()),
nonce: None,
url: url.into(),
};
let protected = serde_json::to_string(&protected)?;
let protected = b64_encode(&protected);
let payload = b64_encode(payload);
let signing_input = format!("{}.{}", protected, payload);
let hash_func = match sign_alg {
JwsSignatureAlgorithm::Hs256 => HashFunction::Sha256,
JwsSignatureAlgorithm::Hs384 => HashFunction::Sha384,
JwsSignatureAlgorithm::Hs512 => HashFunction::Sha512,
_ => {
return Err(format!("{}: not a HMAC-based signature algorithm", sign_alg).into());
}
};
let signature = hash_func.hmac(key, signing_input.as_bytes())?;
let signature = b64_encode(&signature);
let data = JwsData {
protected,
payload,
signature,
};
let str_data = serde_json::to_string(&data)?;
Ok(str_data)
let protected = JwsProtectedHeader {
alg: sign_alg.to_string(),
jwk: None,
kid: Some(key_id.to_string()),
nonce: None,
url: url.into(),
};
let protected = serde_json::to_string(&protected)?;
let protected = b64_encode(&protected);
let payload = b64_encode(payload);
let signing_input = format!("{}.{}", protected, payload);
let hash_func = match sign_alg {
JwsSignatureAlgorithm::Hs256 => HashFunction::Sha256,
JwsSignatureAlgorithm::Hs384 => HashFunction::Sha384,
JwsSignatureAlgorithm::Hs512 => HashFunction::Sha512,
_ => {
return Err(format!("{}: not a HMAC-based signature algorithm", sign_alg).into());
}
};
let signature = hash_func.hmac(key, signing_input.as_bytes())?;
let signature = b64_encode(&signature);
let data = JwsData {
protected,
payload,
signature,
};
let str_data = serde_json::to_string(&data)?;
Ok(str_data)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{encode_jwk, encode_kid};
use acme_common::crypto::{gen_keypair, KeyType};
use super::{encode_jwk, encode_kid};
use acme_common::crypto::{gen_keypair, KeyType};
#[test]
fn test_default_jwk() {
let key_type = KeyType::EcdsaP256;
let key_pair = gen_keypair(key_type).unwrap();
let payload = "Dummy payload 1";
let payload_b64 = "RHVtbXkgcGF5bG9hZCAx";
let s = encode_jwk(
&key_pair,
&key_type.get_default_signature_alg(),
payload.as_bytes(),
"",
Some(String::new()),
);
assert!(s.is_ok());
let s = s.unwrap();
assert!(s.contains("\"protected\""));
assert!(s.contains("\"payload\""));
assert!(s.contains("\"signature\""));
assert!(s.contains(payload_b64));
}
#[test]
fn test_default_jwk() {
let key_type = KeyType::EcdsaP256;
let key_pair = gen_keypair(key_type).unwrap();
let payload = "Dummy payload 1";
let payload_b64 = "RHVtbXkgcGF5bG9hZCAx";
let s = encode_jwk(
&key_pair,
&key_type.get_default_signature_alg(),
payload.as_bytes(),
"",
Some(String::new()),
);
assert!(s.is_ok());
let s = s.unwrap();
assert!(s.contains("\"protected\""));
assert!(s.contains("\"payload\""));
assert!(s.contains("\"signature\""));
assert!(s.contains(payload_b64));
}
#[test]
fn test_default_nopad_jwk() {
let key_type = KeyType::EcdsaP256;
let key_pair = gen_keypair(key_type).unwrap();
let payload = "Dummy payload";
let payload_b64 = "RHVtbXkgcGF5bG9hZA";
let payload_b64_pad = "RHVtbXkgcGF5bG9hZA==";
let s = encode_jwk(
&key_pair,
&key_type.get_default_signature_alg(),
payload.as_bytes(),
"",
Some(String::new()),
);
assert!(s.is_ok());
let s = s.unwrap();
assert!(s.contains("\"protected\""));
assert!(s.contains("\"payload\""));
assert!(s.contains("\"signature\""));
assert!(s.contains(payload_b64));
assert!(!s.contains(payload_b64_pad));
}
#[test]
fn test_default_nopad_jwk() {
let key_type = KeyType::EcdsaP256;
let key_pair = gen_keypair(key_type).unwrap();
let payload = "Dummy payload";
let payload_b64 = "RHVtbXkgcGF5bG9hZA";
let payload_b64_pad = "RHVtbXkgcGF5bG9hZA==";
let s = encode_jwk(
&key_pair,
&key_type.get_default_signature_alg(),
payload.as_bytes(),
"",
Some(String::new()),
);
assert!(s.is_ok());
let s = s.unwrap();
assert!(s.contains("\"protected\""));
assert!(s.contains("\"payload\""));
assert!(s.contains("\"signature\""));
assert!(s.contains(payload_b64));
assert!(!s.contains(payload_b64_pad));
}
#[test]
fn test_default_kid() {
let key_type = KeyType::EcdsaP256;
let key_pair = gen_keypair(key_type).unwrap();
let payload = "Dummy payload 1";
let payload_b64 = "RHVtbXkgcGF5bG9hZCAx";
let key_id = "0x2a";
let s = encode_kid(
&key_pair,
&key_type.get_default_signature_alg(),
key_id,
payload.as_bytes(),
"",
"",
);
assert!(s.is_ok());
let s = s.unwrap();
assert!(s.contains("\"protected\""));
assert!(s.contains("\"payload\""));
assert!(s.contains("\"signature\""));
assert!(s.contains(payload_b64));
}
#[test]
fn test_default_kid() {
let key_type = KeyType::EcdsaP256;
let key_pair = gen_keypair(key_type).unwrap();
let payload = "Dummy payload 1";
let payload_b64 = "RHVtbXkgcGF5bG9hZCAx";
let key_id = "0x2a";
let s = encode_kid(
&key_pair,
&key_type.get_default_signature_alg(),
key_id,
payload.as_bytes(),
"",
"",
);
assert!(s.is_ok());
let s = s.unwrap();
assert!(s.contains("\"protected\""));
assert!(s.contains("\"payload\""));
assert!(s.contains("\"signature\""));
assert!(s.contains(payload_b64));
}
} }

8
acmed/src/logs.rs

@ -1,6 +1,6 @@
pub trait HasLogger { pub trait HasLogger {
fn warn(&self, msg: &str);
fn info(&self, msg: &str);
fn debug(&self, msg: &str);
fn trace(&self, msg: &str);
fn warn(&self, msg: &str);
fn info(&self, msg: &str);
fn debug(&self, msg: &str);
fn trace(&self, msg: &str);
} }

218
acmed/src/main.rs

@ -1,6 +1,6 @@
use crate::main_event_loop::MainEventLoop; use crate::main_event_loop::MainEventLoop;
use acme_common::crypto::{ use acme_common::crypto::{
get_lib_name, get_lib_version, HashFunction, JwsSignatureAlgorithm, KeyType,
get_lib_name, get_lib_version, HashFunction, JwsSignatureAlgorithm, KeyType,
}; };
use acme_common::logs::{set_log_system, DEFAULT_LOG_LEVEL}; use acme_common::logs::{set_log_system, DEFAULT_LOG_LEVEL};
use acme_common::{clean_pid_file, init_server}; use acme_common::{clean_pid_file, init_server};
@ -49,117 +49,117 @@ pub const MAX_RATE_LIMIT_SLEEP_MILISEC: u64 = 3_600_000;
pub const MIN_RATE_LIMIT_SLEEP_MILISEC: u64 = 100; pub const MIN_RATE_LIMIT_SLEEP_MILISEC: u64 = 100;
fn main() { fn main() {
let full_version = format!(
"{} built for {}\n\nCryptographic library:\n - {} {}\nHTTP client library:\n - {} {}",
APP_VERSION,
env!("ACMED_TARGET"),
get_lib_name(),
get_lib_version(),
env!("ACMED_HTTP_LIB_NAME"),
env!("ACMED_HTTP_LIB_VERSION")
);
let default_log_level = DEFAULT_LOG_LEVEL.to_string().to_lowercase();
let matches = Command::new(APP_NAME)
.version(APP_VERSION)
.long_version(full_version)
.arg(
Arg::new("config")
.short('c')
.long("config")
.help("Path to the main configuration file")
.num_args(1)
.value_name("FILE")
.default_value(DEFAULT_CONFIG_FILE),
)
.arg(
Arg::new("log-level")
.long("log-level")
.help("Specify the log level")
.num_args(1)
.value_name("LEVEL")
.value_parser(["error", "warn", "info", "debug", "trace"])
.default_value(default_log_level),
)
.arg(
Arg::new("to-syslog")
.long("log-syslog")
.help("Sends log messages via syslog")
.conflicts_with("to-stderr")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("to-stderr")
.long("log-stderr")
.help("Prints log messages to the standard error output")
.conflicts_with("to-syslog")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("foreground")
.short('f')
.long("foreground")
.help("Runs in the foreground")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("pid-file")
.long("pid-file")
.help("Path to the PID file")
.num_args(1)
.value_name("FILE")
.default_value(DEFAULT_PID_FILE)
.default_value_if("no-pid-file", clap::builder::ArgPredicate::IsPresent, None)
.conflicts_with("no-pid-file"),
)
.arg(
Arg::new("no-pid-file")
.long("no-pid-file")
.help("Do not create any PID file")
.conflicts_with("pid-file")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("root-cert")
.long("root-cert")
.help("Add a root certificate to the trust store (can be set multiple times)")
.num_args(1)
.action(ArgAction::Append)
.value_name("FILE"),
)
.get_matches();
let full_version = format!(
"{} built for {}\n\nCryptographic library:\n - {} {}\nHTTP client library:\n - {} {}",
APP_VERSION,
env!("ACMED_TARGET"),
get_lib_name(),
get_lib_version(),
env!("ACMED_HTTP_LIB_NAME"),
env!("ACMED_HTTP_LIB_VERSION")
);
let default_log_level = DEFAULT_LOG_LEVEL.to_string().to_lowercase();
let matches = Command::new(APP_NAME)
.version(APP_VERSION)
.long_version(full_version)
.arg(
Arg::new("config")
.short('c')
.long("config")
.help("Path to the main configuration file")
.num_args(1)
.value_name("FILE")
.default_value(DEFAULT_CONFIG_FILE),
)
.arg(
Arg::new("log-level")
.long("log-level")
.help("Specify the log level")
.num_args(1)
.value_name("LEVEL")
.value_parser(["error", "warn", "info", "debug", "trace"])
.default_value(default_log_level),
)
.arg(
Arg::new("to-syslog")
.long("log-syslog")
.help("Sends log messages via syslog")
.conflicts_with("to-stderr")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("to-stderr")
.long("log-stderr")
.help("Prints log messages to the standard error output")
.conflicts_with("to-syslog")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("foreground")
.short('f')
.long("foreground")
.help("Runs in the foreground")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("pid-file")
.long("pid-file")
.help("Path to the PID file")
.num_args(1)
.value_name("FILE")
.default_value(DEFAULT_PID_FILE)
.default_value_if("no-pid-file", clap::builder::ArgPredicate::IsPresent, None)
.conflicts_with("no-pid-file"),
)
.arg(
Arg::new("no-pid-file")
.long("no-pid-file")
.help("Do not create any PID file")
.conflicts_with("pid-file")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("root-cert")
.long("root-cert")
.help("Add a root certificate to the trust store (can be set multiple times)")
.num_args(1)
.action(ArgAction::Append)
.value_name("FILE"),
)
.get_matches();
match set_log_system(
matches.get_one::<String>("log-level").map(|e| e.as_str()),
matches.get_flag("to-syslog"),
matches.get_flag("to-stderr"),
) {
Ok(_) => {}
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(2);
}
};
match set_log_system(
matches.get_one::<String>("log-level").map(|e| e.as_str()),
matches.get_flag("to-syslog"),
matches.get_flag("to-stderr"),
) {
Ok(_) => {}
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(2);
}
};
let root_certs = match matches.get_many::<String>("root-cert") {
Some(v) => v.map(|e| e.as_str()).collect(),
None => vec![],
};
let root_certs = match matches.get_many::<String>("root-cert") {
Some(v) => v.map(|e| e.as_str()).collect(),
None => vec![],
};
let config_file = matches
.get_one::<String>("config")
.map(|e| e.as_str())
.unwrap_or(DEFAULT_CONFIG_FILE);
let pid_file = matches.get_one::<String>("pid-file").map(|e| e.as_str());
let config_file = matches
.get_one::<String>("config")
.map(|e| e.as_str())
.unwrap_or(DEFAULT_CONFIG_FILE);
let pid_file = matches.get_one::<String>("pid-file").map(|e| e.as_str());
init_server(matches.get_flag("foreground"), pid_file);
init_server(matches.get_flag("foreground"), pid_file);
let mut srv = match MainEventLoop::new(config_file, &root_certs) {
Ok(s) => s,
Err(e) => {
error!("{}", e);
let _ = clean_pid_file(pid_file);
std::process::exit(1);
}
};
srv.run();
let mut srv = match MainEventLoop::new(config_file, &root_certs) {
Ok(s) => s,
Err(e) => {
error!("{}", e);
let _ = clean_pid_file(pid_file);
std::process::exit(1);
}
};
srv.run();
} }

358
acmed/src/main_event_loop.rs

@ -16,194 +16,194 @@ type AccountSync = Arc<RwLock<Account>>;
type EndpointSync = Arc<RwLock<Endpoint>>; type EndpointSync = Arc<RwLock<Endpoint>>;
fn renew_certificate(crt: &Certificate, endpoint: &mut Endpoint, account: &mut Account) { fn renew_certificate(crt: &Certificate, endpoint: &mut Endpoint, account: &mut Account) {
let (status, is_success) = match request_certificate(crt, endpoint, account) {
Ok(_) => ("success".to_string(), true),
Err(e) => {
let e = e.prefix("unable to renew the certificate");
crt.warn(&e.message);
(e.message, false)
}
};
match crt.call_post_operation_hooks(&status, is_success) {
Ok(_) => {}
Err(e) => {
let e = e.prefix("post-operation hook error");
crt.warn(&e.message);
}
};
let (status, is_success) = match request_certificate(crt, endpoint, account) {
Ok(_) => ("success".to_string(), true),
Err(e) => {
let e = e.prefix("unable to renew the certificate");
crt.warn(&e.message);
(e.message, false)
}
};
match crt.call_post_operation_hooks(&status, is_success) {
Ok(_) => {}
Err(e) => {
let e = e.prefix("post-operation hook error");
crt.warn(&e.message);
}
};
} }
pub struct MainEventLoop { pub struct MainEventLoop {
certs: Vec<Certificate>,
accounts: HashMap<String, AccountSync>,
endpoints: HashMap<String, EndpointSync>,
certs: Vec<Certificate>,
accounts: HashMap<String, AccountSync>,
endpoints: HashMap<String, EndpointSync>,
} }
impl MainEventLoop { impl MainEventLoop {
pub fn new(config_file: &str, root_certs: &[&str]) -> Result<Self, Error> {
let cnf = config::from_file(config_file)?;
let file_hooks = vec![
HookType::FilePreCreate,
HookType::FilePostCreate,
HookType::FilePreEdit,
HookType::FilePostEdit,
]
.into_iter()
.collect();
let cert_hooks = vec![
HookType::ChallengeHttp01,
HookType::ChallengeHttp01Clean,
HookType::ChallengeDns01,
HookType::ChallengeDns01Clean,
HookType::ChallengeTlsAlpn01,
HookType::ChallengeTlsAlpn01Clean,
HookType::PostOperation,
]
.into_iter()
.collect();
pub fn new(config_file: &str, root_certs: &[&str]) -> Result<Self, Error> {
let cnf = config::from_file(config_file)?;
let file_hooks = vec![
HookType::FilePreCreate,
HookType::FilePostCreate,
HookType::FilePreEdit,
HookType::FilePostEdit,
]
.into_iter()
.collect();
let cert_hooks = vec![
HookType::ChallengeHttp01,
HookType::ChallengeHttp01Clean,
HookType::ChallengeDns01,
HookType::ChallengeDns01Clean,
HookType::ChallengeTlsAlpn01,
HookType::ChallengeTlsAlpn01Clean,
HookType::PostOperation,
]
.into_iter()
.collect();
let mut accounts = HashMap::new();
for acc in cnf.account.iter() {
let fm = FileManager {
account_directory: cnf.get_account_dir(),
account_name: acc.name.clone(),
crt_name: String::new(),
crt_name_format: String::new(),
crt_directory: String::new(),
crt_key_type: String::new(),
cert_file_mode: cnf.get_cert_file_mode(),
cert_file_owner: cnf.get_cert_file_user(),
cert_file_group: cnf.get_cert_file_group(),
pk_file_mode: cnf.get_pk_file_mode(),
pk_file_owner: cnf.get_pk_file_user(),
pk_file_group: cnf.get_pk_file_group(),
hooks: acc
.get_hooks(&cnf)?
.iter()
.filter(|h| !h.hook_type.is_disjoint(&file_hooks))
.map(|e| e.to_owned())
.collect(),
env: acc.env.clone(),
};
let account = acc.to_generic(&fm)?;
accounts.insert(acc.name.clone(), account);
}
let mut accounts = HashMap::new();
for acc in cnf.account.iter() {
let fm = FileManager {
account_directory: cnf.get_account_dir(),
account_name: acc.name.clone(),
crt_name: String::new(),
crt_name_format: String::new(),
crt_directory: String::new(),
crt_key_type: String::new(),
cert_file_mode: cnf.get_cert_file_mode(),
cert_file_owner: cnf.get_cert_file_user(),
cert_file_group: cnf.get_cert_file_group(),
pk_file_mode: cnf.get_pk_file_mode(),
pk_file_owner: cnf.get_pk_file_user(),
pk_file_group: cnf.get_pk_file_group(),
hooks: acc
.get_hooks(&cnf)?
.iter()
.filter(|h| !h.hook_type.is_disjoint(&file_hooks))
.map(|e| e.to_owned())
.collect(),
env: acc.env.clone(),
};
let account = acc.to_generic(&fm)?;
accounts.insert(acc.name.clone(), account);
}
let mut certs: Vec<Certificate> = Vec::new();
let mut endpoints = HashMap::new();
for crt in cnf.certificate.iter() {
let endpoint = crt.get_endpoint(&cnf, root_certs)?;
let endpoint_name = endpoint.name.clone();
let crt_name = crt.get_crt_name()?;
let key_type = crt.get_key_type()?;
let hooks = crt.get_hooks(&cnf)?;
let fm = FileManager {
account_directory: cnf.get_account_dir(),
account_name: crt.account.clone(),
crt_name: crt_name.clone(),
crt_name_format: crt.get_crt_name_format(&cnf)?,
crt_directory: crt.get_crt_dir(&cnf),
crt_key_type: key_type.to_string(),
cert_file_mode: cnf.get_cert_file_mode(),
cert_file_owner: cnf.get_cert_file_user(),
cert_file_group: cnf.get_cert_file_group(),
pk_file_mode: cnf.get_pk_file_mode(),
pk_file_owner: cnf.get_pk_file_user(),
pk_file_group: cnf.get_pk_file_group(),
hooks: hooks
.iter()
.filter(|h| !h.hook_type.is_disjoint(&file_hooks))
.map(|e| e.to_owned())
.collect(),
env: crt.env.clone(),
};
let cert = Certificate {
account_name: crt.account.clone(),
identifiers: crt.get_identifiers()?,
subject_attributes: crt.subject_attributes.to_generic(),
key_type,
csr_digest: crt.get_csr_digest()?,
kp_reuse: crt.get_kp_reuse(),
endpoint_name: endpoint_name.clone(),
hooks: hooks
.iter()
.filter(|h| !h.hook_type.is_disjoint(&cert_hooks))
.map(|e| e.to_owned())
.collect(),
crt_name,
env: crt.env.to_owned(),
renew_delay: crt.get_renew_delay(&cnf)?,
file_manager: fm,
};
let crt_id = cert.get_id();
if certs.iter().any(|c| c.get_id() == crt_id) {
let msg = format!("{}: duplicate certificate id", crt_id);
return Err(msg.into());
}
match accounts.get_mut(&crt.account) {
Some(acc) => acc.add_endpoint_name(&endpoint_name),
None => {
let msg = format!("{}: account not found", &crt.account);
return Err(msg.into());
}
};
endpoints.entry(endpoint_name).or_insert(endpoint);
certs.push(cert);
}
let mut certs: Vec<Certificate> = Vec::new();
let mut endpoints = HashMap::new();
for crt in cnf.certificate.iter() {
let endpoint = crt.get_endpoint(&cnf, root_certs)?;
let endpoint_name = endpoint.name.clone();
let crt_name = crt.get_crt_name()?;
let key_type = crt.get_key_type()?;
let hooks = crt.get_hooks(&cnf)?;
let fm = FileManager {
account_directory: cnf.get_account_dir(),
account_name: crt.account.clone(),
crt_name: crt_name.clone(),
crt_name_format: crt.get_crt_name_format(&cnf)?,
crt_directory: crt.get_crt_dir(&cnf),
crt_key_type: key_type.to_string(),
cert_file_mode: cnf.get_cert_file_mode(),
cert_file_owner: cnf.get_cert_file_user(),
cert_file_group: cnf.get_cert_file_group(),
pk_file_mode: cnf.get_pk_file_mode(),
pk_file_owner: cnf.get_pk_file_user(),
pk_file_group: cnf.get_pk_file_group(),
hooks: hooks
.iter()
.filter(|h| !h.hook_type.is_disjoint(&file_hooks))
.map(|e| e.to_owned())
.collect(),
env: crt.env.clone(),
};
let cert = Certificate {
account_name: crt.account.clone(),
identifiers: crt.get_identifiers()?,
subject_attributes: crt.subject_attributes.to_generic(),
key_type,
csr_digest: crt.get_csr_digest()?,
kp_reuse: crt.get_kp_reuse(),
endpoint_name: endpoint_name.clone(),
hooks: hooks
.iter()
.filter(|h| !h.hook_type.is_disjoint(&cert_hooks))
.map(|e| e.to_owned())
.collect(),
crt_name,
env: crt.env.to_owned(),
renew_delay: crt.get_renew_delay(&cnf)?,
file_manager: fm,
};
let crt_id = cert.get_id();
if certs.iter().any(|c| c.get_id() == crt_id) {
let msg = format!("{}: duplicate certificate id", crt_id);
return Err(msg.into());
}
match accounts.get_mut(&crt.account) {
Some(acc) => acc.add_endpoint_name(&endpoint_name),
None => {
let msg = format!("{}: account not found", &crt.account);
return Err(msg.into());
}
};
endpoints.entry(endpoint_name).or_insert(endpoint);
certs.push(cert);
}
Ok(MainEventLoop {
certs,
accounts: accounts
.iter()
.map(|(k, v)| (k.to_owned(), Arc::new(RwLock::new(v.to_owned()))))
.collect(),
endpoints: endpoints
.iter()
.map(|(k, v)| (k.to_owned(), Arc::new(RwLock::new(v.to_owned()))))
.collect(),
})
}
Ok(MainEventLoop {
certs,
accounts: accounts
.iter()
.map(|(k, v)| (k.to_owned(), Arc::new(RwLock::new(v.to_owned()))))
.collect(),
endpoints: endpoints
.iter()
.map(|(k, v)| (k.to_owned(), Arc::new(RwLock::new(v.to_owned()))))
.collect(),
})
}
pub fn run(&mut self) {
loop {
self.renew_certificates();
thread::sleep(Duration::from_secs(crate::DEFAULT_SLEEP_TIME));
}
}
pub fn run(&mut self) {
loop {
self.renew_certificates();
thread::sleep(Duration::from_secs(crate::DEFAULT_SLEEP_TIME));
}
}
fn renew_certificates(&mut self) {
let mut handles = vec![];
for (ep_name, endpoint_lock) in self.endpoints.iter_mut() {
let mut certs_to_renew = vec![];
for crt in self.certs.iter() {
if crt.endpoint_name == *ep_name {
match crt.should_renew() {
Ok(true) => {
let crt_arc = Arc::new(crt.clone());
certs_to_renew.push(crt_arc);
}
Ok(false) => {}
Err(e) => {
crt.warn(&e.message);
}
}
}
}
let mut accounts_lock = self.accounts.clone();
let ep_lock = endpoint_lock.clone();
let handle = thread::spawn(move || {
let mut endpoint = ep_lock.write().unwrap();
for crt in certs_to_renew {
if let Some(acc_lock) = accounts_lock.get_mut(&crt.account_name) {
let mut account = acc_lock.write().unwrap();
renew_certificate(&crt, &mut endpoint, &mut account);
};
}
});
handles.push(handle);
}
for handle in handles {
let _ = handle.join();
}
}
fn renew_certificates(&mut self) {
let mut handles = vec![];
for (ep_name, endpoint_lock) in self.endpoints.iter_mut() {
let mut certs_to_renew = vec![];
for crt in self.certs.iter() {
if crt.endpoint_name == *ep_name {
match crt.should_renew() {
Ok(true) => {
let crt_arc = Arc::new(crt.clone());
certs_to_renew.push(crt_arc);
}
Ok(false) => {}
Err(e) => {
crt.warn(&e.message);
}
}
}
}
let mut accounts_lock = self.accounts.clone();
let ep_lock = endpoint_lock.clone();
let handle = thread::spawn(move || {
let mut endpoint = ep_lock.write().unwrap();
for crt in certs_to_renew {
if let Some(acc_lock) = accounts_lock.get_mut(&crt.account_name) {
let mut account = acc_lock.write().unwrap();
renew_certificate(&crt, &mut endpoint, &mut account);
};
}
});
handles.push(handle);
}
for handle in handles {
let _ = handle.join();
}
}
} }

418
acmed/src/storage.rs

@ -16,280 +16,280 @@ use std::os::unix::fs::OpenOptionsExt;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FileManager { pub struct FileManager {
pub account_name: String,
pub account_directory: String,
pub crt_name: String,
pub crt_name_format: String,
pub crt_directory: String,
pub crt_key_type: String,
pub cert_file_mode: u32,
pub cert_file_owner: Option<String>,
pub cert_file_group: Option<String>,
pub pk_file_mode: u32,
pub pk_file_owner: Option<String>,
pub pk_file_group: Option<String>,
pub hooks: Vec<Hook>,
pub env: HashMap<String, String>,
pub account_name: String,
pub account_directory: String,
pub crt_name: String,
pub crt_name_format: String,
pub crt_directory: String,
pub crt_key_type: String,
pub cert_file_mode: u32,
pub cert_file_owner: Option<String>,
pub cert_file_group: Option<String>,
pub pk_file_mode: u32,
pub pk_file_owner: Option<String>,
pub pk_file_group: Option<String>,
pub hooks: Vec<Hook>,
pub env: HashMap<String, String>,
} }
impl HasLogger for FileManager { impl HasLogger for FileManager {
fn warn(&self, msg: &str) {
log::warn!("{}: {}", self, msg);
}
fn warn(&self, msg: &str) {
log::warn!("{}: {}", self, msg);
}
fn info(&self, msg: &str) {
log::info!("{}: {}", self, msg);
}
fn info(&self, msg: &str) {
log::info!("{}: {}", self, msg);
}
fn debug(&self, msg: &str) {
log::debug!("{}: {}", self, msg);
}
fn debug(&self, msg: &str) {
log::debug!("{}: {}", self, msg);
}
fn trace(&self, msg: &str) {
log::trace!("{}: {}", self, msg);
}
fn trace(&self, msg: &str) {
log::trace!("{}: {}", self, msg);
}
} }
impl fmt::Display for FileManager { impl fmt::Display for FileManager {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = if !self.crt_name.is_empty() {
format!("certificate \"{}_{}\"", self.crt_name, self.crt_key_type)
} else {
format!("account \"{}\"", self.account_name)
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = if !self.crt_name.is_empty() {
format!("certificate \"{}_{}\"", self.crt_name, self.crt_key_type)
} else {
format!("account \"{}\"", self.account_name)
};
write!(f, "{}", s)
}
} }
#[derive(Clone)] #[derive(Clone)]
enum FileType { enum FileType {
Account,
PrivateKey,
Certificate,
Account,
PrivateKey,
Certificate,
} }
impl fmt::Display for FileType { impl fmt::Display for FileType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
FileType::Account => "account",
FileType::PrivateKey => "pk",
FileType::Certificate => "crt",
};
write!(f, "{}", s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
FileType::Account => "account",
FileType::PrivateKey => "pk",
FileType::Certificate => "crt",
};
write!(f, "{}", s)
}
} }
#[derive(Clone, Serialize)] #[derive(Clone, Serialize)]
pub struct CertFileFormat { pub struct CertFileFormat {
pub ext: String,
pub file_type: String,
pub key_type: String,
pub name: String,
pub ext: String,
pub file_type: String,
pub key_type: String,
pub name: String,
} }
fn get_file_full_path( fn get_file_full_path(
fm: &FileManager,
file_type: FileType,
fm: &FileManager,
file_type: FileType,
) -> Result<(String, String, PathBuf), Error> { ) -> Result<(String, String, PathBuf), Error> {
let base_path = match file_type {
FileType::Account => &fm.account_directory,
FileType::PrivateKey => &fm.crt_directory,
FileType::Certificate => &fm.crt_directory,
};
let file_name = match file_type {
FileType::Account => format!(
"{account}.{file_type}.{ext}",
account = b64_encode(&fm.account_name),
file_type = file_type,
ext = "bin"
),
FileType::PrivateKey | FileType::Certificate => {
let fmt_data = CertFileFormat {
key_type: fm.crt_key_type.to_string(),
ext: "pem".into(),
file_type: file_type.to_string(),
name: fm.crt_name.to_owned(),
};
render_template(&fm.crt_name_format, &fmt_data)?
}
};
let mut path = PathBuf::from(&base_path);
path.push(&file_name);
Ok((base_path.to_string(), file_name, path))
let base_path = match file_type {
FileType::Account => &fm.account_directory,
FileType::PrivateKey => &fm.crt_directory,
FileType::Certificate => &fm.crt_directory,
};
let file_name = match file_type {
FileType::Account => format!(
"{account}.{file_type}.{ext}",
account = b64_encode(&fm.account_name),
file_type = file_type,
ext = "bin"
),
FileType::PrivateKey | FileType::Certificate => {
let fmt_data = CertFileFormat {
key_type: fm.crt_key_type.to_string(),
ext: "pem".into(),
file_type: file_type.to_string(),
name: fm.crt_name.to_owned(),
};
render_template(&fm.crt_name_format, &fmt_data)?
}
};
let mut path = PathBuf::from(&base_path);
path.push(&file_name);
Ok((base_path.to_string(), file_name, path))
} }
fn get_file_path(fm: &FileManager, file_type: FileType) -> Result<PathBuf, Error> { fn get_file_path(fm: &FileManager, file_type: FileType) -> Result<PathBuf, Error> {
let (_, _, path) = get_file_full_path(fm, file_type)?;
Ok(path)
let (_, _, path) = get_file_full_path(fm, file_type)?;
Ok(path)
} }
fn read_file(fm: &FileManager, path: &Path) -> Result<Vec<u8>, Error> { fn read_file(fm: &FileManager, path: &Path) -> Result<Vec<u8>, Error> {
fm.trace(&format!("reading file {:?}", path));
let mut file =
File::open(path).map_err(|e| Error::from(e).prefix(&path.display().to_string()))?;
let mut contents = vec![];
file.read_to_end(&mut contents)?;
Ok(contents)
fm.trace(&format!("reading file {:?}", path));
let mut file =
File::open(path).map_err(|e| Error::from(e).prefix(&path.display().to_string()))?;
let mut contents = vec![];
file.read_to_end(&mut contents)?;
Ok(contents)
} }
#[cfg(unix)] #[cfg(unix)]
fn set_owner(fm: &FileManager, path: &Path, file_type: FileType) -> Result<(), Error> { fn set_owner(fm: &FileManager, path: &Path, file_type: FileType) -> Result<(), Error> {
let (uid, gid) = match file_type {
FileType::Certificate => (fm.cert_file_owner.to_owned(), fm.cert_file_group.to_owned()),
FileType::PrivateKey => (fm.pk_file_owner.to_owned(), fm.pk_file_group.to_owned()),
FileType::Account => {
// The account file does not need to be accessible to users other different from the current one.
return Ok(());
}
};
let uid = match uid {
Some(u) => {
if u.bytes().all(|b| b.is_ascii_digit()) {
let raw_uid = u
.parse::<u32>()
.map_err(|_| Error::from("unable to parse the UID"))?;
let nix_uid = nix::unistd::Uid::from_raw(raw_uid);
Some(nix_uid)
} else {
let user = nix::unistd::User::from_name(&u)?;
user.map(|u| u.uid)
}
}
None => None,
};
let gid = match gid {
Some(g) => {
if g.bytes().all(|b| b.is_ascii_digit()) {
let raw_gid = g
.parse::<u32>()
.map_err(|_| Error::from("unable to parse the GID"))?;
let nix_gid = nix::unistd::Gid::from_raw(raw_gid);
Some(nix_gid)
} else {
let grp = nix::unistd::Group::from_name(&g)?;
grp.map(|g| g.gid)
}
}
None => None,
};
match uid {
Some(u) => fm.trace(&format!("{:?}: setting the uid to {}", path, u.as_raw())),
None => fm.trace(&format!("{:?}: uid unchanged", path)),
};
match gid {
Some(g) => fm.trace(&format!("{:?}: setting the gid to {}", path, g.as_raw())),
None => fm.trace(&format!("{:?}: gid unchanged", path)),
};
match nix::unistd::chown(path, uid, gid) {
Ok(_) => Ok(()),
Err(e) => Err(format!("{}", e).into()),
}
let (uid, gid) = match file_type {
FileType::Certificate => (fm.cert_file_owner.to_owned(), fm.cert_file_group.to_owned()),
FileType::PrivateKey => (fm.pk_file_owner.to_owned(), fm.pk_file_group.to_owned()),
FileType::Account => {
// The account file does not need to be accessible to users other different from the current one.
return Ok(());
}
};
let uid = match uid {
Some(u) => {
if u.bytes().all(|b| b.is_ascii_digit()) {
let raw_uid = u
.parse::<u32>()
.map_err(|_| Error::from("unable to parse the UID"))?;
let nix_uid = nix::unistd::Uid::from_raw(raw_uid);
Some(nix_uid)
} else {
let user = nix::unistd::User::from_name(&u)?;
user.map(|u| u.uid)
}
}
None => None,
};
let gid = match gid {
Some(g) => {
if g.bytes().all(|b| b.is_ascii_digit()) {
let raw_gid = g
.parse::<u32>()
.map_err(|_| Error::from("unable to parse the GID"))?;
let nix_gid = nix::unistd::Gid::from_raw(raw_gid);
Some(nix_gid)
} else {
let grp = nix::unistd::Group::from_name(&g)?;
grp.map(|g| g.gid)
}
}
None => None,
};
match uid {
Some(u) => fm.trace(&format!("{:?}: setting the uid to {}", path, u.as_raw())),
None => fm.trace(&format!("{:?}: uid unchanged", path)),
};
match gid {
Some(g) => fm.trace(&format!("{:?}: setting the gid to {}", path, g.as_raw())),
None => fm.trace(&format!("{:?}: gid unchanged", path)),
};
match nix::unistd::chown(path, uid, gid) {
Ok(_) => Ok(()),
Err(e) => Err(format!("{}", e).into()),
}
} }
fn write_file(fm: &FileManager, file_type: FileType, data: &[u8]) -> Result<(), Error> { fn write_file(fm: &FileManager, file_type: FileType, data: &[u8]) -> Result<(), Error> {
let (file_directory, file_name, path) = get_file_full_path(fm, file_type.clone())?;
let mut hook_data = FileStorageHookData {
file_name,
file_directory,
file_path: path.to_owned(),
env: HashMap::new(),
};
hook_data.set_env(&fm.env);
let is_new = !path.is_file();
let (file_directory, file_name, path) = get_file_full_path(fm, file_type.clone())?;
let mut hook_data = FileStorageHookData {
file_name,
file_directory,
file_path: path.to_owned(),
env: HashMap::new(),
};
hook_data.set_env(&fm.env);
let is_new = !path.is_file();
if is_new {
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreCreate)?;
} else {
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreEdit)?;
}
if is_new {
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreCreate)?;
} else {
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreEdit)?;
}
fm.trace(&format!("writing file {:?}", path));
let mut file = if cfg!(unix) {
let mut options = OpenOptions::new();
options.mode(match &file_type {
FileType::Certificate => fm.cert_file_mode,
FileType::PrivateKey => fm.pk_file_mode,
FileType::Account => crate::DEFAULT_ACCOUNT_FILE_MODE,
});
options
.write(true)
.create(true)
.open(&path)
.map_err(|e| Error::from(e).prefix(&path.display().to_string()))?
} else {
File::create(&path).map_err(|e| Error::from(e).prefix(&path.display().to_string()))?
};
file.write_all(data)
.map_err(|e| Error::from(e).prefix(&path.display().to_string()))?;
if cfg!(unix) {
set_owner(fm, &path, file_type).map_err(|e| e.prefix(&path.display().to_string()))?;
}
fm.trace(&format!("writing file {:?}", path));
let mut file = if cfg!(unix) {
let mut options = OpenOptions::new();
options.mode(match &file_type {
FileType::Certificate => fm.cert_file_mode,
FileType::PrivateKey => fm.pk_file_mode,
FileType::Account => crate::DEFAULT_ACCOUNT_FILE_MODE,
});
options
.write(true)
.create(true)
.open(&path)
.map_err(|e| Error::from(e).prefix(&path.display().to_string()))?
} else {
File::create(&path).map_err(|e| Error::from(e).prefix(&path.display().to_string()))?
};
file.write_all(data)
.map_err(|e| Error::from(e).prefix(&path.display().to_string()))?;
if cfg!(unix) {
set_owner(fm, &path, file_type).map_err(|e| e.prefix(&path.display().to_string()))?;
}
if is_new {
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostCreate)?;
} else {
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostEdit)?;
}
Ok(())
if is_new {
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostCreate)?;
} else {
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostEdit)?;
}
Ok(())
} }
pub fn get_account_data(fm: &FileManager) -> Result<Vec<u8>, Error> { pub fn get_account_data(fm: &FileManager) -> Result<Vec<u8>, Error> {
let path = get_file_path(fm, FileType::Account)?;
read_file(fm, &path)
let path = get_file_path(fm, FileType::Account)?;
read_file(fm, &path)
} }
pub fn set_account_data(fm: &FileManager, data: &[u8]) -> Result<(), Error> { pub fn set_account_data(fm: &FileManager, data: &[u8]) -> Result<(), Error> {
write_file(fm, FileType::Account, data)
write_file(fm, FileType::Account, data)
} }
pub fn get_keypair(fm: &FileManager) -> Result<KeyPair, Error> { pub fn get_keypair(fm: &FileManager) -> Result<KeyPair, Error> {
let path = get_file_path(fm, FileType::PrivateKey)?;
let raw_key = read_file(fm, &path)?;
let key = KeyPair::from_pem(&raw_key)?;
Ok(key)
let path = get_file_path(fm, FileType::PrivateKey)?;
let raw_key = read_file(fm, &path)?;
let key = KeyPair::from_pem(&raw_key)?;
Ok(key)
} }
pub fn set_keypair(fm: &FileManager, key_pair: &KeyPair) -> Result<(), Error> { pub fn set_keypair(fm: &FileManager, key_pair: &KeyPair) -> Result<(), Error> {
let data = key_pair.private_key_to_pem()?;
write_file(fm, FileType::PrivateKey, &data)
let data = key_pair.private_key_to_pem()?;
write_file(fm, FileType::PrivateKey, &data)
} }
pub fn get_certificate(fm: &FileManager) -> Result<X509Certificate, Error> { pub fn get_certificate(fm: &FileManager) -> Result<X509Certificate, Error> {
let path = get_file_path(fm, FileType::Certificate)?;
let raw_crt = read_file(fm, &path)?;
let crt = X509Certificate::from_pem(&raw_crt)?;
Ok(crt)
let path = get_file_path(fm, FileType::Certificate)?;
let raw_crt = read_file(fm, &path)?;
let crt = X509Certificate::from_pem(&raw_crt)?;
Ok(crt)
} }
pub fn write_certificate(fm: &FileManager, data: &[u8]) -> Result<(), Error> { pub fn write_certificate(fm: &FileManager, data: &[u8]) -> Result<(), Error> {
write_file(fm, FileType::Certificate, data)
write_file(fm, FileType::Certificate, data)
} }
fn check_files(fm: &FileManager, file_types: &[FileType]) -> bool { fn check_files(fm: &FileManager, file_types: &[FileType]) -> bool {
for t in file_types.iter().cloned() {
let path = match get_file_path(fm, t) {
Ok(p) => p,
Err(_) => {
return false;
}
};
fm.trace(&format!(
"testing file path: {}",
path.to_str().unwrap_or_default()
));
if !path.is_file() {
return false;
}
}
true
for t in file_types.iter().cloned() {
let path = match get_file_path(fm, t) {
Ok(p) => p,
Err(_) => {
return false;
}
};
fm.trace(&format!(
"testing file path: {}",
path.to_str().unwrap_or_default()
));
if !path.is_file() {
return false;
}
}
true
} }
pub fn account_files_exists(fm: &FileManager) -> bool { pub fn account_files_exists(fm: &FileManager) -> bool {
let file_types = vec![FileType::Account];
check_files(fm, &file_types)
let file_types = vec![FileType::Account];
check_files(fm, &file_types)
} }
pub fn certificate_files_exists(fm: &FileManager) -> bool { pub fn certificate_files_exists(fm: &FileManager) -> bool {
let file_types = vec![FileType::PrivateKey, FileType::Certificate];
check_files(fm, &file_types)
let file_types = vec![FileType::PrivateKey, FileType::Certificate];
check_files(fm, &file_types)
} }

102
acmed/src/template.rs

@ -4,70 +4,70 @@ use serde_json::Value;
use tinytemplate::TinyTemplate; use tinytemplate::TinyTemplate;
macro_rules! default_format { macro_rules! default_format {
($value: ident, $output: ident) => {{
$output.push_str(&$value.to_string());
Ok(())
}};
($value: ident, $output: ident) => {{
$output.push_str(&$value.to_string());
Ok(())
}};
} }
fn formatter_rev_labels(value: &Value, output: &mut String) -> tinytemplate::error::Result<()> { fn formatter_rev_labels(value: &Value, output: &mut String) -> tinytemplate::error::Result<()> {
match value {
Value::Null => Ok(()),
Value::Bool(v) => default_format!(v, output),
Value::Number(v) => default_format!(v, output),
Value::String(v) => {
let s = v.rsplit('.').collect::<Vec<&str>>().join(".");
output.push_str(&s);
Ok(())
}
_ => Ok(()),
}
match value {
Value::Null => Ok(()),
Value::Bool(v) => default_format!(v, output),
Value::Number(v) => default_format!(v, output),
Value::String(v) => {
let s = v.rsplit('.').collect::<Vec<&str>>().join(".");
output.push_str(&s);
Ok(())
}
_ => Ok(()),
}
} }
pub fn render_template<T>(template: &str, data: &T) -> Result<String, Error> pub fn render_template<T>(template: &str, data: &T) -> Result<String, Error>
where where
T: Serialize,
T: Serialize,
{ {
let mut reg = TinyTemplate::new();
reg.add_formatter("rev_labels", formatter_rev_labels);
reg.add_template("reg", template)?;
Ok(reg.render("reg", data)?)
let mut reg = TinyTemplate::new();
reg.add_formatter("rev_labels", formatter_rev_labels);
reg.add_template("reg", template)?;
Ok(reg.render("reg", data)?)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::render_template;
use serde::Serialize;
use super::render_template;
use serde::Serialize;
#[derive(Serialize)]
struct TplTest {
foo: String,
bar: u64,
}
#[derive(Serialize)]
struct TplTest {
foo: String,
bar: u64,
}
#[test]
fn test_basic_template() {
let c = TplTest {
foo: String::from("test"),
bar: 42,
};
let tpl = "This is { foo } { bar -} !";
let rendered = render_template(tpl, &c);
assert!(rendered.is_ok());
let rendered = rendered.unwrap();
assert_eq!(rendered, "This is test 42!");
}
#[test]
fn test_basic_template() {
let c = TplTest {
foo: String::from("test"),
bar: 42,
};
let tpl = "This is { foo } { bar -} !";
let rendered = render_template(tpl, &c);
assert!(rendered.is_ok());
let rendered = rendered.unwrap();
assert_eq!(rendered, "This is test 42!");
}
#[test]
fn test_formatter_rev_labels() {
let c = TplTest {
foo: String::from("mx1.example.org"),
bar: 42,
};
let tpl = "{ foo } - { foo | rev_labels }";
let rendered = render_template(tpl, &c);
assert!(rendered.is_ok());
let rendered = rendered.unwrap();
assert_eq!(rendered, "mx1.example.org - org.example.mx1");
}
#[test]
fn test_formatter_rev_labels() {
let c = TplTest {
foo: String::from("mx1.example.org"),
bar: 42,
};
let tpl = "{ foo } - { foo | rev_labels }";
let rendered = render_template(tpl, &c);
assert!(rendered.is_ok());
let rendered = rendered.unwrap();
assert_eq!(rendered, "mx1.example.org - org.example.mx1");
}
} }

1
rustfmt.toml

@ -0,0 +1 @@
hard_tabs = true

44
tacd/build.rs

@ -2,39 +2,39 @@ use std::env;
use std::path::PathBuf; use std::path::PathBuf;
macro_rules! set_rustc_env_var { macro_rules! set_rustc_env_var {
($name: expr, $value: expr) => {{
println!("cargo:rustc-env={}={}", $name, $value);
}};
($name: expr, $value: expr) => {{
println!("cargo:rustc-env={}={}", $name, $value);
}};
} }
macro_rules! set_env_var_if_absent { macro_rules! set_env_var_if_absent {
($name: expr, $default_value: expr) => {{
if let Err(_) = env::var($name) {
set_rustc_env_var!($name, $default_value);
}
}};
($name: expr, $default_value: expr) => {{
if let Err(_) = env::var($name) {
set_rustc_env_var!($name, $default_value);
}
}};
} }
macro_rules! set_specific_path_if_absent { macro_rules! set_specific_path_if_absent {
($env_name: expr, $env_default: expr, $name: expr, $default_value: expr) => {{
let prefix = env::var($env_name).unwrap_or(String::from($env_default));
let mut value = PathBuf::new();
value.push(prefix);
value.push($default_value);
set_env_var_if_absent!($name, value.to_str().unwrap());
}};
($env_name: expr, $env_default: expr, $name: expr, $default_value: expr) => {{
let prefix = env::var($env_name).unwrap_or(String::from($env_default));
let mut value = PathBuf::new();
value.push(prefix);
value.push($default_value);
set_env_var_if_absent!($name, value.to_str().unwrap());
}};
} }
macro_rules! set_runstate_path_if_absent { macro_rules! set_runstate_path_if_absent {
($name: expr, $default_value: expr) => {{
set_specific_path_if_absent!("RUNSTATEDIR", "/run", $name, $default_value);
}};
($name: expr, $default_value: expr) => {{
set_specific_path_if_absent!("RUNSTATEDIR", "/run", $name, $default_value);
}};
} }
fn main() { fn main() {
if let Ok(target) = env::var("TARGET") {
println!("cargo:rustc-env=TACD_TARGET={}", target);
};
if let Ok(target) = env::var("TARGET") {
println!("cargo:rustc-env=TACD_TARGET={}", target);
};
set_runstate_path_if_absent!("TACD_DEFAULT_PID_FILE", "tacd.pid");
set_runstate_path_if_absent!("TACD_DEFAULT_PID_FILE", "tacd.pid");
} }

148
tacd/src/main.rs

@ -22,69 +22,69 @@ const DEFAULT_CRT_DIGEST: HashFunction = HashFunction::Sha256;
const ALPN_ACME_PROTO_NAME: &[u8] = b"\x0aacme-tls/1"; const ALPN_ACME_PROTO_NAME: &[u8] = b"\x0aacme-tls/1";
fn read_line(path: Option<&String>) -> Result<String, Error> { fn read_line(path: Option<&String>) -> Result<String, Error> {
let mut input = String::new();
match path {
Some(p) => File::open(p)?.read_to_string(&mut input)?,
None => io::stdin().read_line(&mut input)?,
};
let line = input.trim().to_string();
Ok(line)
let mut input = String::new();
match path {
Some(p) => File::open(p)?.read_to_string(&mut input)?,
None => io::stdin().read_line(&mut input)?,
};
let line = input.trim().to_string();
Ok(line)
} }
fn get_acme_value(cnf: &ArgMatches, opt: &str, opt_file: &str) -> Result<String, Error> { fn get_acme_value(cnf: &ArgMatches, opt: &str, opt_file: &str) -> Result<String, Error> {
match cnf.get_one::<String>(opt) {
Some(v) => Ok(v.to_string()),
None => {
debug!(
"reading {} from {}",
opt,
cnf.get_one::<String>(opt_file)
.map(|e| e.as_str())
.unwrap_or("stdin")
);
read_line(cnf.get_one::<String>(opt_file))
}
}
match cnf.get_one::<String>(opt) {
Some(v) => Ok(v.to_string()),
None => {
debug!(
"reading {} from {}",
opt,
cnf.get_one::<String>(opt_file)
.map(|e| e.as_str())
.unwrap_or("stdin")
);
read_line(cnf.get_one::<String>(opt_file))
}
}
} }
fn init(cnf: &ArgMatches) -> Result<(), Error> { fn init(cnf: &ArgMatches) -> Result<(), Error> {
acme_common::init_server(
cnf.get_flag("foreground"),
cnf.get_one::<String>("pid-file").map(|e| e.as_str()),
);
let domain = get_acme_value(cnf, "domain", "domain-file")?;
let domain = to_idna(&domain)?;
let ext = get_acme_value(cnf, "acme-ext", "acme-ext-file")?;
let listen_addr = cnf
.get_one::<String>("listen")
.map(|e| e.as_str())
.unwrap_or(DEFAULT_LISTEN_ADDR);
let crt_signature_alg = match cnf.get_one::<&str>("crt-signature-alg") {
Some(alg) => alg.parse()?,
None => DEFAULT_CRT_KEY_TYPE,
};
let crt_digest = match cnf.get_one::<&str>("crt-digest") {
Some(alg) => alg.parse()?,
None => DEFAULT_CRT_DIGEST,
};
let (pk, cert) = X509Certificate::from_acme_ext(&domain, &ext, crt_signature_alg, crt_digest)?;
info!("starting {} on {} for {}", APP_NAME, listen_addr, domain);
server_start(listen_addr, &cert, &pk)?;
Ok(())
acme_common::init_server(
cnf.get_flag("foreground"),
cnf.get_one::<String>("pid-file").map(|e| e.as_str()),
);
let domain = get_acme_value(cnf, "domain", "domain-file")?;
let domain = to_idna(&domain)?;
let ext = get_acme_value(cnf, "acme-ext", "acme-ext-file")?;
let listen_addr = cnf
.get_one::<String>("listen")
.map(|e| e.as_str())
.unwrap_or(DEFAULT_LISTEN_ADDR);
let crt_signature_alg = match cnf.get_one::<&str>("crt-signature-alg") {
Some(alg) => alg.parse()?,
None => DEFAULT_CRT_KEY_TYPE,
};
let crt_digest = match cnf.get_one::<&str>("crt-digest") {
Some(alg) => alg.parse()?,
None => DEFAULT_CRT_DIGEST,
};
let (pk, cert) = X509Certificate::from_acme_ext(&domain, &ext, crt_signature_alg, crt_digest)?;
info!("starting {} on {} for {}", APP_NAME, listen_addr, domain);
server_start(listen_addr, &cert, &pk)?;
Ok(())
} }
fn main() { fn main() {
let full_version = format!(
"{} built for {}\n\nCryptographic library:\n - {} {}",
APP_VERSION,
env!("TACD_TARGET"),
get_lib_name(),
get_lib_version(),
);
let default_crt_key_type = DEFAULT_CRT_KEY_TYPE.to_string();
let default_crt_digest = DEFAULT_CRT_DIGEST.to_string();
let default_log_level = DEFAULT_LOG_LEVEL.to_string().to_lowercase();
let matches = Command::new(APP_NAME)
let full_version = format!(
"{} built for {}\n\nCryptographic library:\n - {} {}",
APP_VERSION,
env!("TACD_TARGET"),
get_lib_name(),
get_lib_version(),
);
let default_crt_key_type = DEFAULT_CRT_KEY_TYPE.to_string();
let default_crt_digest = DEFAULT_CRT_DIGEST.to_string();
let default_log_level = DEFAULT_LOG_LEVEL.to_string().to_lowercase();
let matches = Command::new(APP_NAME)
.version(APP_VERSION) .version(APP_VERSION)
.long_version(full_version) .long_version(full_version)
.arg( .arg(
@ -197,25 +197,25 @@ fn main() {
) )
.get_matches(); .get_matches();
match set_log_system(
matches.get_one::<String>("log-level").map(|e| e.as_str()),
matches.get_flag("to-syslog"),
matches.get_flag("to-stderr"),
) {
Ok(_) => {}
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(2);
}
};
match set_log_system(
matches.get_one::<String>("log-level").map(|e| e.as_str()),
matches.get_flag("to-syslog"),
matches.get_flag("to-stderr"),
) {
Ok(_) => {}
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(2);
}
};
match init(&matches) {
Ok(_) => {}
Err(e) => {
error!("{}", e);
let pid_file = matches.get_one::<String>("pid-file").map(|e| e.as_str());
let _ = clean_pid_file(pid_file);
std::process::exit(1);
}
};
match init(&matches) {
Ok(_) => {}
Err(e) => {
error!("{}", e);
let pid_file = matches.get_one::<String>("pid-file").map(|e| e.as_str());
let _ = clean_pid_file(pid_file);
std::process::exit(1);
}
};
} }

66
tacd/src/openssl_server.rs

@ -15,41 +15,41 @@ const ALPN_ERROR: AlpnError = AlpnError::ALERT_FATAL;
const ALPN_ERROR: AlpnError = AlpnError::NOACK; const ALPN_ERROR: AlpnError = AlpnError::NOACK;
macro_rules! listen_and_accept { macro_rules! listen_and_accept {
($lt: ident, $addr: ident, $acceptor: ident) => {
let listener = $lt::bind($addr)?;
for stream in listener.incoming() {
if let Ok(stream) = stream {
let acceptor = $acceptor.clone();
thread::spawn(move || {
debug!("new client");
let _ = acceptor.accept(stream).unwrap();
});
};
}
};
($lt: ident, $addr: ident, $acceptor: ident) => {
let listener = $lt::bind($addr)?;
for stream in listener.incoming() {
if let Ok(stream) = stream {
let acceptor = $acceptor.clone();
thread::spawn(move || {
debug!("new client");
let _ = acceptor.accept(stream).unwrap();
});
};
}
};
} }
pub fn start( pub fn start(
listen_addr: &str,
certificate: &X509Certificate,
key_pair: &KeyPair,
listen_addr: &str,
certificate: &X509Certificate,
key_pair: &KeyPair,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
acceptor.set_alpn_select_callback(|_, client| {
debug!("ALPN negociation");
ssl::select_next_proto(crate::ALPN_ACME_PROTO_NAME, client).ok_or(ALPN_ERROR)
});
acceptor.set_private_key(&key_pair.inner_key)?;
acceptor.set_certificate(&certificate.inner_cert)?;
acceptor.check_private_key()?;
let acceptor = Arc::new(acceptor.build());
if cfg!(unix) && listen_addr.starts_with("unix:") {
let listen_addr = &listen_addr[5..];
debug!("listening on unix socket {}", listen_addr);
listen_and_accept!(UnixListener, listen_addr, acceptor);
} else {
debug!("listening on {}", listen_addr);
listen_and_accept!(TcpListener, listen_addr, acceptor);
}
Err("main thread loop unexpectedly exited".into())
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
acceptor.set_alpn_select_callback(|_, client| {
debug!("ALPN negociation");
ssl::select_next_proto(crate::ALPN_ACME_PROTO_NAME, client).ok_or(ALPN_ERROR)
});
acceptor.set_private_key(&key_pair.inner_key)?;
acceptor.set_certificate(&certificate.inner_cert)?;
acceptor.check_private_key()?;
let acceptor = Arc::new(acceptor.build());
if cfg!(unix) && listen_addr.starts_with("unix:") {
let listen_addr = &listen_addr[5..];
debug!("listening on unix socket {}", listen_addr);
listen_and_accept!(UnixListener, listen_addr, acceptor);
} else {
debug!("listening on {}", listen_addr);
listen_and_accept!(TcpListener, listen_addr, acceptor);
}
Err("main thread loop unexpectedly exited".into())
} }
Loading…
Cancel
Save