-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introducing the Boring crypto provider.
Also adding examples and basic documentation.
- Loading branch information
Showing
27 changed files
with
5,575 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,33 @@ | ||
# TODO | ||
[![codecov](https://codecov.io/gh/quinn-rs/quinn/branch/main/graph/badge.svg)](https://codecov.io/gh/quinn-rs/quinn-boring) | ||
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE-MIT) | ||
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE-APACHE) | ||
|
||
A crypto provider for [quinn](https://github.com/quinn-rs/quinn) based on [BoringSSL](https://github.com/google/boringssl). | ||
|
||
## Getting Started | ||
|
||
The [examples](examples) directory provides example client and server applications, which can be run as follows: | ||
|
||
```sh | ||
$ cargo run --example server ./ | ||
$ cargo run --example client https://localhost:4433/Cargo.toml | ||
``` | ||
|
||
This launches an 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. | ||
|
||
## Testing | ||
|
||
This repository relies on the [quinn_proto integration tests](https://github.com/quinn-rs/quinn/tree/main/quinn-proto/src/tests), | ||
which can be made to run with the BoringSSL provider. | ||
|
||
## FIPS | ||
|
||
The BoringSSL provider is based on the Cloudflare [Boring library](https://github.com/cloudflare/boring), which | ||
supports building against a FIPS-validated version of BoringSSL. | ||
|
||
## Authors | ||
|
||
* [Nathan Mittler](https://github.com/nmittler) - *Project owner* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
## HTTP/0.9 File Serving Example | ||
|
||
The examples in this directory were copied from [quinn](https://github.com/quinn-rs/quinn/tree/main/quinn/examples) | ||
and modified to use BoringSSL. | ||
|
||
The `server` and `client` examples demonstrate fetching files using a HTTP-like toy protocol. | ||
|
||
1. Server (`server.rs`) | ||
|
||
The server listens for any client requesting a file. | ||
If the file path is valid and allowed, it returns the contents. | ||
|
||
Open up a terminal and execute: | ||
|
||
```text | ||
$ cargo run --example server ./ | ||
``` | ||
|
||
2. Client (`client.rs`) | ||
|
||
The client requests a file and prints it to the console. | ||
If the file is on the server, it will receive the response. | ||
|
||
In a new terminal execute: | ||
|
||
```test | ||
$ cargo run --example client https://localhost:4433/Cargo.toml | ||
``` | ||
|
||
where `Cargo.toml` is any file in the directory passed to the server. | ||
|
||
**Result:** | ||
|
||
The output will be the contents of this README. | ||
|
||
**Troubleshooting:** | ||
|
||
If the client times out with no activity on the server, try forcing the server to run on IPv4 by | ||
running it with `cargo run --example server -- ./ --listen 127.0.0.1:4433`. The server listens on | ||
IPv6 by default, `localhost` tends to resolve to IPv4, and support for accepting IPv4 packets on | ||
IPv6 sockets varies between platforms. | ||
|
||
If the client prints `failed to process request: failed reading file`, the request was processed | ||
successfully but the path segment of the URL did not correspond to a file in the directory being | ||
served. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
//! This example demonstrates an HTTP client that requests files from a server. | ||
//! | ||
//! Checkout the `README.md` for guidance. | ||
|
||
use std::{ | ||
fs, | ||
io::{self, Write}, | ||
net::ToSocketAddrs, | ||
path::PathBuf, | ||
sync::Arc, | ||
time::{Duration, Instant}, | ||
}; | ||
|
||
use anyhow::{anyhow, Result}; | ||
use boring::x509::X509; | ||
use clap::Parser; | ||
use tracing::{error, info}; | ||
use url::Url; | ||
|
||
/// HTTP/0.9 over QUIC client | ||
#[derive(Parser, Debug)] | ||
#[clap(name = "client")] | ||
struct Opt { | ||
url: Url, | ||
|
||
/// Override hostname used for certificate verification | ||
#[clap(long = "host")] | ||
host: Option<String>, | ||
|
||
/// Custom certificate authority to trust, in DER format | ||
#[clap(parse(from_os_str), long = "ca")] | ||
ca: Option<PathBuf>, | ||
|
||
/// Simulate NAT rebinding after connecting | ||
#[clap(long = "rebind")] | ||
rebind: bool, | ||
} | ||
|
||
fn main() { | ||
tracing::subscriber::set_global_default( | ||
tracing_subscriber::FmtSubscriber::builder() | ||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) | ||
.finish(), | ||
) | ||
.unwrap(); | ||
let opt = Opt::parse(); | ||
let code = { | ||
if let Err(e) = run(opt) { | ||
eprintln!("ERROR: {}", e); | ||
1 | ||
} else { | ||
0 | ||
} | ||
}; | ||
std::process::exit(code); | ||
} | ||
|
||
#[tokio::main] | ||
async fn run(options: Opt) -> Result<()> { | ||
let url = options.url; | ||
let remote = (url.host_str().unwrap(), url.port().unwrap_or(4433)) | ||
.to_socket_addrs()? | ||
.next() | ||
.ok_or_else(|| anyhow!("couldn't resolve to an address"))?; | ||
|
||
let mut client_crypto = quinn_boring::ClientConfig::new()?; | ||
if let Some(ca_path) = options.ca { | ||
client_crypto.add_trusted_cert(X509::from_der(&fs::read(ca_path)?)?)?; | ||
} else { | ||
let dirs = directories_next::ProjectDirs::from("org", "quinn", "quinn-examples").unwrap(); | ||
let path = dirs.data_local_dir(); | ||
info!("using cert directory: {:?}", path); | ||
match fs::read(path.join("cert.der")) { | ||
Ok(cert) => { | ||
client_crypto.add_trusted_cert(X509::from_der(&cert)?)?; | ||
} | ||
Err(ref e) if e.kind() == io::ErrorKind::NotFound => { | ||
info!("local server certificate not found"); | ||
} | ||
Err(e) => { | ||
error!("failed to open local server certificate: {}", e); | ||
} | ||
} | ||
} | ||
|
||
let mut endpoint = quinn_boring::helpers::client_endpoint("[::]:0".parse().unwrap())?; | ||
endpoint.set_default_client_config(quinn::ClientConfig::new(Arc::new(client_crypto))); | ||
|
||
let request = format!("GET {}\r\n", url.path()); | ||
let start = Instant::now(); | ||
let rebind = options.rebind; | ||
let host = options | ||
.host | ||
.as_ref() | ||
.map_or_else(|| url.host_str(), |x| Some(x)) | ||
.ok_or_else(|| anyhow!("no hostname specified"))?; | ||
|
||
eprintln!("connecting to {} at {}", host, remote); | ||
let conn = endpoint | ||
.connect(remote, host)? | ||
.await | ||
.map_err(|e| anyhow!("failed to connect: {}", e))?; | ||
eprintln!("connected at {:?}", start.elapsed()); | ||
let (mut send, recv) = conn | ||
.open_bi() | ||
.await | ||
.map_err(|e| anyhow!("failed to open stream: {}", e))?; | ||
if rebind { | ||
let socket = std::net::UdpSocket::bind("[::]:0").unwrap(); | ||
let addr = socket.local_addr().unwrap(); | ||
eprintln!("rebinding to {}", addr); | ||
endpoint.rebind(socket).expect("rebind failed"); | ||
} | ||
|
||
send.write_all(request.as_bytes()) | ||
.await | ||
.map_err(|e| anyhow!("failed to send request: {}", e))?; | ||
send.finish() | ||
.await | ||
.map_err(|e| anyhow!("failed to shutdown stream: {}", e))?; | ||
let response_start = Instant::now(); | ||
eprintln!("request sent at {:?}", response_start - start); | ||
let resp = recv | ||
.read_to_end(usize::MAX) | ||
.await | ||
.map_err(|e| anyhow!("failed to read response: {}", e))?; | ||
let duration = response_start.elapsed(); | ||
eprintln!( | ||
"response received in {:?} - {} KiB/s", | ||
duration, | ||
resp.len() as f32 / (duration_secs(&duration) * 1024.0) | ||
); | ||
io::stdout().write_all(&resp).unwrap(); | ||
io::stdout().flush().unwrap(); | ||
conn.close(0u32.into(), b"done"); | ||
|
||
// Give the server a fair chance to receive the close packet | ||
endpoint.wait_idle().await; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn duration_secs(x: &Duration) -> f32 { | ||
x.as_secs() as f32 + x.subsec_nanos() as f32 * 1e-9 | ||
} |
Oops, something went wrong.