From 41f7d2ea8f645adf630ca5712259fa34770c331e Mon Sep 17 00:00:00 2001 From: kota-yata Date: Sun, 30 Mar 2025 01:36:34 +0900 Subject: [PATCH] docs: separate example code from document separate certificate's examples out of md file into rs files format --- Cargo.lock | 11 +++ Cargo.toml | 2 +- docs/book/Cargo.toml | 32 +++++++++ docs/book/src/quinn/certificate-certs.rs | 38 +++++++++++ docs/book/src/quinn/certificate-insecure.rs | 75 +++++++++++++++++++++ docs/book/src/quinn/certificate.md | 65 ++---------------- docs/book/src/quinn/data-transfer.md | 62 ++--------------- docs/book/src/quinn/data-transfer.rs | 47 +++++++++++++ docs/book/src/quinn/set-up-connection.md | 36 +--------- docs/book/src/quinn/set-up-connection.rs | 39 +++++++++++ 10 files changed, 258 insertions(+), 149 deletions(-) create mode 100644 docs/book/Cargo.toml create mode 100644 docs/book/src/quinn/certificate-certs.rs create mode 100644 docs/book/src/quinn/certificate-insecure.rs create mode 100644 docs/book/src/quinn/data-transfer.rs create mode 100644 docs/book/src/quinn/set-up-connection.rs diff --git a/Cargo.lock b/Cargo.lock index ef25fcb70..87e1973d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index a020722c5..1928245ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/docs/book/Cargo.toml b/docs/book/Cargo.toml new file mode 100644 index 000000000..629c194c7 --- /dev/null +++ b/docs/book/Cargo.toml @@ -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" diff --git a/docs/book/src/quinn/certificate-certs.rs b/docs/book/src/quinn/certificate-certs.rs new file mode 100644 index 000000000..0cb3f8f3e --- /dev/null +++ b/docs/book/src/quinn/certificate-certs.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::PrivateKeyDer<'static>, + ), + Box, +> { + 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, +> { + 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(); +} diff --git a/docs/book/src/quinn/certificate-insecure.rs b/docs/book/src/quinn/certificate-insecure.rs new file mode 100644 index 000000000..b28a08c82 --- /dev/null +++ b/docs/book/src/quinn/certificate-insecure.rs @@ -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); + +impl SkipServerVerification { + fn new() -> Arc { + 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 { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &rustls::pki_types::CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + 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::crypto::verify_tls13_signature( + message, + cert, + dss, + &self.0.signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0.signature_verification_algorithms.supported_schemes() + } +} + +fn configure_client() -> Result { + 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(); +} diff --git a/docs/book/src/quinn/certificate.md b/docs/book/src/quinn/certificate.md index b2973997c..8ec71cae5 100644 --- a/docs/book/src/quinn/certificate.md +++ b/docs/book/src/quinn/certificate.md @@ -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 { - 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, - _ocsp_response: &[u8], - _now: std::time::SystemTime, - ) -> Result { - 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> -{ - 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::PrivateKey), Box> { - 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. diff --git a/docs/book/src/quinn/data-transfer.md b/docs/book/src/quinn/data-transfer.md index c6937a276..2991c5587 100644 --- a/docs/book/src/quinn/data-transfer.md +++ b/docs/book/src/quinn/data-transfer.md @@ -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 diff --git a/docs/book/src/quinn/data-transfer.rs b/docs/book/src/quinn/data-transfer.rs new file mode 100644 index 000000000..8dd07693f --- /dev/null +++ b/docs/book/src/quinn/data-transfer.rs @@ -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(()) +} diff --git a/docs/book/src/quinn/set-up-connection.md b/docs/book/src/quinn/set-up-connection.md index f32006405..5c43652e0 100644 --- a/docs/book/src/quinn/set-up-connection.md +++ b/docs/book/src/quinn/set-up-connection.md @@ -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::().unwrap() -} - -fn server_addr() -> SocketAddr { - "127.0.0.1:5001".parse::().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> { - // 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> { - // 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}} ```

diff --git a/docs/book/src/quinn/set-up-connection.rs b/docs/book/src/quinn/set-up-connection.rs new file mode 100644 index 000000000..7f7ff9fa3 --- /dev/null +++ b/docs/book/src/quinn/set-up-connection.rs @@ -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::().unwrap() +} + +fn server_addr() -> SocketAddr { + "127.0.0.1:5001".parse::().unwrap() +} + +async fn server(config: ServerConfig) -> Result<(), Box> { + // 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> { + // 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(()) +}