mirror of
https://github.com/n0-computer/noq.git
synced 2026-09-25 04:35:17 +00:00
docs: separate example code from document
separate certificate's examples out of md file into rs files format
This commit is contained in:
committed by
Dirkjan Ochtman
parent
f8165c3394
commit
41f7d2ea8f
Generated
+11
@@ -410,6 +410,17 @@ dependencies = [
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "book"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"quinn",
|
||||
"rcgen",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["quinn", "quinn-proto", "quinn-udp", "bench", "perf", "fuzz"]
|
||||
members = ["quinn", "quinn-proto", "quinn-udp", "bench", "perf", "fuzz", "docs/book"]
|
||||
default-members = ["quinn", "quinn-proto", "quinn-udp", "bench", "perf"]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "book"
|
||||
version = "0.1.0"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
quinn = { version = "0.11.7", path = "../../quinn" }
|
||||
rcgen.workspace = true
|
||||
rustls.workspace = true
|
||||
rustls-pemfile.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "certificate-insecure"
|
||||
path = "src/quinn/certificate-insecure.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "certificate-certsr"
|
||||
path = "src/quinn/certificate-certs.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "data-transfer"
|
||||
path = "src/quinn/data-transfer.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "set-up-connection"
|
||||
path = "src/quinn/set-up-connection.rs"
|
||||
@@ -0,0 +1,38 @@
|
||||
use std::error::Error;
|
||||
|
||||
use rustls::{client, pki_types::pem::PemObject};
|
||||
|
||||
fn read_certs_from_file() -> Result<
|
||||
(
|
||||
Vec<rustls::pki_types::CertificateDer<'static>>,
|
||||
rustls::pki_types::PrivateKeyDer<'static>,
|
||||
),
|
||||
Box<dyn Error>,
|
||||
> {
|
||||
let certs = rustls::pki_types::CertificateDer::pem_file_iter("./fullchain.pem")
|
||||
.unwrap()
|
||||
.map(|cert| cert.unwrap())
|
||||
.collect();
|
||||
let key = rustls::pki_types::PrivateKeyDer::from_pem_file("./privkey.pem").unwrap();
|
||||
Ok((certs, key))
|
||||
}
|
||||
|
||||
fn generate_self_signed_cert() -> Result<
|
||||
(
|
||||
rustls::pki_types::CertificateDer<'static>,
|
||||
rustls::pki_types::PrivatePkcs8KeyDer<'static>,
|
||||
),
|
||||
Box<dyn Error>,
|
||||
> {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])?;
|
||||
let cert_der = rustls::pki_types::CertificateDer::from(cert.cert);
|
||||
let key = rustls::pki_types::PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der());
|
||||
Ok((cert_der, key))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (self_signed_certs, self_signed_key) = generate_self_signed_cert().unwrap();
|
||||
let (certs, key) = read_certs_from_file().unwrap();
|
||||
let server_config = quinn::ServerConfig::with_single_cert(certs, key);
|
||||
let client_config = quinn::ClientConfig::with_platform_verifier();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use quinn::{
|
||||
ClientConfig,
|
||||
crypto::rustls::{NoInitialCipherSuite, QuicClientConfig},
|
||||
};
|
||||
|
||||
// Implementation of `ServerCertVerifier` that verifies everything as trustworthy.
|
||||
#[derive(Debug)]
|
||||
struct SkipServerVerification(Arc<rustls::crypto::CryptoProvider>);
|
||||
|
||||
impl SkipServerVerification {
|
||||
fn new() -> Arc<Self> {
|
||||
Arc::new(Self(Arc::new(rustls::crypto::ring::default_provider())))
|
||||
}
|
||||
}
|
||||
|
||||
impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &rustls::pki_types::CertificateDer<'_>,
|
||||
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
|
||||
_server_name: &rustls::pki_types::ServerName<'_>,
|
||||
_ocsp: &[u8],
|
||||
_now: rustls::pki_types::UnixTime,
|
||||
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
|
||||
Ok(rustls::client::danger::ServerCertVerified::assertion())
|
||||
}
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &rustls::pki_types::CertificateDer<'_>,
|
||||
dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||
rustls::crypto::verify_tls12_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&self.0.signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &rustls::pki_types::CertificateDer<'_>,
|
||||
dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||
rustls::crypto::verify_tls13_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&self.0.signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||
self.0.signature_verification_algorithms.supported_schemes()
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_client() -> Result<ClientConfig, NoInitialCipherSuite> {
|
||||
let crypto = rustls::ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(SkipServerVerification::new())
|
||||
.with_no_client_auth();
|
||||
|
||||
Ok(ClientConfig::new(Arc::new(QuicClientConfig::try_from(
|
||||
crypto,
|
||||
)?)))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let client_config = configure_client().unwrap();
|
||||
}
|
||||
@@ -19,41 +19,13 @@ rustls = { version = "*", features = ["dangerous_configuration", "quic"] }
|
||||
Then, allow the client to skip the certificate validation by implementing [ServerCertVerifier][ServerCertVerifier] and letting it assert verification for any server.
|
||||
|
||||
```rust
|
||||
// Implementation of `ServerCertVerifier` that verifies everything as trustworthy.
|
||||
struct SkipServerVerification;
|
||||
|
||||
impl SkipServerVerification {
|
||||
fn new() -> Arc<Self> {
|
||||
Arc::new(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl rustls::client::ServerCertVerifier for SkipServerVerification {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &rustls::Certificate,
|
||||
_intermediates: &[rustls::Certificate],
|
||||
_server_name: &rustls::ServerName,
|
||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: std::time::SystemTime,
|
||||
) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
|
||||
Ok(rustls::client::ServerCertVerified::assertion())
|
||||
}
|
||||
}
|
||||
{{#include certificate-insecure.rs:5:57}}
|
||||
```
|
||||
|
||||
After that, modify the [ClientConfig][ClientConfig] to use this [ServerCertVerifier][ServerCertVerifier] implementation.
|
||||
|
||||
```rust
|
||||
fn configure_client() -> ClientConfig {
|
||||
let crypto = rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_custom_certificate_verifier(SkipServerVerification::new())
|
||||
.with_no_client_auth();
|
||||
|
||||
ClientConfig::new(Arc::new(crypto))
|
||||
}
|
||||
{{#include certificate-insecure.rs:59:66}}
|
||||
```
|
||||
|
||||
Finally, if you plug this [ClientConfig][ClientConfig] into the [Endpoint::set_default_client_config()][set_default_client_config] your client endpoint should verify all connections as trustworthy.
|
||||
@@ -73,12 +45,7 @@ This example uses [rcgen][4] to generate a certificate.
|
||||
Let's look at an example:
|
||||
|
||||
```rust
|
||||
fn generate_self_signed_cert() -> Result<(rustls::Certificate, rustls::PrivateKey), Box<dyn Error>>
|
||||
{
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])?;
|
||||
let key = rustls::PrivateKey(cert.serialize_private_key_der());
|
||||
Ok((rustls::Certificate(cert.serialize_der()?), key))
|
||||
}
|
||||
{{#include certificate-certs.rs:14:19}}
|
||||
```
|
||||
|
||||
*Note that [generate_simple_self_signed][generate_simple_self_signed] returns a [Certificate][2] that can be serialized to both `.der` and `.pem` formats.*
|
||||
@@ -101,27 +68,7 @@ certbot asks for the required data and writes the certificates to `fullchain.pem
|
||||
These files can then be referenced in code.
|
||||
|
||||
```rust
|
||||
use std::{error::Error, fs::File, io::BufReader};
|
||||
|
||||
pub fn read_certs_from_file(
|
||||
) -> Result<(Vec<rustls::Certificate>, rustls::PrivateKey), Box<dyn Error>> {
|
||||
let mut cert_chain_reader = BufReader::new(File::open("./fullchain.pem")?);
|
||||
let certs = rustls_pemfile::certs(&mut cert_chain_reader)?
|
||||
.into_iter()
|
||||
.map(rustls::Certificate)
|
||||
.collect();
|
||||
|
||||
let mut key_reader = BufReader::new(File::open("./privkey.pem")?);
|
||||
// if the file starts with "BEGIN RSA PRIVATE KEY"
|
||||
// let mut keys = rustls_pemfile::rsa_private_keys(&mut key_reader)?;
|
||||
// if the file starts with "BEGIN PRIVATE KEY"
|
||||
let mut keys = rustls_pemfile::pkcs8_private_keys(&mut key_reader)?;
|
||||
|
||||
assert_eq!(keys.len(), 1);
|
||||
let key = rustls::PrivateKey(keys.remove(0));
|
||||
|
||||
Ok((certs, key))
|
||||
}
|
||||
{{#include certificate-certs.rs:5:12}}
|
||||
```
|
||||
|
||||
### Configuring Certificates
|
||||
@@ -132,7 +79,7 @@ After configuring plug the configuration into the `Endpoint`.
|
||||
**Configure Server**
|
||||
|
||||
```rust
|
||||
let server_config = ServerConfig::with_single_cert(certs, key)?;
|
||||
{{#include certificate-certs.rs:24}}
|
||||
```
|
||||
|
||||
This is the only thing you need to do for your server to be secured.
|
||||
@@ -140,7 +87,7 @@ This is the only thing you need to do for your server to be secured.
|
||||
**Configure Client**
|
||||
|
||||
```rust
|
||||
let client_config = ClientConfig::with_native_roots();
|
||||
{{#include certificate-certs.rs:25}}
|
||||
```
|
||||
|
||||
This is the only thing you need to do for your client to trust a server certificate signed by a conventional certificate authority.
|
||||
|
||||
@@ -34,34 +34,13 @@ For example, from the connection initiator to the peer and the other way around.
|
||||
*open bidirectional stream*
|
||||
|
||||
```rust
|
||||
async fn open_bidirectional_stream(connection: Connection) -> anyhow::Result<()> {
|
||||
let (mut send, recv) = connection
|
||||
.open_bi()
|
||||
.await?;
|
||||
|
||||
send.write_all(b"test").await?;
|
||||
send.finish().await?;
|
||||
|
||||
let received = recv.read_to_end(10).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
{{#include data-transfer.rs:7:13}}
|
||||
```
|
||||
|
||||
*iterate incoming bidirectional stream(s)*
|
||||
|
||||
```rust
|
||||
async fn receive_bidirectional_stream(connection: Connection) -> anyhow::Result<()> {
|
||||
while let Ok((mut send, recv)) = connection.accept_bi().await {
|
||||
// Because it is a bidirectional stream, we can both send and receive.
|
||||
println!("request: {:?}", recv.read_to_end(50).await?);
|
||||
|
||||
send.write_all(b"response").await?;
|
||||
send.finish().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
{{#include data-transfer.rs:15:23}}
|
||||
```
|
||||
|
||||
## Unidirectional Streams
|
||||
@@ -72,29 +51,13 @@ It is possible to get reliability without ordering (so no head-of-line blocking)
|
||||
*open unidirectional stream*
|
||||
|
||||
```rust
|
||||
async fn open_unidirectional_stream(connection: Connection)-> anyhow::Result<()> {
|
||||
let mut send = connection
|
||||
.open_uni()
|
||||
.await?;
|
||||
|
||||
send.write_all(b"test").await?;
|
||||
send.finish().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
{{#include data-transfer.rs:25:30}}
|
||||
```
|
||||
|
||||
*iterating incoming unidirectional stream(s)*
|
||||
|
||||
```rust
|
||||
async fn receive_unidirectional_stream(connection: Connection) -> anyhow::Result<()> {
|
||||
while let Ok(recv) = connection.accept_uni().await {
|
||||
// Because it is a unidirectional stream, we can only receive not send back.
|
||||
println!("{:?}", recv.read_to_end(50).await?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
{{#include data-transfer.rs:32:38}}
|
||||
```
|
||||
|
||||
## Unreliable Messaging
|
||||
@@ -105,26 +68,13 @@ This could be useful if data arrival isn't essential or when high throughput is
|
||||
*send datagram*
|
||||
|
||||
```rust
|
||||
async fn send_unreliable(connection: Connection)-> anyhow::Result<()> {
|
||||
connection
|
||||
.send_datagram(b"test".into())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
{{#include data-transfer.rs:40:43}}
|
||||
```
|
||||
|
||||
*iterating datagram stream(s)*
|
||||
|
||||
```rust
|
||||
async fn receive_datagram(connection: Connection) -> anyhow::Result<()> {
|
||||
while let Ok(received_bytes) = connection.read_datagram().await {
|
||||
// Because it is a unidirectional stream, we can only receive not send back.
|
||||
println!("request: {:?}", received);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
{{#include data-transfer.rs:45:51}}
|
||||
```
|
||||
|
||||
[Endpoint]: https://docs.rs/quinn/latest/quinn/struct.Endpoint.html
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
use quinn::Connection;
|
||||
|
||||
async fn open_bidirectional_stream(connection: Connection) -> anyhow::Result<()> {
|
||||
let (mut send, recv) = connection.open_bi().await?;
|
||||
send.write_all(b"test").await?;
|
||||
send.finish().await?;
|
||||
let received = recv.read_to_end(10).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive_bidirectional_stream(connection: Connection) -> anyhow::Result<()> {
|
||||
while let Ok((mut send, recv)) = connection.accept_bi().await {
|
||||
// Because it is a bidirectional stream, we can both send and receive.
|
||||
println!("request: {:?}", recv.read_to_end(50).await?);
|
||||
send.write_all(b"response").await?;
|
||||
send.finish().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn open_unidirectional_stream(connection: Connection) -> anyhow::Result<()> {
|
||||
let mut send = connection.open_uni().await?;
|
||||
send.write_all(b"test").await?;
|
||||
send.finish().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive_unidirectional_stream(connection: Connection) -> anyhow::Result<()> {
|
||||
while let Ok(recv) = connection.accept_uni().await {
|
||||
// Because it is a unidirectional stream, we can only receive not send back.
|
||||
println!("{:?}", recv.read_to_end(50).await?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_unreliable(connection: Connection) -> anyhow::Result<()> {
|
||||
connection.send_datagram(b"test".into()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive_datagram(connection: Connection) -> anyhow::Result<()> {
|
||||
while let Ok(received_bytes) = connection.read_datagram().await {
|
||||
// Because it is a unidirectional stream, we can only receive not send back.
|
||||
println!("request: {:?}", received_bytes);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -12,15 +12,7 @@ It all starts with the [Endpoint][Endpoint] struct, this is the entry point of t
|
||||
Let's start by defining some constants.
|
||||
|
||||
```rust
|
||||
static SERVER_NAME: &str = "localhost";
|
||||
|
||||
fn client_addr() -> SocketAddr {
|
||||
"127.0.0.1:5000".parse::<SocketAddr>().unwrap()
|
||||
}
|
||||
|
||||
fn server_addr() -> SocketAddr {
|
||||
"127.0.0.1:5001".parse::<SocketAddr>().unwrap()
|
||||
}
|
||||
{{#include set-up-connection.rs:5:13}}
|
||||
```
|
||||
|
||||
**Server**
|
||||
@@ -30,19 +22,7 @@ The [server()][server] method, which can be used for this, returns the `Endpoint
|
||||
`Endpoint` is used to start outgoing connections and accept incoming connections.
|
||||
|
||||
```rust
|
||||
async fn server() -> Result<(), Box<dyn Error>> {
|
||||
// Bind this endpoint to a UDP socket on the given server address.
|
||||
let endpoint = Endpoint::server(config, server_addr())?;
|
||||
|
||||
// Start iterating over incoming connections.
|
||||
while let Some(conn) = endpoint.accept().await {
|
||||
let mut connection = conn.await?;
|
||||
|
||||
// Save connection somewhere, start transferring, receiving data, see DataTransfer tutorial.
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
{{#include set-up-connection.rs:15:27}}
|
||||
```
|
||||
|
||||
**Client**
|
||||
@@ -52,17 +32,7 @@ The client needs to connect to the server using the [connect(server_name)][conne
|
||||
The `SERVER_NAME` argument is the DNS name, matching the certificate configured in the server.
|
||||
|
||||
```rust
|
||||
async fn client() -> Result<(), Box<dyn Error>> {
|
||||
// Bind this endpoint to a UDP socket on the given client address.
|
||||
let mut endpoint = Endpoint::client(client_addr());
|
||||
|
||||
// Connect to the server passing in the server name which is supposed to be in the server certificate.
|
||||
let connection = endpoint.connect(server_addr(), SERVER_NAME)?.await?;
|
||||
|
||||
// Start transferring, receiving data, see data transfer page.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
{{#include set-up-connection.rs:29:39}}
|
||||
```
|
||||
<br><hr>
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use quinn::{Endpoint, ServerConfig};
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
static SERVER_NAME: &str = "localhost";
|
||||
|
||||
fn client_addr() -> SocketAddr {
|
||||
"127.0.0.1:5000".parse::<SocketAddr>().unwrap()
|
||||
}
|
||||
|
||||
fn server_addr() -> SocketAddr {
|
||||
"127.0.0.1:5001".parse::<SocketAddr>().unwrap()
|
||||
}
|
||||
|
||||
async fn server(config: ServerConfig) -> Result<(), Box<dyn Error>> {
|
||||
// Bind this endpoint to a UDP socket on the given server address.
|
||||
let endpoint = Endpoint::server(config, server_addr())?;
|
||||
|
||||
// Start iterating over incoming connections.
|
||||
while let Some(conn) = endpoint.accept().await {
|
||||
let mut connection = conn.await?;
|
||||
|
||||
// Save connection somewhere, start transferring, receiving data, see DataTransfer tutorial.
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn client() -> Result<(), Box<dyn Error>> {
|
||||
// Bind this endpoint to a UDP socket on the given client address.
|
||||
let mut endpoint = Endpoint::client(client_addr());
|
||||
|
||||
// Connect to the server passing in the server name which is supposed to be in the server certificate.
|
||||
let connection = endpoint.connect(server_addr(), SERVER_NAME)?.await?;
|
||||
|
||||
// Start transferring, receiving data, see data transfer page.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user