docs: separate example code from document

separate certificate's examples out of md file into rs files

format
This commit is contained in:
kota-yata
2025-03-30 01:36:34 +09:00
committed by Dirkjan Ochtman
parent f8165c3394
commit 41f7d2ea8f
10 changed files with 258 additions and 149 deletions
Generated
+11
View File
@@ -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
View File
@@ -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"
+32
View File
@@ -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"
+38
View File
@@ -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();
}
+6 -59
View File
@@ -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.
+6 -56
View File
@@ -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
+47
View File
@@ -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(())
}
+3 -33
View File
@@ -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>
+39
View File
@@ -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(())
}