Skip to content

Commit

Permalink
Automatic certificate generation in examples
Browse files Browse the repository at this point in the history
  • Loading branch information
Ralith authored and djc committed Jan 10, 2019
1 parent 08a3201 commit 06474e9
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 32 deletions.
49 changes: 31 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,39 @@ Quinn was created and is maintained by Dirkjan Ochtman and Benjamin Saunders.
- [x] Session resumption
- [ ] HTTP over QUIC

## How to start

The server [currently always requires][insecure] certificates to be supplied.
Example certificates are included in the repository for test purposes.
The client must be configured to trust the test certificate authority unless the
client is built with the `dangerous_configuration` feature and passed
`--accept-insecure-certs`.
## Certificates

By default, Quinn clients validate the cryptographic identity of servers they
connect to. This prevents an active, on-path attacker from intercepting
messages, but requires trusting some certificate authority. For many purposes,
this can be accomplished by using certificates from [Let's Encrypt][letsencrypt]
for servers, and invoking
`ClientConfigBuilder::add_default_certificate_authorities` on clients.

For some cases, including peer-to-peer, trust-on-first-use, deliberately
insecure applications, or any case where servers are not identified by domain
name, this isn't practical. Arbitrary certificate validation logic can be
implemented by enabling the `dangerous_configuration` feature of `rustls` and
constructing a quinn `ClientConfig` with an overridden certificate verifier by
hand.

When operating your own certificate authority doesn't make sense, [rcgen][rcgen]
can be used to generate self-signed certificates on demand. To support
trust-on-first-use, servers that automatically generate self-signed certificates
should write their generated certificate to persistent storage and reuse it on
future runs.

## Running the Examples

```sh
$ cargo run --example server -- --cert ./certs/server.chain --key ./certs/server.rsa ./
$ cargo run --example client -- --ca ./certs/ca.der https://localhost:4433/Cargo.toml
$ cargo run --example server ./
$ cargo run --example client https://localhost:4433/Cargo.toml
```

In the above example, the server will run on localhost and serve the "." folder to
the client. The client will request the "Cargo.toml" file.

To run the example client/server across a network you need to update the
`certs/openssl.cnf` file and change the `DNS.3` entry to suit the DNS name of the
server, and then regenerate the certificates using the `certs/generate.sh` script.
For real-world use, a certificate signed by a legitimate CA is recommended when
possible.
This launches a HTTP 0.9 server on the loopback address serving the current
working directory, with the client fetching `./Cargo.toml`. By default, the
server generates a self-signed certificate and stores it to disk, where the
client will automatically find and trust it.

## Development

Expand All @@ -83,4 +95,5 @@ the variable.
[slides]: https://dirkjan.ochtman.nl/files/quic-future-in-rust.pdf
[animation]: https://dirkjan.ochtman.nl/files/head-of-line-blocking.html
[youtube]: https://www.youtube.com/watch?v=EHgyY5DNdvI
[insecure]: https://github.com/djc/quinn/issues/58
[letsencrypt]: https://letsencrypt.org/
[rcgen]: https://crates.io/crates/rcgen
1 change: 1 addition & 0 deletions quinn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ tokio = "0.1.6"
tokio-current-thread = "0.1"
url = "1.7"
rcgen = "0.1"
directories = "1.0.2"

[[example]]
name = "server"
Expand Down
13 changes: 13 additions & 0 deletions quinn/examples/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ fn run(log: Logger, options: Opt) -> Result<()> {
if let Some(ca_path) = options.ca {
client_config
.add_certificate_authority(quinn::Certificate::from_der(&fs::read(&ca_path)?)?)?;
} else {
let dirs = directories::ProjectDirs::from("org", "quinn", "quinn-examples").unwrap();
match fs::read(dirs.data_local_dir().join("cert.der")) {
Ok(cert) => {
client_config.add_certificate_authority(quinn::Certificate::from_der(&cert)?)?;
}
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
info!(log, "local server certificate not found");
}
Err(e) => {
error!(log, "failed to open local server certificate: {}", e);
}
}
}

endpoint.default_client_config(client_config.build());
Expand Down
51 changes: 37 additions & 14 deletions quinn/examples/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@ extern crate failure;
#[macro_use]
extern crate slog;

use std::ascii;
use std::fmt;
use std::fs;
use std::net::SocketAddr;
use std::path::{self, Path, PathBuf};
use std::rc::Rc;
use std::str;
use std::{ascii, fmt, fs, io, str};

use failure::{Error, Fail, ResultExt};
use futures::{Future, Stream};
Expand Down Expand Up @@ -53,16 +50,16 @@ struct Opt {
#[structopt(parse(from_os_str))]
root: PathBuf,
/// TLS private key in PEM format
#[structopt(parse(from_os_str), short = "k", long = "key")]
key: PathBuf,
#[structopt(parse(from_os_str), short = "k", long = "key", requires = "cert")]
key: Option<PathBuf>,
/// TLS certificate in PEM format
#[structopt(parse(from_os_str), short = "c", long = "cert")]
cert: PathBuf,
#[structopt(parse(from_os_str), short = "c", long = "cert", requires = "key")]
cert: Option<PathBuf>,
/// Enable stateless retries
#[structopt(long = "stateless-retry")]
stateless_retry: bool,
/// Address to listen on
#[structopt(long = "listen", default_value = "[::]:4433")]
#[structopt(long = "listen", default_value = "[::1]:4433")]
listen: SocketAddr,
}

Expand Down Expand Up @@ -96,11 +93,37 @@ fn run(log: Logger, options: Opt) -> Result<()> {
server_config.use_stateless_retry(true);
}

let key = fs::read(&options.key).context("failed to read private key")?;
let key = quinn::PrivateKey::from_pem(&key)?;
let cert_chain = fs::read(&options.cert).context("failed to read certificate chain")?;
let cert_chain = quinn::CertificateChain::from_pem(&cert_chain)?;
server_config.set_certificate(cert_chain, key)?;
if let (Some(ref key), Some(ref cert)) = (options.key, options.cert) {
let key = fs::read(key).context("failed to read private key")?;
let key = quinn::PrivateKey::from_pem(&key)?;
let cert_chain = fs::read(cert).context("failed to read certificate chain")?;
let cert_chain = quinn::CertificateChain::from_pem(&cert_chain)?;
server_config.set_certificate(cert_chain, key)?;
} else {
let dirs = directories::ProjectDirs::from("org", "quinn", "quinn-examples").unwrap();
let path = dirs.data_local_dir();
let cert_path = path.join("cert.der");
let key_path = path.join("key.der");
let (cert, key) = match fs::read(&cert_path).and_then(|x| Ok((x, fs::read(&key_path)?))) {
Ok(x) => x,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
info!(log, "generating self-signed certificates");
let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]);
let key = cert.serialize_private_key_der();
let cert = cert.serialize_der();
fs::create_dir_all(&path).context("failed to create certificate directory")?;
fs::write(&cert_path, &cert).context("failed to write certificate")?;
fs::write(&key_path, &key).context("failed to write private key")?;
(cert, key)
}
Err(e) => {
bail!("failed to read certificate: {}", e);
}
};
let key = quinn::PrivateKey::from_der(&key)?;
let cert = quinn::Certificate::from_der(&cert)?;
server_config.set_certificate(quinn::CertificateChain::from_certs(vec![cert]), key)?;
}

let mut endpoint = quinn::Endpoint::new();
endpoint.logger(log.clone());
Expand Down

0 comments on commit 06474e9

Please sign in to comment.