diff --git a/Cargo.toml b/Cargo.toml index 6c0acf0..719a73b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,11 @@ edition = "2018" readme = "README.md" [features] -default = [] +default = ["tls"] tls = ["libdoh/tls"] [dependencies] -libdoh = { path = "src/libdoh", version = "0.2" } +libdoh = { path = "src/libdoh", version = "0.3" } clap = "2" jemallocator = "0" tokio = { version = "0.2", features = ["rt-threaded", "time", "tcp", "udp", "stream"] } diff --git a/README.md b/README.md index 0791440..a4229de 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,12 @@ cargo install doh-proxy With built-in support for HTTPS (requires openssl-dev): ```sh -cargo install doh-proxy --features=tls +cargo install doh-proxy ``` ## Usage ```text -A DNS-over-HTTP server proxy - USAGE: doh-proxy [FLAGS] [OPTIONS] @@ -42,17 +40,17 @@ FLAGS: OPTIONS: -E, --err-ttl TTL for errors, in seconds [default: 2] -l, --listen-address Address to listen to [default: 127.0.0.1:3000] - -b, --local-bind-address Address to connect from [default: 0.0.0.0:0] + -b, --local-bind-address Address to connect from -c, --max-clients Maximum number of simultaneous clients [default: 512] -X, --max-ttl Maximum TTL, in seconds [default: 604800] -T, --min-ttl Minimum TTL, in seconds [default: 10] -p, --path URI path [default: /dns-query] -u, --server-address Address to connect to [default: 9.9.9.9:53] -t, --timeout Timeout, in seconds [default: 10] - -I, --tls-cert-password - Password for the PKCS12-encoded identity (only required for built-in TLS) + -I, --tls-cert-key-path + Path to the PEM-encoded secret keys (only required for built-in TLS) - -i, --tls-cert-path Path to a PKCS12-encoded identity (only required for built-in TLS) + -i, --tls-cert-path Path to a PEM-encoded identity (only required for built-in TLS) ``` ## HTTP/2 termination @@ -65,22 +63,14 @@ If `doh-proxy` and the HTTP/2 front-end run on the same host, using the HTTP pro If both are on distinct networks, such as when using a CDN, `doh-proxy` can handle HTTPS requests, provided that it was compiled with the `tls` feature. -The identity must be encoded in PKCS12 format. Given an existing certificate `cert.pem` and its secret key `cert.key`, this can be achieved using the `openssl` command-line tool: +The certificates and private keys must be encoded in PEM format. They can be stored in the same file. -```sh -openssl pkcs12 -export -out cert.p12 -in cert.pem -inkey cert.key -``` - -A password will be interactive asked for, but the `-passout` command-line option can be added to provide it non-interactively. - -Once done, check that the permissions on `cert.p12` are reasonable. - -In order to enable built-in HTTPS support, add the `--tls-cert-path` option to specify the location of the `cert.p12` file, as well as the password using `--tls-cert-password`. +In order to enable built-in HTTPS support, add the `--tls-cert-path` option to specify the location of the certificates file, as well as the private keys file using `--tls-cert-key-path`. Once HTTPS is enabled, HTTP connections will not be accepted. -A sample self-signed certificate [`localhost.p12`](https://github.com/jedisct1/rust-doh/raw/master/localhost.p12) can be used for testing. -The password is `test`. +A sample self-signed certificate [`localhost.pem`](https://github.com/jedisct1/rust-doh/raw/master/localhost.pem) can be used for testing. +The file also includes the private key. ## Accepting both DNSCrypt and DoH connections on port 443 diff --git a/localhost.p12 b/localhost.p12 deleted file mode 100644 index 4d93646..0000000 Binary files a/localhost.p12 and /dev/null differ diff --git a/localhost.pem b/localhost.pem new file mode 100644 index 0000000..fe5ad50 --- /dev/null +++ b/localhost.pem @@ -0,0 +1,47 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDb7g6EQhbfby97 +k4oMbZTzdi2TWFBs7qK/QwgOu+L6EhNHPO1ZEU29v0APFBFJO5zyyAk9bZ9k9tPB +bCuVVI9jEUfLH3UCjEQPG6XI2w++uVh0yALvc/uurCvRHVlle/V7cAoikndc2SjE +RQUALbACIqwD5g0F77BYwcsreB4GH253/R6Q2/CJZ4jNHPjkocOJiVr3ejA0kkoN +MXpGUXWcrVVk20M2A1CeO7HAulLRcklEdoHE3v46pjp0iZK0F9LyZX1U1ql+4QL3 +iQttoZ4tMg83lFHSt4G9PrpIhzXr9W4NW822faSvrIwwN/JbItUmRa7n/3+MkuJQ +IGGNDayXAgMBAAECggEBANs0fmGSocuXvYL1Pi4+9qxnCOwIpTi97Zam0BwnZwcL +Bw4FCyiwV4UdX1LoFIailT9i49rHLYzre4oZL6OKgdQjQCSTuQOOHLPWQbpdpWba +w/C5/jr+pkemMZIfJ6BAGiArPt7Qj4oKpFhj1qUj5H9sYXkNTcOx8Fm25rLv6TT9 +O7wg0oCpyG+iBSbCYBp9mDMz8pfo4P3BhcFiyKCKeiAC6KuHU81dvuKeFB4XQK+X +no2NqDqe6MBkmTqjNNy+wi1COR7lu34LPiWU5Hq5PdIEqBBUMjlMI6oYlhlgNTdx +SvsqFz3Xs6kpAhJTrSiAqscPYosgaMQxo+LI26PJnikCgYEA9n0OERkm0wSBHnHY +Kx8jaxNYg93jEzVnEgI/MBTJZqEyCs9fF6Imv737VawEN/BhesZZX7bGZQfDo8AT +aiSa5upkkSGXEqTu5ytyoKFTb+dJ/qmx3+zP6dPVzDnc8WPYMoUg7vvjZkXXJgZX ++oMlMUW1wWiDNI3wP19W9Is6xssCgYEA5GqkUBEns6eTFJV0JKqbEORJJ7lx5NZe +cIx+jPpLkILG4mOKOg1TBx0wkxa9cELtsNsM+bPtu9OqRMhsfPBmsXDHhJwg0Z6G +eDTfYYPkpRhwZvl6jBZn9sLVR9wfg2hE+n0lfV3mceg336KOkwAehDU84SWZ2e0S +esqkpbHJa+UCgYA7PY0O8POSzcdWkNf6bS5vAqRIdSCpMjGGc4HKRYSuJNnJHVPm +czNK7Bcm3QPaiexzvI4oYd5G09niVjyUSx3rl7P56Y/MjFVau+d90agjAfyXtyMo +BVtnAGGnBtUiMvP4GGT06xcZMnnmCqpEbBaZQ/7N8Bdwnxh5sqlMdtX2hwKBgAhL +hyQRO2vezgyVUN50A6WdZLq4lVZGIq/bqkzcWhopZaebDc4F5doASV9OGBsXkyI1 +EkePLTcA/NH6pVX0NQaEnfpG4To7k46R/PrBm3ATbyGONdEYjzX65VvytoJDKx4d +pVrkKhZA5KaOdLcJ7hHHDSrv/qJXZbBn44rQ5guxAoGBAJ6oeUsUUETakxlmIhmK +xuQmWqLf97BKt8r6Z8CqHKWK7vpG2OmgFYCQGaR7angQ8hmAOv6jM56XhoagDBoc +UoaoEyo9/uCk6NRUkUMj7Tk/5UQSiWLceVH27w+icMFhf1b7EmmNfk+APsiathO5 +j4edf1AinVCPwRVVu1dtLL5P +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDAjCCAeoCCQCptj0+TjjIJjANBgkqhkiG9w0BAQsFADBDMREwDwYDVQQKDAhE +TlNDcnlwdDEaMBgGA1UECwwRTG9jYWwgdGVzdCBzZXJ2ZXIxEjAQBgNVBAMMCWxv +Y2FsaG9zdDAeFw0xOTExMTgxNDA2MzBaFw0zMzA3MjcxNDA2MzBaMEMxETAPBgNV +BAoMCEROU0NyeXB0MRowGAYDVQQLDBFMb2NhbCB0ZXN0IHNlcnZlcjESMBAGA1UE +AwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2+4O +hEIW328ve5OKDG2U83Ytk1hQbO6iv0MIDrvi+hITRzztWRFNvb9ADxQRSTuc8sgJ +PW2fZPbTwWwrlVSPYxFHyx91AoxEDxulyNsPvrlYdMgC73P7rqwr0R1ZZXv1e3AK +IpJ3XNkoxEUFAC2wAiKsA+YNBe+wWMHLK3geBh9ud/0ekNvwiWeIzRz45KHDiYla +93owNJJKDTF6RlF1nK1VZNtDNgNQnjuxwLpS0XJJRHaBxN7+OqY6dImStBfS8mV9 +VNapfuEC94kLbaGeLTIPN5RR0reBvT66SIc16/VuDVvNtn2kr6yMMDfyWyLVJkWu +5/9/jJLiUCBhjQ2slwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA6Vz5HnGuy8jZz +5i8ipbcDMCZNdpYYnxgD53hEKOfoSv7LaF0ztD8Kmg3s5LHv9EHlkK3+G6FWRGiP +9f6IbtRITaiVQP3M13T78hpN5Qq5jgsqjR7ZcN7Etr6ZFd7G/0+mzqbyBuW/3szt +RdX/YLy1csvjbZoNNuXGWRohXjg0Mjko2tRLmARvxA/gZV5zWycv3BD2BPzyCdS9 +MDMYSF0RPiL8+alfwLNqLcqMA5liHlmZa85uapQyoUI3ksKJkEgU53aD8cYhH9Yn +6mVpsrvrcRLBiHlbi24QBolhFkCSRK8bXes8XDIPuD8iYRwlrVBwOakMFQWMqNfI +IMOKJomU +-----END CERTIFICATE----- diff --git a/src/config.rs b/src/config.rs index dbe0c3f..0fa46aa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -114,14 +114,14 @@ pub fn parse_opts(globals: &mut Globals) { .short("i") .long("tls-cert-path") .takes_value(true) - .help("Path to a PKCS12-encoded identity (only required for built-in TLS)"), + .help("Path to a PEM-encoded certificates (only required for built-in TLS)"), ) .arg( - Arg::with_name("tls_cert_password") + Arg::with_name("tls_cert_key_path") .short("I") - .long("tls-cert-password") + .long("tls-cert-key-path") .takes_value(true) - .help("Password for the PKCS12-encoded identity (only required for built-in TLS)"), + .help("Path to the PEM-encoded secret keys (only required for built-in TLS)"), ); let matches = options.get_matches(); @@ -160,9 +160,7 @@ pub fn parse_opts(globals: &mut Globals) { #[cfg(feature = "tls")] { - globals.tls_cert_path = matches.value_of("tls_cert_path").map(PathBuf::from); - globals.tls_cert_password = matches - .value_of("tls_cert_password") - .map(ToString::to_string); + globals.tls_cert_path = matches.value_of("tls_cert_key_path").map(PathBuf::from); + globals.tls_cert_key_path = matches.value_of("tls_cert_key_path").map(PathBuf::from); } } diff --git a/src/libdoh/Cargo.toml b/src/libdoh/Cargo.toml index 46cf5bc..0bdf923 100644 --- a/src/libdoh/Cargo.toml +++ b/src/libdoh/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libdoh" -version = "0.2.2" +version = "0.3.0" authors = ["Frank Denis "] description = "DoH library for the rust-doh app" keywords = ["dns","https","doh","proxy"] @@ -12,7 +12,7 @@ edition = "2018" [features] default = [] -tls = ["native-tls", "tokio-tls"] +tls = ["tokio-rustls"] [dependencies] anyhow = "1.0" @@ -20,9 +20,8 @@ byteorder = "1.3" base64 = "0.11" futures = "0.3" hyper = { version = "0.13", default-features = false, features = ["stream"] } -native-tls = { version = "0.2.3", optional = true } tokio = { version = "0.2", features = ["rt-threaded", "time", "tcp", "udp", "stream"] } -tokio-tls = { version = "0.3", optional = true } +tokio-rustls = { version = "0.12", optional = true } [profile.release] codegen-units = 1 diff --git a/src/libdoh/src/globals.rs b/src/libdoh/src/globals.rs index 7f998d2..196c820 100644 --- a/src/libdoh/src/globals.rs +++ b/src/libdoh/src/globals.rs @@ -13,7 +13,7 @@ pub struct Globals { pub tls_cert_path: Option, #[cfg(feature = "tls")] - pub tls_cert_password: Option, + pub tls_cert_key_path: Option, pub listen_address: SocketAddr, pub local_bind_address: SocketAddr, diff --git a/src/libdoh/src/lib.rs b/src/libdoh/src/lib.rs index bd862b6..ccd615e 100644 --- a/src/libdoh/src/lib.rs +++ b/src/libdoh/src/lib.rs @@ -266,9 +266,9 @@ impl DoH { let path = &self.globals.path; #[cfg(feature = "tls")] - let tls_acceptor = match (&self.globals.tls_cert_path, &self.globals.tls_cert_password) { - (Some(tls_cert_path), Some(tls_cert_password)) => { - Some(create_tls_acceptor(tls_cert_path, tls_cert_password).unwrap()) + let tls_acceptor = match (&self.globals.tls_cert_path, &self.globals.tls_cert_key_path) { + (Some(tls_cert_path), Some(tls_cert_key_path)) => { + Some(create_tls_acceptor(tls_cert_path, tls_cert_key_path).unwrap()) } _ => None, }; diff --git a/src/libdoh/src/tls.rs b/src/libdoh/src/tls.rs index b0159f9..2aee569 100644 --- a/src/libdoh/src/tls.rs +++ b/src/libdoh/src/tls.rs @@ -2,39 +2,80 @@ use crate::errors::*; use crate::{DoH, LocalExecutor}; use hyper::server::conn::Http; -use native_tls::{self, Identity}; use std::fs::File; -use std::io; -use std::io::Read; +use std::io::{self, BufReader}; use std::path::Path; -use tokio::stream::StreamExt; -pub use tokio_tls::TlsAcceptor; - +use std::sync::Arc; use tokio::net::TcpListener; +use tokio::stream::StreamExt; +use tokio_rustls::{ + rustls::{internal::pemfile, NoClientAuth, ServerConfig}, + TlsAcceptor, +}; -pub fn create_tls_acceptor

(path: P, password: &str) -> io::Result +pub fn create_tls_acceptor(certs_path: P, certs_keys_path: P2) -> io::Result where P: AsRef, + P2: AsRef, { - let identity_bin = { - let mut fp = File::open(path)?; - let mut identity_bin = vec![]; - fp.read_to_end(&mut identity_bin)?; - identity_bin + let certs = { + let certs_path_str = certs_path.as_ref().display().to_string(); + let mut reader = BufReader::new(File::open(certs_path).map_err(|e| { + io::Error::new( + e.kind(), + format!( + "Unable to load the certificates [{}]: {}", + certs_path_str, + e.to_string() + ), + ) + })?); + pemfile::certs(&mut reader).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidInput, + "Unable to parse the certificates", + ) + })? }; - let identity = Identity::from_pkcs12(&identity_bin, password).map_err(|_| { - io::Error::new( + let certs_keys = { + let certs_keys_path_str = certs_keys_path.as_ref().display().to_string(); + let mut reader = BufReader::new(File::open(certs_keys_path).map_err(|e| { + io::Error::new( + e.kind(), + format!( + "Unable to load the certificate keys [{}]: {}", + certs_keys_path_str, + e.to_string() + ), + ) + })?); + let keys = pemfile::pkcs8_private_keys(&mut reader).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidInput, + "Unable to parse the certificates private keys", + ) + })?; + if keys.is_empty() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "No private keys found", + )); + } + keys + }; + let mut server_config = ServerConfig::new(NoClientAuth::new()); + let has_valid_cert_and_key = certs_keys.into_iter().any(|certs_key| { + server_config + .set_single_cert(certs.clone(), certs_key) + .is_ok() + }); + if !has_valid_cert_and_key { + return Err(io::Error::new( io::ErrorKind::InvalidInput, - "Unusable PKCS12-encoded identity. The encoding and/or the password may be wrong", - ) - })?; - let native_acceptor = native_tls::TlsAcceptor::new(identity).map_err(|_| { - io::Error::new( - io::ErrorKind::InvalidInput, - "Unable to use the provided PKCS12-encoded identity", - ) - })?; - Ok(TlsAcceptor::from(native_acceptor)) + "Invalid private key for the given certificate", + )); + } + Ok(TlsAcceptor::from(Arc::new(server_config))) } impl DoH { diff --git a/src/main.rs b/src/main.rs index 92b1b86..efbf144 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ fn main() { #[cfg(feature = "tls")] tls_cert_path: None, #[cfg(feature = "tls")] - tls_cert_password: None, + tls_cert_key_path: None, listen_address: LISTEN_ADDRESS.parse().unwrap(), local_bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),