Browse Source

Add an option to specify new trusted root certificates

Sometimes, it is not possible to use certificates signed by a known
certificate authority. Hence, in order to prevent a TLS error, it is
required to explicitly add a new trusted root certificate.
This is the case with Pebble, which provides the certificate.

https://github.com/letsencrypt/pebble#avoiding-client-https-errors
pull/5/head
Rodolphe Breard 5 years ago
parent
commit
447d1f848f
  1. 24
      acmed/src/acme_proto.rs
  2. 3
      acmed/src/acme_proto/account.rs
  3. 42
      acmed/src/acme_proto/http.rs
  4. 15
      acmed/src/main.rs
  5. 10
      acmed/src/main_event_loop.rs

24
acmed/src/acme_proto.rs

@ -66,28 +66,28 @@ macro_rules! set_empty_data_builder {
}; };
} }
pub fn request_certificate(cert: &Certificate) -> Result<(), Error> {
pub fn request_certificate(cert: &Certificate, root_certs: &[String]) -> Result<(), Error> {
// 1. Get the directory // 1. Get the directory
let directory = http::get_directory(&cert.remote_url)?;
let directory = http::get_directory(root_certs, &cert.remote_url)?;
// 2. Get a first nonce // 2. Get a first nonce
let nonce = http::get_nonce(&directory.new_nonce)?;
let nonce = http::get_nonce(root_certs, &directory.new_nonce)?;
// 3. Get or create the account // 3. Get or create the account
let (account, nonce) = AccountManager::new(cert, &directory, &nonce)?;
let (account, nonce) = AccountManager::new(cert, &directory, &nonce, root_certs)?;
// 4. Create a new order // 4. Create a new order
let new_order = NewOrder::new(&cert.domains); let new_order = NewOrder::new(&cert.domains);
let new_order = serde_json::to_string(&new_order)?; let new_order = serde_json::to_string(&new_order)?;
let data_builder = set_data_builder!(account, new_order.as_bytes(), directory.new_order); let data_builder = set_data_builder!(account, new_order.as_bytes(), directory.new_order);
let (order, order_url, mut nonce): (Order, String, String) = let (order, order_url, mut nonce): (Order, String, String) =
http::get_obj_loc(&directory.new_order, &data_builder, &nonce)?;
http::get_obj_loc(root_certs, &directory.new_order, &data_builder, &nonce)?;
// 5. Get all the required authorizations // 5. Get all the required authorizations
for auth_url in order.authorizations.iter() { for auth_url in order.authorizations.iter() {
let data_builder = set_empty_data_builder!(account, auth_url); let data_builder = set_empty_data_builder!(account, auth_url);
let (auth, new_nonce): (Authorization, String) = let (auth, new_nonce): (Authorization, String) =
http::get_obj(&auth_url, &data_builder, &nonce)?;
http::get_obj(root_certs, &auth_url, &data_builder, &nonce)?;
nonce = new_nonce; nonce = new_nonce;
if auth.status == AuthorizationStatus::Valid { if auth.status == AuthorizationStatus::Valid {
@ -114,7 +114,7 @@ pub fn request_certificate(cert: &Certificate) -> Result<(), Error> {
// 8. Tell the server the challenge has been completed // 8. Tell the server the challenge has been completed
let chall_url = challenge.get_url(); let chall_url = challenge.get_url();
let data_builder = set_data_builder!(account, b"{}", chall_url); let data_builder = set_data_builder!(account, b"{}", chall_url);
let new_nonce = http::post_challenge_response(&chall_url, &data_builder, &nonce)?;
let new_nonce = http::post_challenge_response(root_certs, &chall_url, &data_builder, &nonce)?;
nonce = new_nonce; nonce = new_nonce;
} }
} }
@ -123,7 +123,7 @@ pub fn request_certificate(cert: &Certificate) -> Result<(), Error> {
let data_builder = set_empty_data_builder!(account, auth_url); let data_builder = set_empty_data_builder!(account, auth_url);
let break_fn = |a: &Authorization| a.status == AuthorizationStatus::Valid; let break_fn = |a: &Authorization| a.status == AuthorizationStatus::Valid;
let (_, new_nonce): (Authorization, String) = let (_, new_nonce): (Authorization, String) =
http::pool_obj(&auth_url, &data_builder, &break_fn, &nonce)?;
http::pool_obj(root_certs, &auth_url, &data_builder, &break_fn, &nonce)?;
nonce = new_nonce; nonce = new_nonce;
} }
@ -131,26 +131,26 @@ pub fn request_certificate(cert: &Certificate) -> Result<(), Error> {
let data_builder = set_empty_data_builder!(account, order_url); let data_builder = set_empty_data_builder!(account, order_url);
let break_fn = |o: &Order| o.status == OrderStatus::Ready; let break_fn = |o: &Order| o.status == OrderStatus::Ready;
let (order, nonce): (Order, String) = let (order, nonce): (Order, String) =
http::pool_obj(&order_url, &data_builder, &break_fn, &nonce)?;
http::pool_obj(root_certs, &order_url, &data_builder, &break_fn, &nonce)?;
// 11. Finalize the order by sending the CSR // 11. Finalize the order by sending the CSR
let (priv_key, pub_key) = certificate::get_key_pair(cert)?; let (priv_key, pub_key) = certificate::get_key_pair(cert)?;
let csr = certificate::generate_csr(cert, &priv_key, &pub_key)?; let csr = certificate::generate_csr(cert, &priv_key, &pub_key)?;
let data_builder = set_data_builder!(account, csr.as_bytes(), order.finalize); let data_builder = set_data_builder!(account, csr.as_bytes(), order.finalize);
let (_, nonce): (Order, String) = http::get_obj(&order.finalize, &data_builder, &nonce)?;
let (_, nonce): (Order, String) = http::get_obj(root_certs, &order.finalize, &data_builder, &nonce)?;
// 12. Pool the order in order to see whether or not it is valid // 12. Pool the order in order to see whether or not it is valid
let data_builder = set_empty_data_builder!(account, order_url); let data_builder = set_empty_data_builder!(account, order_url);
let break_fn = |o: &Order| o.status == OrderStatus::Valid; let break_fn = |o: &Order| o.status == OrderStatus::Valid;
let (order, nonce): (Order, String) = let (order, nonce): (Order, String) =
http::pool_obj(&order_url, &data_builder, &break_fn, &nonce)?;
http::pool_obj(root_certs, &order_url, &data_builder, &break_fn, &nonce)?;
// 13. Download the certificate // 13. Download the certificate
let crt_url = order let crt_url = order
.certificate .certificate
.ok_or_else(|| Error::from("No certificate available for download."))?; .ok_or_else(|| Error::from("No certificate available for download."))?;
let data_builder = set_empty_data_builder!(account, crt_url); let data_builder = set_empty_data_builder!(account, crt_url);
let (crt, _) = http::get_certificate(&crt_url, &data_builder, &nonce)?;
let (crt, _) = http::get_certificate(root_certs, &crt_url, &data_builder, &nonce)?;
storage::write_certificate(cert, &crt.as_bytes())?; storage::write_certificate(cert, &crt.as_bytes())?;
info!("Certificate renewed for {}", cert.domains.join(", ")); info!("Certificate renewed for {}", cert.domains.join(", "));

3
acmed/src/acme_proto/account.rs

@ -20,6 +20,7 @@ impl AccountManager {
cert: &Certificate, cert: &Certificate,
directory: &Directory, directory: &Directory,
nonce: &str, nonce: &str,
root_certs: &[String],
) -> Result<(Self, String), Error> { ) -> Result<(Self, String), Error> {
// TODO: store the key id (account url) // TODO: store the key id (account url)
let (priv_key, pub_key) = if storage::account_files_exists(cert) { let (priv_key, pub_key) = if storage::account_files_exists(cert) {
@ -42,7 +43,7 @@ impl AccountManager {
let data_builder = let data_builder =
|n: &str| encode_jwk(&priv_key, account.as_bytes(), &directory.new_account, n); |n: &str| encode_jwk(&priv_key, account.as_bytes(), &directory.new_account, n);
let (acc_rep, account_url, nonce): (AccountResponse, String, String) = let (acc_rep, account_url, nonce): (AccountResponse, String, String) =
http::get_obj_loc(&directory.new_account, &data_builder, &nonce)?;
http::get_obj_loc(root_certs, &directory.new_account, &data_builder, &nonce)?;
let ac = AccountManager { let ac = AccountManager {
priv_key, priv_key,
pub_key, pub_key,

42
acmed/src/acme_proto/http.rs

@ -6,6 +6,7 @@ use http_req::uri::Uri;
use log::{debug, trace, warn}; use log::{debug, trace, warn};
use std::str::FromStr; use std::str::FromStr;
use std::{thread, time}; use std::{thread, time};
use std::path::Path;
const CONTENT_TYPE_JOSE: &str = "application/jose+json"; const CONTENT_TYPE_JOSE: &str = "application/jose+json";
const CONTENT_TYPE_JSON: &str = "application/json"; const CONTENT_TYPE_JSON: &str = "application/json";
@ -24,7 +25,7 @@ impl FromStr for DummyString {
} }
} }
fn new_request(uri: &Uri, method: Method) -> Request {
fn new_request<'a>(root_certs: &'a [String], uri: &'a Uri, method: Method) -> Request<'a> {
debug!("{}: {}", method, uri); debug!("{}: {}", method, uri);
let useragent = format!( let useragent = format!(
"{}/{} ({}) {}", "{}/{} ({}) {}",
@ -34,6 +35,9 @@ fn new_request(uri: &Uri, method: Method) -> Request {
env!("ACMED_HTTP_LIB_AGENT") env!("ACMED_HTTP_LIB_AGENT")
); );
let mut rb = Request::new(uri); let mut rb = Request::new(uri);
for file_name in root_certs.iter() {
rb.root_cert_file_pem(&Path::new(file_name));
}
rb.method(method); rb.method(method);
rb.header("User-Agent", &useragent); rb.header("User-Agent", &useragent);
// TODO: allow to configure the language // TODO: allow to configure the language
@ -93,9 +97,9 @@ fn nonce_from_response(res: &Response) -> Result<String, Error> {
} }
} }
fn post_jose_type(url: &str, data: &[u8], accept_type: &str) -> Result<(Response, String), Error> {
fn post_jose_type(root_certs: &[String], url: &str, data: &[u8], accept_type: &str) -> Result<(Response, String), Error> {
let uri = url.parse::<Uri>()?; let uri = url.parse::<Uri>()?;
let mut request = new_request(&uri, Method::POST);
let mut request = new_request(root_certs, &uri, Method::POST);
request.header("Content-Type", CONTENT_TYPE_JOSE); request.header("Content-Type", CONTENT_TYPE_JOSE);
request.header("Content-Length", &data.len().to_string()); request.header("Content-Length", &data.len().to_string());
request.header("Accept", accept_type); request.header("Accept", accept_type);
@ -116,6 +120,7 @@ fn check_response(res: &Response, body: &str) -> Result<(), AcmeError> {
} }
fn fetch_obj_type<T, G>( fn fetch_obj_type<T, G>(
root_certs: &[String],
url: &str, url: &str,
data_builder: &G, data_builder: &G,
nonce: &str, nonce: &str,
@ -128,7 +133,7 @@ where
let mut nonce = nonce.to_string(); let mut nonce = nonce.to_string();
for _ in 0..crate::DEFAULT_HTTP_FAIL_NB_RETRY { for _ in 0..crate::DEFAULT_HTTP_FAIL_NB_RETRY {
let data = data_builder(&nonce)?; let data = data_builder(&nonce)?;
let (res, res_body) = post_jose_type(url, data.as_bytes(), accept_type)?;
let (res, res_body) = post_jose_type(root_certs, url, data.as_bytes(), accept_type)?;
nonce = nonce_from_response(&res)?; nonce = nonce_from_response(&res)?;
match check_response(&res, &res_body) { match check_response(&res, &res_body) {
@ -150,15 +155,16 @@ where
Err("Too much errors, will not retry".into()) Err("Too much errors, will not retry".into())
} }
fn fetch_obj<T, G>(url: &str, data_builder: &G, nonce: &str) -> Result<(T, String, String), Error>
fn fetch_obj<T, G>(root_certs: &[String], url: &str, data_builder: &G, nonce: &str) -> Result<(T, String, String), Error>
where where
T: std::str::FromStr<Err = Error>, T: std::str::FromStr<Err = Error>,
G: Fn(&str) -> Result<String, Error>, G: Fn(&str) -> Result<String, Error>,
{ {
fetch_obj_type(url, data_builder, nonce, CONTENT_TYPE_JSON)
fetch_obj_type(root_certs, url, data_builder, nonce, CONTENT_TYPE_JSON)
} }
pub fn get_obj_loc<T, G>( pub fn get_obj_loc<T, G>(
root_certs: &[String],
url: &str, url: &str,
data_builder: &G, data_builder: &G,
nonce: &str, nonce: &str,
@ -167,7 +173,7 @@ where
T: std::str::FromStr<Err = Error>, T: std::str::FromStr<Err = Error>,
G: Fn(&str) -> Result<String, Error>, G: Fn(&str) -> Result<String, Error>,
{ {
let (obj, location, nonce) = fetch_obj(url, data_builder, nonce)?;
let (obj, location, nonce) = fetch_obj(root_certs, url, data_builder, nonce)?;
if location.is_empty() { if location.is_empty() {
Err("Location header not found.".into()) Err("Location header not found.".into())
} else { } else {
@ -175,16 +181,17 @@ where
} }
} }
pub fn get_obj<T, G>(url: &str, data_builder: &G, nonce: &str) -> Result<(T, String), Error>
pub fn get_obj<T, G>(root_certs: &[String], url: &str, data_builder: &G, nonce: &str) -> Result<(T, String), Error>
where where
T: std::str::FromStr<Err = Error>, T: std::str::FromStr<Err = Error>,
G: Fn(&str) -> Result<String, Error>, G: Fn(&str) -> Result<String, Error>,
{ {
let (obj, _, nonce) = fetch_obj(url, data_builder, nonce)?;
let (obj, _, nonce) = fetch_obj(root_certs, url, data_builder, nonce)?;
Ok((obj, nonce)) Ok((obj, nonce))
} }
pub fn pool_obj<T, G, S>( pub fn pool_obj<T, G, S>(
root_certs: &[String],
url: &str, url: &str,
data_builder: &G, data_builder: &G,
break_fn: &S, break_fn: &S,
@ -198,7 +205,7 @@ where
let mut nonce: String = nonce.to_string(); let mut nonce: String = nonce.to_string();
for _ in 0..crate::DEFAULT_POOL_NB_TRIES { for _ in 0..crate::DEFAULT_POOL_NB_TRIES {
thread::sleep(time::Duration::from_secs(crate::DEFAULT_POOL_WAIT_SEC)); thread::sleep(time::Duration::from_secs(crate::DEFAULT_POOL_WAIT_SEC));
let (obj, _, new_nonce) = fetch_obj(url, data_builder, &nonce)?;
let (obj, _, new_nonce) = fetch_obj(root_certs, url, data_builder, &nonce)?;
if break_fn(&obj) { if break_fn(&obj) {
return Ok((obj, new_nonce)); return Ok((obj, new_nonce));
} }
@ -208,15 +215,16 @@ where
Err(msg.into()) Err(msg.into())
} }
pub fn post_challenge_response<G>(url: &str, data_builder: &G, nonce: &str) -> Result<String, Error>
pub fn post_challenge_response<G>(root_certs: &[String], url: &str, data_builder: &G, nonce: &str) -> Result<String, Error>
where where
G: Fn(&str) -> Result<String, Error>, G: Fn(&str) -> Result<String, Error>,
{ {
let (_, _, nonce): (DummyString, String, String) = fetch_obj(url, data_builder, nonce)?;
let (_, _, nonce): (DummyString, String, String) = fetch_obj(root_certs, url, data_builder, nonce)?;
Ok(nonce) Ok(nonce)
} }
pub fn get_certificate<G>( pub fn get_certificate<G>(
root_certs: &[String],
url: &str, url: &str,
data_builder: &G, data_builder: &G,
nonce: &str, nonce: &str,
@ -224,22 +232,22 @@ pub fn get_certificate<G>(
where where
G: Fn(&str) -> Result<String, Error>, G: Fn(&str) -> Result<String, Error>,
{ {
let (res_body, _, nonce): (DummyString, String, String) = fetch_obj(url, data_builder, nonce)?;
let (res_body, _, nonce): (DummyString, String, String) = fetch_obj(root_certs, url, data_builder, nonce)?;
Ok((res_body.content, nonce)) Ok((res_body.content, nonce))
} }
pub fn get_directory(url: &str) -> Result<Directory, Error> {
pub fn get_directory(root_certs: &[String], url: &str) -> Result<Directory, Error> {
let uri = url.parse::<Uri>()?; let uri = url.parse::<Uri>()?;
let mut request = new_request(&uri, Method::GET);
let mut request = new_request(root_certs, &uri, Method::GET);
request.header("Accept", CONTENT_TYPE_JSON); request.header("Accept", CONTENT_TYPE_JSON);
let (r, s) = send_request_retry(&request)?; let (r, s) = send_request_retry(&request)?;
check_response(&r, &s)?; check_response(&r, &s)?;
Directory::from_str(&s) Directory::from_str(&s)
} }
pub fn get_nonce(url: &str) -> Result<String, Error> {
pub fn get_nonce(root_certs: &[String], url: &str) -> Result<String, Error> {
let uri = url.parse::<Uri>()?; let uri = url.parse::<Uri>()?;
let request = new_request(&uri, Method::HEAD);
let request = new_request(root_certs, &uri, Method::HEAD);
let (res, res_body) = send_request_retry(&request)?; let (res, res_body) = send_request_retry(&request)?;
check_response(&res, &res_body)?; check_response(&res, &res_body)?;
nonce_from_response(&res) nonce_from_response(&res)

15
acmed/src/main.rs

@ -75,6 +75,14 @@ fn main() {
.value_name("FILE") .value_name("FILE")
.conflicts_with("foregroung"), .conflicts_with("foregroung"),
) )
.arg(
Arg::with_name("root-cert")
.long("root-cert")
.help("Add a root certificate to the trust store")
.takes_value(true)
.multiple(true)
.value_name("FILE")
)
.get_matches(); .get_matches();
match acme_common::logs::set_log_system( match acme_common::logs::set_log_system(
@ -89,13 +97,18 @@ fn main() {
} }
}; };
let root_certs = match matches.values_of("root-cert") {
Some(v) => v.collect(),
None => vec![],
};
init_server( init_server(
matches.is_present("foregroung"), matches.is_present("foregroung"),
matches.value_of("pid-file").unwrap_or(DEFAULT_PID_FILE), matches.value_of("pid-file").unwrap_or(DEFAULT_PID_FILE),
); );
let config_file = matches.value_of("config").unwrap_or(DEFAULT_CONFIG_FILE); let config_file = matches.value_of("config").unwrap_or(DEFAULT_CONFIG_FILE);
let mut srv = match MainEventLoop::new(&config_file) {
let mut srv = match MainEventLoop::new(&config_file, &root_certs) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
error!("{}", e); error!("{}", e);

10
acmed/src/main_event_loop.rs

@ -8,10 +8,11 @@ use std::time::Duration;
pub struct MainEventLoop { pub struct MainEventLoop {
certs: Vec<Certificate>, certs: Vec<Certificate>,
root_certs: Vec<String>,
} }
impl MainEventLoop { impl MainEventLoop {
pub fn new(config_file: &str) -> Result<Self, Error> {
pub fn new(config_file: &str, root_certs: &[&str]) -> Result<Self, Error> {
let cnf = config::from_file(config_file)?; let cnf = config::from_file(config_file)?;
let mut certs = Vec::new(); let mut certs = Vec::new();
@ -44,7 +45,10 @@ impl MainEventLoop {
certs.push(cert); certs.push(cert);
} }
Ok(MainEventLoop { certs })
Ok(MainEventLoop {
certs,
root_certs: root_certs.iter().map(|v| v.to_string()).collect(),
})
} }
pub fn run(&mut self) { pub fn run(&mut self) {
@ -54,7 +58,7 @@ impl MainEventLoop {
match crt.should_renew() { match crt.should_renew() {
Ok(sr) => { Ok(sr) => {
if sr { if sr {
let status = match request_certificate(crt) {
let status = match request_certificate(crt, &self.root_certs) {
Ok(_) => "Success.".to_string(), Ok(_) => "Success.".to_string(),
Err(e) => { Err(e) => {
let msg = format!( let msg = format!(

Loading…
Cancel
Save