diff --git a/CHANGELOG.md b/CHANGELOG.md index e32be96..fa1612b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - System users and groups can now be specified by name in addition to uid/gid. +### Changed +- The HTTP(S) part is now handled by `attohttpc` instead of `reqwest`. + ## [0.8.0] - 2020-06-12 diff --git a/acme_common/Cargo.toml b/acme_common/Cargo.toml index 4015097..4d9fcbb 100644 --- a/acme_common/Cargo.toml +++ b/acme_common/Cargo.toml @@ -13,14 +13,15 @@ publish = false name = "acme_common" [dependencies] +attohttpc = { version = "0.15", default-features = false } base64 = "0.12" daemonize = "0.4" env_logger = "0.7" handlebars = "3.0" log = "0.4" +native-tls = "0.2" openssl = "0.10" punycode = "0.4" -reqwest = { version = "0.10", features = ["blocking", "default-tls"] } serde_json = "1.0" syslog = "5.0" toml = "0.5" diff --git a/acme_common/src/crypto/openssl_certificate.rs b/acme_common/src/crypto/openssl_certificate.rs index 16e7895..b061a31 100644 --- a/acme_common/src/crypto/openssl_certificate.rs +++ b/acme_common/src/crypto/openssl_certificate.rs @@ -58,6 +58,10 @@ impl X509Certificate { }) } + pub fn from_pem_native(pem_data: &[u8]) -> Result { + Ok(native_tls::Certificate::from_pem(pem_data)?) + } + pub fn from_acme_ext(domain: &str, acme_ext: &str) -> Result<(KeyPair, Self), Error> { let key_pair = gen_keypair(KeyType::EcdsaP256)?; let inner_cert = gen_certificate(domain, &key_pair, acme_ext)?; diff --git a/acme_common/src/error.rs b/acme_common/src/error.rs index ea2d652..03574fa 100644 --- a/acme_common/src/error.rs +++ b/acme_common/src/error.rs @@ -81,33 +81,27 @@ impl From for Error { } } +impl From for Error { + fn from(error: attohttpc::Error) -> Self { + format!("HTTP error: {}", error).into() + } +} + impl From for Error { fn from(error: handlebars::TemplateRenderError) -> Self { format!("Template error: {}", error).into() } } -impl From for Error { - fn from(error: openssl::error::ErrorStack) -> Self { +impl From for Error { + fn from(error: native_tls::Error) -> Self { format!("{}", error).into() } } -impl From for Error { - fn from(error: reqwest::Error) -> Self { - format!("HTTP error: {}", error).into() - } -} - -impl From for Error { - fn from(error: reqwest::header::InvalidHeaderName) -> Self { - format!("Invalid HTTP header name: {}", error).into() - } -} - -impl From for Error { - fn from(error: reqwest::header::InvalidHeaderValue) -> Self { - format!("Invalid HTTP header value: {}", error).into() +impl From for Error { + fn from(error: openssl::error::ErrorStack) -> Self { + format!("{}", error).into() } } diff --git a/acmed/Cargo.toml b/acmed/Cargo.toml index a52a090..04446a2 100644 --- a/acmed/Cargo.toml +++ b/acmed/Cargo.toml @@ -14,12 +14,12 @@ publish = false [dependencies] acme_common = { path = "../acme_common" } +attohttpc = { version = "0.15", features = ["charsets", "json"] } clap = "2.32" handlebars = "3.0" log = "0.4" nom = "5.0" openssl-sys = "0.9" -reqwest = { version = "0.10", features = ["blocking", "default-tls", "json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.5" diff --git a/acmed/build.rs b/acmed/build.rs index bd3a2f1..9a2715a 100644 --- a/acmed/build.rs +++ b/acmed/build.rs @@ -58,7 +58,7 @@ fn set_lock() { } }; for p in lock.package.iter() { - if p.name == "reqwest" { + 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); diff --git a/acmed/src/http.rs b/acmed/src/http.rs index ccedc98..6fb1e33 100644 --- a/acmed/src/http.rs +++ b/acmed/src/http.rs @@ -1,8 +1,8 @@ use crate::acme_proto::structs::HttpApiError; use crate::endpoint::Endpoint; +use acme_common::crypto::X509Certificate; use acme_common::error::Error; -use reqwest::blocking::{Client, Response}; -use reqwest::header::{self, HeaderMap, HeaderValue}; +use attohttpc::{charsets, header, Response, Session}; use std::fs::File; use std::io::prelude::*; use std::{thread, time}; @@ -40,12 +40,9 @@ fn update_nonce(endpoint: &mut Endpoint, response: &Response) -> Result<(), Erro } fn check_status(response: &Response) -> Result<(), Error> { - let status = response.status(); - if !status.is_success() { - let msg = status - .canonical_reason() - .unwrap_or(""); - let msg = format!("HTTP error: {}: {}", status.as_u16(), msg); + if !response.is_success() { + let status = response.status(); + let msg = format!("HTTP error: {}: {}", status.as_u16(), status.as_str()); return Err(msg.into()); } Ok(()) @@ -55,14 +52,14 @@ fn rate_limit(endpoint: &mut Endpoint) { endpoint.rl.block_until_allowed(); } -pub fn header_to_string(header_value: &HeaderValue) -> Result { +pub fn header_to_string(header_value: &header::HeaderValue) -> Result { let s = header_value .to_str() .map_err(|_| Error::from("Invalid nonce format."))?; Ok(s.to_string()) } -fn get_client(root_certs: &[String]) -> Result { +fn get_session(root_certs: &[String]) -> Result { let useragent = format!( "{}/{} ({}) {}", crate::APP_NAME, @@ -70,31 +67,25 @@ fn get_client(root_certs: &[String]) -> Result { env!("ACMED_TARGET"), env!("ACMED_HTTP_LIB_AGENT") ); - let mut headers = HeaderMap::with_capacity(2); // TODO: allow to change the language - headers.insert( - header::ACCEPT_LANGUAGE, - HeaderValue::from_static("en-US,en;q=0.5"), - ); - headers.insert(header::USER_AGENT, HeaderValue::from_str(&useragent)?); - let mut client_builder = Client::builder().default_headers(headers).referer(false); + 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() { let mut buff = Vec::new(); File::open(crt_file)?.read_to_end(&mut buff)?; - let crt = reqwest::Certificate::from_pem(&buff)?; - client_builder = client_builder.add_root_certificate(crt); + let crt = X509Certificate::from_pem_native(&buff)?; + session.add_root_certificate(crt); } - let client = client_builder.build()?; - Ok(client) + Ok(session) } pub fn get(endpoint: &mut Endpoint, root_certs: &[String], url: &str) -> Result { - let client = get_client(root_certs)?; + let mut session = get_session(root_certs)?; + session.try_header(header::ACCEPT, CONTENT_TYPE_JSON)?; rate_limit(endpoint); - let response = client - .get(url) - .header(header::ACCEPT, HeaderValue::from_static(CONTENT_TYPE_JSON)) - .send()?; + let response = session.get(url).send()?; update_nonce(endpoint, &response)?; check_status(&response)?; Ok(response) @@ -111,20 +102,17 @@ pub fn post( where F: Fn(&str, &str) -> Result, { - let client = get_client(root_certs)?; + let mut session = get_session(root_certs)?; + session.try_header(header::ACCEPT, accept)?; + session.try_header(header::CONTENT_TYPE, content_type)?; if endpoint.nonce.is_none() { let _ = new_nonce(endpoint, root_certs); } for _ in 0..crate::DEFAULT_HTTP_FAIL_NB_RETRY { let nonce = &endpoint.nonce.clone().unwrap(); - let body = data_builder(&nonce, url)?.into_bytes(); + let body = data_builder(&nonce, url)?; rate_limit(endpoint); - let response = client - .post(url) - .body(body) - .header(header::ACCEPT, HeaderValue::from_str(accept)?) - .header(header::CONTENT_TYPE, HeaderValue::from_str(content_type)?) - .send()?; + let response = session.post(url).text(&body).send()?; update_nonce(endpoint, &response)?; match check_status(&response) { Ok(_) => {