From 671fe5f2d99d40094794e543fa76e36dcc162a6e Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Sun, 30 Oct 2022 18:37:08 -0500 Subject: [PATCH] Add example of generating a new CA and a new server cert signed by that CA, resolving #79 --- CHANGELOG.md | 5 + Cargo.lock | 274 ++++++++++++++++++- Cargo.toml | 11 + examples/auto-gen-ca-and-server-nativetls.rs | 191 +++++++++++++ examples/auto-gen-ca-and-server-tls.rs | 217 +++++++++++++++ examples/plumber.rs | 77 ++++++ 6 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 examples/auto-gen-ca-and-server-nativetls.rs create mode 100644 examples/auto-gen-ca-and-server-tls.rs create mode 100644 examples/plumber.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f5bc79e2..5618914a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changes +## Unreleased + +- Add [example](./examples/auto-gen-ca-and-server-tls.rs) that creates a new CA and new server certificate signed by it. + Contributed by [iamjpotts](https://github.com/iamjpotts). + ## Release 0.11.1 - June 17, 2023 - Make botan a dev-dependency again. Contributed by [mbrubeck](https://github.com/mbrubeck). diff --git a/Cargo.lock b/Cargo.lock index ea74012d..2edb8709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "botan" version = "0.10.6" @@ -122,6 +128,41 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -184,6 +225,33 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "foreign-types" version = "0.3.2" @@ -256,6 +324,12 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + [[package]] name = "log" version = "0.4.20" @@ -274,6 +348,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -364,7 +456,7 @@ version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -384,6 +476,12 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "openssl-sys" version = "0.9.91" @@ -415,6 +513,16 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pipe" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7b8f27da217eb966df4c58d4159ea939431950ca03cf782c22bd7c5c1d8d75" +dependencies = [ + "crossbeam-channel", + "readwrite", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -501,11 +609,14 @@ name = "rcgen" version = "0.11.1" dependencies = [ "botan", + "native-tls", "openssl", "pem", + "pipe", "rand", "ring", "rsa", + "rustls", "rustls-webpki", "time", "x509-parser", @@ -513,6 +624,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "readwrite" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73891b98dabbe836d23a094941e6ec891bc4880e771faea98813f2ff27ede473" + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "ring" version = "0.16.20" @@ -559,6 +685,31 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.38.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.21.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + [[package]] name = "rustls-webpki" version = "0.101.4" @@ -569,6 +720,48 @@ dependencies = [ "untrusted", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.186" @@ -661,6 +854,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "1.0.47" @@ -836,6 +1042,72 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "x509-parser" version = "0.15.1" diff --git a/Cargo.toml b/Cargo.toml index bfa4f23a..10f89122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,11 @@ default = ["pem"] features = ["x509-parser"] [dev-dependencies] +native-tls = "0.2" openssl = "0.10" +pipe = { version = "0.4", features = ["bidirectional"] } x509-parser = { version = "0.15", features = ["verify"] } +rustls = "0.21" rustls-webpki = { version = "0.101.0", features = ["std"] } rand = "0.8" rsa = "0.9" @@ -48,3 +51,11 @@ botan = { version = "0.10", features = ["vendored"] } # ignores profile overrides for non leaf packages) [profile.dev.package.num-bigint-dig] opt-level = 3 + +[[example]] +name = "auto-gen-ca-and-server-nativetls" +required-features = ["x509-parser"] + +[[example]] +name = "auto-gen-ca-and-server-tls" +required-features = ["x509-parser"] diff --git a/examples/auto-gen-ca-and-server-nativetls.rs b/examples/auto-gen-ca-and-server-nativetls.rs new file mode 100644 index 00000000..8dab2b1d --- /dev/null +++ b/examples/auto-gen-ca-and-server-nativetls.rs @@ -0,0 +1,191 @@ +//! Auto-generate a CA certificate and a server certificate signed +//! by that CA, and start a TLS server and client using them. +//! +//! Run with: +//! +//! $ cargo run --example auto-gen-ca-and-server-nativetls --features=x509-parser +//! +//! This doesn't start a network service (not even on localhost). +//! Instead, it creates an in-memory TLS server and an in-memory +//! TLS client in two separate threads in the same process. + +use std::error::Error; +use std::io::{BufRead, BufReader, Read, Write}; +use std::thread; + +use native_tls::{HandshakeError, Identity, TlsAcceptor, TlsConnector, TlsStream}; +use rcgen::{BasicConstraints, CertificateSigningRequest, DnType, IsCa, SanType, KeyUsagePurpose, DistinguishedName, CertificateParams, Certificate, RcgenError}; + +const SAN: &str = "example-server"; + +fn read_line(reader: &mut A) -> Result { + let mut buffer = String::new(); + + reader.read_line(&mut buffer)?; + + Ok(buffer.trim_end().into()) +} + +fn main() -> Result<(), Box> { + let ca = gen_cert_for_ca()?; + + println!("CA private key:\n{}", ca.serialize_private_key_pem()); + println!(); + println!("CA certificate:\n{}", ca.serialize_pem()?); + + let host_cert = gen_cert_for_server(&ca)?; + + println!("Preparing to verify with tls"); + + let leaf_then_ca = format!("{}{}", host_cert.signed_certificate_pem, ca.serialize_pem()?); + + println!(); + println!("Server private key:\n{}", host_cert.private_key_pem); + println!("Server chain:\n{}\n", leaf_then_ca); + + // For use by server + let server_id = Identity::from_pkcs8( + leaf_then_ca.as_bytes(), + host_cert.private_key_pem.as_bytes() + )?; + + // For use by client + let native_ca_cert = native_tls::Certificate::from_pem(ca.serialize_pem()?.as_bytes())?; + + // We can use either bipipe or bipipe_buffered when one side reads before it writes, but + // we cannot use either if both sides write before they read - both sides will block on write. + let (p_client, p_server) = pipe::bipipe(); + + let t_client = thread::spawn(move || -> Result<(), String> { + println!("Client creating"); + + let test_client = TlsConnector::builder() + .add_root_certificate(native_ca_cert) + .build() + .map_err(|e| e.to_string())?; + + println!("Client connecting"); + + let mut stream = map_tls_io_error(test_client.connect(SAN, p_client))?; + + println!("Client connected"); + + println!("Client sending message"); + + let message = "Hello from client.\n".as_bytes(); + stream.write_all(message) + .unwrap(); + + stream.flush().unwrap(); + + println!("Client sent message"); + + let mut reader = BufReader::new(stream); + println!("Client received message: {}", read_line(&mut reader).unwrap()); + + Ok(()) + }); + + let t_server = thread::spawn(move || -> Result<(), String> { + println!("Server creating"); + + let test_server = TlsAcceptor::new(server_id) + .map_err(|e| e.to_string())?; + + println!("Server accepting"); + + let mut stream = map_tls_io_error(test_server.accept(p_server))?; + + println!("Server accepted"); + + let mut reader = BufReader::new(&mut stream); + println!("Server received message: {}", read_line(&mut reader).unwrap()); + + println!("Server sending message"); + + let message = "Hello from server.\n".as_bytes(); + stream.write_all(message) + .unwrap(); + + println!("Server sent message"); + + Ok(()) + }); + + println!("Joining client"); + t_client.join() + .map_err(|e| format!("{:?}", e))??; + + println!("Joining server"); + t_server.join() + .map_err(|e| format!("{:?}", e))??; + + println!(); + println!("Succeeded."); + + Ok(()) +} + +struct ServerCertificate { + private_key_pem: String, + + // Server certificate only; does not include complete certificate chain. + signed_certificate_pem: String, +} + +fn gen_cert_for_ca() -> Result { + let mut dn = DistinguishedName::new(); + dn.push(DnType::CountryName, "USA"); + dn.push(DnType::CommonName, "Auto-Generated CA"); + + let mut params = CertificateParams::default(); + + params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + params.distinguished_name = dn; + params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign]; + + Certificate::from_params(params) +} + +fn gen_cert_for_server(ca: &Certificate) -> Result { + let mut dn = DistinguishedName::new(); + dn.push(DnType::CountryName, "USA"); + dn.push(DnType::CommonName, "Auto-Generated Server"); + + let mut params = CertificateParams::default(); + + params.is_ca = IsCa::NoCa; + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + params.distinguished_name = dn; + params.subject_alt_names = vec![SanType::DnsName(SAN.into())]; + + let unsigned = Certificate::from_params(params)?; + + let request_pem = unsigned.serialize_request_pem()?; + + let csr = CertificateSigningRequest::from_pem(&request_pem)?; + + let signed_pem = csr.serialize_pem_with_signer(&ca)?; + + Ok(ServerCertificate { + private_key_pem: unsigned.serialize_private_key_pem(), + signed_certificate_pem: signed_pem + }) +} + +fn map_tls_io_error(tls_result: Result, HandshakeError>) -> Result, String> +where + S: Read + Write +{ + match tls_result { + Ok(stream) => Ok(stream), + Err(he) => { + match he { + HandshakeError::Failure(e) => Err(format!("{}", e)), + // Can't directly unwrap because TlsStream doesn't implement Debug trait + HandshakeError::WouldBlock(_) => Err("Would block".into()) + } + } + } +} diff --git a/examples/auto-gen-ca-and-server-tls.rs b/examples/auto-gen-ca-and-server-tls.rs new file mode 100644 index 00000000..15fa3887 --- /dev/null +++ b/examples/auto-gen-ca-and-server-tls.rs @@ -0,0 +1,217 @@ +//! Auto-generate a CA certificate and a server certificate signed +//! by that CA, and start a TLS server and client using them. +//! +//! Run with: +//! +//! $ cargo run --example auto-gen-ca-and-server-tls --features=x509-parser +//! +//! This doesn't start a network service (not even on localhost). +//! Instead, it creates an in-memory TLS server and an in-memory +//! TLS client in two separate threads in the same process. + +use std::error::Error; +use std::io::{BufRead, BufReader, Write}; +use std::sync::Arc; +use std::thread; +use rustls; +use rustls::{PrivateKey, RootCertStore, ServerName}; +use rustls::server::Acceptor; + +use rcgen::{BasicConstraints, CertificateSigningRequest, DnType, IsCa, SanType, KeyUsagePurpose, DistinguishedName, CertificateParams, Certificate, RcgenError}; + +const SAN: &str = "example-server"; + +fn read_line(reader: &mut A) -> Result { + let mut buffer = String::new(); + + reader.read_line(&mut buffer)?; + + Ok(buffer.trim_end().into()) +} + +fn main() -> Result<(), Box> { + let ca = gen_cert_for_ca()?; + let rustls_ca = rustls::Certificate(ca.serialize_der().unwrap()); + + println!("CA private key:\n{}", ca.serialize_private_key_pem()); + println!(); + println!("CA certificate:\n{}", ca.serialize_pem()?); + + let host_cert = gen_cert_for_server(&ca)?; + let host_cert_rustls = rustls::Certificate(host_cert.signed_certificate_der); + let host_pk = PrivateKey(host_cert.private_key_der); + + println!("Preparing to verify with tls"); + + let leaf_then_ca = vec![host_cert_rustls, rustls_ca.clone()]; + + println!(); + + // Does not work with either buffered or unbuffered bipipe, even when we use the same message + // pattern as with the nativetls example, where client sends then reads, and server reads and + // then sends. Client blocks sending a message; server blocks reading a message. + let (mut p_client, mut p_server) = pipe::bipipe_buffered(); + + let t_client = thread::spawn(move || -> Result<(), String> { + println!("Client creating"); + + let mut root_store = RootCertStore::empty(); + + root_store.add(&rustls_ca) + .unwrap(); + + let client_config = Arc::new( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth() + ); + + let server_name = ServerName::try_from(SAN) + .unwrap(); + + println!("Client connecting"); + + let mut conn = rustls::ClientConnection::new(client_config, server_name) + .unwrap(); + + println!("Client connected"); + + let mut stream = rustls::Stream::new(&mut conn, &mut p_client); + + println!("Client handshaking? {}", stream.conn.is_handshaking()); + + stream.conn.process_new_packets().unwrap(); + + println!("Client handshaking? {}", stream.conn.is_handshaking()); + + println!("Client sending message"); + + let message = "Hello from client.\n".as_bytes(); + stream.write_all(message) + .unwrap(); + + stream.flush().unwrap(); + + println!("Client sent message"); + + let mut reader = BufReader::new(stream); + println!("Client received message: {}", read_line(&mut reader).unwrap()); + + Ok(()) + }); + + let t_server = thread::spawn(move || -> Result<(), String> { + println!("Server creating"); + + let server_config = Arc::new( + rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(leaf_then_ca, host_pk) + .unwrap() + ); + + let mut acceptor = Acceptor::default(); + + println!("Server accepting"); + let accepted = loop { + acceptor.read_tls(&mut p_server) + .unwrap(); + + if let Some(accepted) = acceptor.accept().unwrap() { + break accepted; + } + }; + + + let mut conn = accepted.into_connection(server_config) + .unwrap(); + + println!("Server accepted"); + + let mut stream = rustls::Stream::new(&mut conn, &mut p_server); + + println!("Server handshaking? {}", stream.conn.is_handshaking()); + + stream.conn.process_new_packets().unwrap(); + + println!("Server handshaking? {}", stream.conn.is_handshaking()); + + let mut reader = BufReader::new(&mut stream); + println!("Server received message: {}", read_line(&mut reader).unwrap()); + + println!("Server sending message"); + + let message = "Hello from server.\n".as_bytes(); + stream.write_all(message) + .unwrap(); + + println!("Server sent message"); + + Ok(()) + }); + + println!("Joining client"); + t_client.join() + .map_err(|e| format!("{:?}", e))??; + + println!("Joining server"); + t_server.join() + .map_err(|e| format!("{:?}", e))??; + + println!(); + println!("Succeeded."); + + Ok(()) +} + +struct ServerCertificate { + private_key_der: Vec, + + // Server certificate only; does not include complete certificate chain. + signed_certificate_der: Vec, +} + +// Create a certificate authority using rcgen and return and rcgen::Certificate. +fn gen_cert_for_ca() -> Result { + let mut dn = DistinguishedName::new(); + dn.push(DnType::CountryName, "USA"); + dn.push(DnType::CommonName, "Auto-Generated CA"); + + let mut params = CertificateParams::default(); + + params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + params.distinguished_name = dn; + params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign]; + + Certificate::from_params(params) +} + +// Create a server (host) certificate using rcgen and return it encoded into the DER format. +fn gen_cert_for_server(ca: &Certificate) -> Result { + let mut dn = DistinguishedName::new(); + dn.push(DnType::CountryName, "USA"); + dn.push(DnType::CommonName, "Auto-Generated Server"); + + let mut params = CertificateParams::default(); + + params.is_ca = IsCa::NoCa; + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + params.distinguished_name = dn; + params.subject_alt_names = vec![SanType::DnsName(SAN.into())]; + + let unsigned = Certificate::from_params(params)?; + + let request_pem = unsigned.serialize_request_pem()?; + + let csr = CertificateSigningRequest::from_pem(&request_pem)?; + + let signed_der = csr.serialize_der_with_signer(&ca)?; + + Ok(ServerCertificate { + private_key_der: unsigned.serialize_private_key_der(), + signed_certificate_der: signed_der + }) +} diff --git a/examples/plumber.rs b/examples/plumber.rs new file mode 100644 index 00000000..5d340090 --- /dev/null +++ b/examples/plumber.rs @@ -0,0 +1,77 @@ +/// Exchange messages across a bidirectional pipe. +/// +/// Provided for comparison with auto-gen-ca-and-server-tls, which uses a bidirectional pipe in +/// its implementation, but fails to exchange messages bidirectionally over TLS. + +use std::error::Error; +use std::io::{BufRead, Write}; +use std::thread; + +fn read_line(reader: &mut A) -> Result { + let mut buffer = String::new(); + + reader.read_line(&mut buffer)?; + + Ok(buffer.trim_end().into()) +} + +fn main() -> Result<(), Box> { + // When using pipe::bipipe(), at least one side must read before it writes. + // When using bipipe_buffered(), both sides can write before they read. + let (p_client, p_server) = pipe::bipipe(); + + let t_client = thread::spawn(move || -> Result<(), String> { + println!("Client creating"); + + let mut text_reader = p_client.0; + let mut text_writer = p_client.1; + + println!("Client pinging"); + + text_writer.write_all("Ping\n".as_bytes()) + .unwrap(); + + println!("Client pinged"); + + let response = read_line(&mut text_reader) + .unwrap(); + + println!("Client received response: {}", response); + + Ok(()) + }); + + let t_server = thread::spawn(move || -> Result<(), String> { + println!("Server creating"); + + let mut text_reader = p_server.0; + let mut text_writer = p_server.1; + + let request = read_line(&mut text_reader) + .unwrap(); + + println!("Server received request: {}", request); + + println!("Server sending response"); + + text_writer.write_all("Pong\n".as_bytes()) + .unwrap(); + + println!("Server sent response"); + + Ok(()) + }); + + println!("Joining client"); + t_client.join() + .map_err(|e| format!("{:?}", e))??; + + println!("Joining server"); + t_server.join() + .map_err(|e| format!("{:?}", e))??; + + println!(); + println!("Succeeded."); + + Ok(()) +}