Replace native-tls with rust-tls, switch to PEM format

This commit is contained in:
Frank Denis 2020-02-01 20:46:36 +01:00
parent 4914572894
commit 16cb57c1e1
10 changed files with 137 additions and 62 deletions

View file

@ -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"] }

View file

@ -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 <err_ttl> TTL for errors, in seconds [default: 2]
-l, --listen-address <listen_address> Address to listen to [default: 127.0.0.1:3000]
-b, --local-bind-address <local_bind_address> Address to connect from [default: 0.0.0.0:0]
-b, --local-bind-address <local_bind_address> Address to connect from
-c, --max-clients <max_clients> Maximum number of simultaneous clients [default: 512]
-X, --max-ttl <max_ttl> Maximum TTL, in seconds [default: 604800]
-T, --min-ttl <min_ttl> Minimum TTL, in seconds [default: 10]
-p, --path <path> URI path [default: /dns-query]
-u, --server-address <server_address> Address to connect to [default: 9.9.9.9:53]
-t, --timeout <timeout> Timeout, in seconds [default: 10]
-I, --tls-cert-password <tls_cert_password>
Password for the PKCS12-encoded identity (only required for built-in TLS)
-I, --tls-cert-key-path <tls_cert_key_path>
Path to the PEM-encoded secret keys (only required for built-in TLS)
-i, --tls-cert-path <tls_cert_path> Path to a PKCS12-encoded identity (only required for built-in TLS)
-i, --tls-cert-path <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

Binary file not shown.

47
localhost.pem Normal file
View file

@ -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-----

View file

@ -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);
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "libdoh"
version = "0.2.2"
version = "0.3.0"
authors = ["Frank Denis <github@pureftpd.org>"]
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

View file

@ -13,7 +13,7 @@ pub struct Globals {
pub tls_cert_path: Option<PathBuf>,
#[cfg(feature = "tls")]
pub tls_cert_password: Option<String>,
pub tls_cert_key_path: Option<PathBuf>,
pub listen_address: SocketAddr,
pub local_bind_address: SocketAddr,

View file

@ -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,
};

View file

@ -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<P>(path: P, password: &str) -> io::Result<TlsAcceptor>
pub fn create_tls_acceptor<P, P2>(certs_path: P, certs_keys_path: P2) -> io::Result<TlsAcceptor>
where
P: AsRef<Path>,
P2: AsRef<Path>,
{
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 {

View file

@ -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),