Browse Source

Add internationalized domain names support

pull/19/head
Rodolphe Breard 5 years ago
parent
commit
39430009ac
  1. 2
      CHANGELOG.md
  2. 1
      acme_common/Cargo.toml
  3. 63
      acme_common/src/lib.rs
  4. 11
      acmed/src/config.rs
  5. 2
      acmed/src/main_event_loop.rs
  6. 2
      tacd/src/main.rs

2
CHANGELOG.md

@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Wildcard certificates are now supported. In the file name, the `*` is replaced by `_`. - Wildcard certificates are now supported. In the file name, the `*` is replaced by `_`.
- Internationalized domain names are now supported.
### Changed ### Changed
- The PID file is now always written whether or not ACMEd is running in the foreground. Previously, it was written only when running in the background. - The PID file is now always written whether or not ACMEd is running in the foreground. Previously, it was written only when running in the background.
@ -24,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- In the directory, the `externalAccountRequired` field is now a boolean instead of a string. - In the directory, the `externalAccountRequired` field is now a boolean instead of a string.
## [0.6.1] - 2019-09-13 ## [0.6.1] - 2019-09-13
### Fixed ### Fixed

1
acme_common/Cargo.toml

@ -20,6 +20,7 @@ handlebars = "3.0"
http_req = "0.5" http_req = "0.5"
log = "0.4" log = "0.4"
openssl = "0.10" openssl = "0.10"
punycode = "0.4"
serde_json = "1.0" serde_json = "1.0"
syslog = "4.0" syslog = "4.0"
toml = "0.5" toml = "0.5"

63
acme_common/src/lib.rs

@ -19,6 +19,23 @@ macro_rules! exit_match {
}; };
} }
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("."))
}
pub fn b64_encode<T: ?Sized + AsRef<[u8]>>(input: &T) -> String { pub fn b64_encode<T: ?Sized + AsRef<[u8]>>(input: &T) -> String {
base64::encode_config(input, base64::URL_SAFE_NO_PAD) base64::encode_config(input, base64::URL_SAFE_NO_PAD)
} }
@ -39,3 +56,49 @@ fn write_pid_file(pid_file: &str) -> Result<(), error::Error> {
file.sync_all()?; file.sync_all()?;
Ok(()) Ok(())
} }
#[cfg(test)]
mod tests {
use super::to_idna;
#[test]
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");
}
#[test]
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");
}
#[test]
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"
);
}
#[test]
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");
}
#[test]
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"
);
}
}

11
acmed/src/config.rs

@ -2,6 +2,7 @@ use crate::certificate::Algorithm;
use crate::hooks; use crate::hooks;
use crate::rate_limits; use crate::rate_limits;
use acme_common::error::Error; use acme_common::error::Error;
use acme_common::to_idna;
use log::info; use log::info;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
@ -289,6 +290,16 @@ impl Certificate {
Algorithm::from_str(algo) Algorithm::from_str(algo)
} }
pub fn get_domains(&self) -> Result<Vec<Domain>, Error> {
let mut ret = vec![];
for d in self.domains.iter() {
let mut nd = d.clone();
nd.dns = to_idna(&nd.dns)?;
ret.push(nd);
}
Ok(ret)
}
pub fn get_kp_reuse(&self) -> bool { pub fn get_kp_reuse(&self) -> bool {
match self.kp_reuse { match self.kp_reuse {
Some(b) => b, Some(b) => b,

2
acmed/src/main_event_loop.rs

@ -48,7 +48,7 @@ impl MainEventLoop {
.to_owned(); .to_owned();
let cert = Certificate { let cert = Certificate {
account: crt.get_account(&cnf)?, account: crt.get_account(&cnf)?,
domains: crt.domains.to_owned(),
domains: crt.get_domains()?,
algo: crt.get_algorithm()?, algo: crt.get_algorithm()?,
kp_reuse: crt.get_kp_reuse(), kp_reuse: crt.get_kp_reuse(),
remote_url: crt.get_remote_url(&cnf)?, remote_url: crt.get_remote_url(&cnf)?,

2
tacd/src/main.rs

@ -3,6 +3,7 @@ mod openssl_server;
use crate::openssl_server::start as server_start; use crate::openssl_server::start as server_start;
use acme_common::crypto::X509Certificate; use acme_common::crypto::X509Certificate;
use acme_common::error::Error; use acme_common::error::Error;
use acme_common::to_idna;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use log::{debug, error, info}; use log::{debug, error, info};
use std::fs::File; use std::fs::File;
@ -44,6 +45,7 @@ fn init(cnf: &ArgMatches) -> Result<(), Error> {
cnf.value_of("pid-file").unwrap_or(DEFAULT_PID_FILE), cnf.value_of("pid-file").unwrap_or(DEFAULT_PID_FILE),
); );
let domain = get_acme_value(cnf, "domain", "domain-file")?; 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 ext = get_acme_value(cnf, "acme-ext", "acme-ext-file")?;
let listen_addr = cnf.value_of("listen").unwrap_or(DEFAULT_LISTEN_ADDR); let listen_addr = cnf.value_of("listen").unwrap_or(DEFAULT_LISTEN_ADDR);
let (pk, cert) = X509Certificate::from_acme_ext(&domain, &ext)?; let (pk, cert) = X509Certificate::from_acme_ext(&domain, &ext)?;

Loading…
Cancel
Save