From 094b9b4ac5e6511f51a38933c7b82c1561459b7c Mon Sep 17 00:00:00 2001 From: Anthony Grondin <104731965+AnthonyGrondin@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:56:20 -0500 Subject: [PATCH] chore: Deduplicate mTLS examples behind a feature. mTLS and non-mTLS examples are essentially the same, structurally, but with different parameters. This merge removes a huge maintenance burden by reducing the amount of example files. --- Cargo.toml | 16 +- examples/async_client.rs | 61 +++++-- examples/async_client_mTLS.rs | 229 --------------------------- examples/async_server.rs | 29 ++++ examples/async_server_mTLS.rs | 289 ---------------------------------- examples/sync_client.rs | 63 ++++++-- examples/sync_client_mTLS.rs | 163 ------------------- examples/sync_server.rs | 31 ++++ examples/sync_server_mTLS.rs | 228 --------------------------- justfile | 8 +- 10 files changed, 168 insertions(+), 949 deletions(-) delete mode 100644 examples/async_client_mTLS.rs delete mode 100644 examples/async_server_mTLS.rs delete mode 100644 examples/sync_client_mTLS.rs delete mode 100644 examples/sync_server_mTLS.rs diff --git a/Cargo.toml b/Cargo.toml index 9688aee..602d6bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,18 +90,10 @@ name = "crypto_self_test_std" name = "async_client" required-features = ["examples-async"] -[[example]] -name = "async_client_mTLS" -required-features = ["examples-async"] - [[example]] name = "async_server" required-features = ["examples-async"] -[[example]] -name = "async_server_mTLS" -required-features = ["examples-async"] - [[example]] name = "edge_server" required-features = ["examples-async", "edge-http"] @@ -162,5 +154,13 @@ esp32s3 = [ "esp-mbedtls/esp32s3", ] +# Enable mTLS for the running example. See example documentation for further details. +# Applies to: +# - async_client +# - async_server +# - sync_client +# - sync_server +mtls = [] + [build-dependencies] embuild = { version = "0.33", features = ["espidf"] } diff --git a/examples/async_client.rs b/examples/async_client.rs index 7c9c2ce..310cf26 100644 --- a/examples/async_client.rs +++ b/examples/async_client.rs @@ -1,10 +1,16 @@ //! Example for a client connection to a server. -//! This example connects to Google.com and then prints out the result +//! This example connects to either `Google.com` or `certauth.cryptomix.com` (mTLS) and then prints out the result. +//! +//! # mTLS +//! Use the mTLS feature to enable client authentication and send client certificates when doing a +//! request. Note that this will connect to `certauth.cryptomix.com` instead of `google.com` #![no_std] #![no_main] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] +use core::ffi::CStr; + #[doc(hidden)] pub use esp_hal as hal; @@ -38,6 +44,19 @@ macro_rules! mk_static { const SSID: &str = env!("SSID"); const PASSWORD: &str = env!("PASSWORD"); +// Setup configuration based on mTLS feature. +cfg_if::cfg_if! { + if #[cfg(feature = "mtls")] { + const REMOTE_IP: Ipv4Address = Ipv4Address::new(62, 210, 201, 125); // certauth.cryptomix.com + const SERVERNAME: &CStr = c"certauth.cryptomix.com"; + const REQUEST: &[u8] = b"GET /json/ HTTP/1.0\r\nHost: certauth.cryptomix.com\r\n\r\n"; + } else { + const REMOTE_IP: Ipv4Address = Ipv4Address::new(142, 250, 185, 68); // google.com + const SERVERNAME: &CStr = c"www.google.com"; + const REQUEST: &[u8] = b"GET /notfound HTTP/1.0\r\nHost: www.google.com\r\n\r\n"; + } +} + #[esp_hal_embassy::main] async fn main(spawner: Spawner) -> ! { init_logger(log::LevelFilter::Info); @@ -115,7 +134,7 @@ async fn main(spawner: Spawner) -> ! { socket.set_timeout(Some(Duration::from_secs(10))); - let remote_endpoint = (Ipv4Address::new(142, 250, 185, 68), 443); // www.google.com + let remote_endpoint = (REMOTE_IP, 443); println!("connecting..."); let r = socket.connect(remote_endpoint).await; if let Err(e) = r { @@ -124,6 +143,30 @@ async fn main(spawner: Spawner) -> ! { loop {} } + cfg_if::cfg_if! { + if #[cfg(feature = "mtls")] { + let certificates = Certificates { + ca_chain: X509::pem( + concat!(include_str!("./certs/certauth.cryptomix.com.pem"), "\0").as_bytes(), + ) + .ok(), + certificate: X509::pem(concat!(include_str!("./certs/certificate.pem"), "\0").as_bytes()) + .ok(), + private_key: X509::pem(concat!(include_str!("./certs/private_key.pem"), "\0").as_bytes()) + .ok(), + password: None, + }; + } else { + let certificates = Certificates { + ca_chain: X509::pem( + concat!(include_str!("./certs/www.google.com.pem"), "\0").as_bytes(), + ) + .ok(), + ..Default::default() + }; + } + } + let mut tls = Tls::new(peripherals.SHA) .unwrap() .with_hardware_rsa(peripherals.RSA); @@ -133,16 +176,10 @@ async fn main(spawner: Spawner) -> ! { let mut session = Session::new( &mut socket, Mode::Client { - servername: c"www.google.com", + servername: SERVERNAME, }, TlsVersion::Tls1_3, - Certificates { - ca_chain: X509::pem( - concat!(include_str!("./certs/www.google.com.pem"), "\0").as_bytes(), - ) - .ok(), - ..Default::default() - }, + certificates, tls.reference(), ) .unwrap(); @@ -155,9 +192,7 @@ async fn main(spawner: Spawner) -> ! { use embedded_io_async::Write; - let r = session - .write_all(b"GET /notfound HTTP/1.0\r\nHost: www.google.com\r\n\r\n") - .await; + let r = session.write_all(REQUEST).await; if let Err(e) = r { println!("write error: {:?}", e); #[allow(clippy::empty_loop)] diff --git a/examples/async_client_mTLS.rs b/examples/async_client_mTLS.rs deleted file mode 100644 index 6697bc0..0000000 --- a/examples/async_client_mTLS.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! Example for a client connection using certificate authentication (mTLS) -#![no_std] -#![no_main] -#![feature(type_alias_impl_trait)] -#![feature(impl_trait_in_assoc_type)] -#![allow(non_snake_case)] - -#[doc(hidden)] -pub use esp_hal as hal; - -use embassy_executor::Spawner; - -use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Ipv4Address, Runner, StackResources}; - -use embassy_time::{Duration, Timer}; -use esp_backtrace as _; -use esp_mbedtls::{asynch::Session, Mode, TlsVersion}; -use esp_mbedtls::{Certificates, Tls, X509}; -use esp_println::logger::init_logger; -use esp_println::{print, println}; -use esp_wifi::wifi::{ - ClientConfiguration, Configuration, WifiController, WifiDevice, WifiEvent, WifiStaDevice, - WifiState, -}; -use esp_wifi::{init, EspWifiController}; -use hal::{clock::CpuClock, rng::Rng, timer::timg::TimerGroup}; - -// Patch until https://github.com/embassy-rs/static-cell/issues/16 is fixed -macro_rules! mk_static { - ($t:ty,$val:expr) => {{ - static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); - #[deny(unused_attributes)] - let x = STATIC_CELL.uninit().write(($val)); - x - }}; -} - -const SSID: &str = env!("SSID"); -const PASSWORD: &str = env!("PASSWORD"); - -#[esp_hal_embassy::main] -async fn main(spawner: Spawner) -> ! { - init_logger(log::LevelFilter::Info); - - let peripherals = esp_hal::init({ - let mut config = esp_hal::Config::default(); - config.cpu_clock = CpuClock::max(); - config - }); - - esp_alloc::heap_allocator!(115 * 1024); - - let timg0 = TimerGroup::new(peripherals.TIMG0); - - let init = &*mk_static!( - EspWifiController<'_>, - init( - timg0.timer0, - Rng::new(peripherals.RNG), - peripherals.RADIO_CLK, - ) - .unwrap() - ); - - let wifi = peripherals.WIFI; - let (wifi_interface, controller) = - esp_wifi::wifi::new_with_mode(init, wifi, WifiStaDevice).unwrap(); - - cfg_if::cfg_if! { - if #[cfg(feature = "esp32")] { - let timg1 = TimerGroup::new(peripherals.TIMG1); - esp_hal_embassy::init(timg1.timer0); - } else { - use esp_hal::timer::systimer::SystemTimer; - let systimer = SystemTimer::new(peripherals.SYSTIMER); - esp_hal_embassy::init(systimer.alarm0); - } - } - - let config = Config::dhcpv4(Default::default()); - - let seed = 1234; // very random, very secure seed - - // Init network stack - let (stack, runner) = embassy_net::new( - wifi_interface, - config, - mk_static!(StackResources<3>, StackResources::<3>::new()), - seed, - ); - - spawner.spawn(connection(controller)).ok(); - spawner.spawn(net_task(runner)).ok(); - - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; - - loop { - if stack.is_link_up() { - break; - } - Timer::after(Duration::from_millis(500)).await; - } - - println!("Waiting to get IP address..."); - loop { - if let Some(config) = stack.config_v4() { - println!("Got IP: {}", config.address); - break; - } - Timer::after(Duration::from_millis(500)).await; - } - - let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - - socket.set_timeout(Some(Duration::from_secs(10))); - - let remote_endpoint = (Ipv4Address::new(62, 210, 201, 125), 443); // certauth.cryptomix.com - println!("connecting..."); - let r = socket.connect(remote_endpoint).await; - if let Err(e) = r { - println!("connect error: {:?}", e); - #[allow(clippy::empty_loop)] - loop {} - } - - let certificates = Certificates { - ca_chain: X509::pem( - concat!(include_str!("./certs/certauth.cryptomix.com.pem"), "\0").as_bytes(), - ) - .ok(), - certificate: X509::pem(concat!(include_str!("./certs/certificate.pem"), "\0").as_bytes()) - .ok(), - private_key: X509::pem(concat!(include_str!("./certs/private_key.pem"), "\0").as_bytes()) - .ok(), - password: None, - }; - - let mut tls = Tls::new(peripherals.SHA) - .unwrap() - .with_hardware_rsa(peripherals.RSA); - - tls.set_debug(0); - - let mut session = Session::new( - &mut socket, - Mode::Client { - servername: c"certauth.cryptomix.com", - }, - TlsVersion::Tls1_3, - certificates, - tls.reference(), - ) - .unwrap(); - - println!("Start tls connect"); - session.connect().await.unwrap(); - - println!("connected!"); - let mut buf = [0; 1024]; - - use embedded_io_async::Write; - - let r = session - .write_all(b"GET /json/ HTTP/1.0\r\nHost: certauth.cryptomix.com\r\n\r\n") - .await; - if let Err(e) = r { - println!("write error: {:?}", e); - #[allow(clippy::empty_loop)] - loop {} - } - - loop { - let n = match session.read(&mut buf).await { - Ok(n) => n, - Err(esp_mbedtls::TlsError::Eof) => { - break; - } - Err(e) => { - println!("read error: {:?}", e); - break; - } - }; - print!("{}", core::str::from_utf8(&buf[..n]).unwrap()); - } - println!("Done"); - - #[allow(clippy::empty_loop)] - loop {} -} - -#[embassy_executor::task] -async fn connection(mut controller: WifiController<'static>) { - println!("start connection task"); - println!("Device capabilities: {:?}", controller.capabilities()); - loop { - if matches!(esp_wifi::wifi::wifi_state(), WifiState::StaConnected) { - // wait until we're no longer connected - controller.wait_for_event(WifiEvent::StaDisconnected).await; - Timer::after(Duration::from_millis(5000)).await - } - if !matches!(controller.is_started(), Ok(true)) { - let client_config = Configuration::Client(ClientConfiguration { - ssid: SSID.try_into().unwrap(), - password: PASSWORD.try_into().unwrap(), - ..Default::default() - }); - controller.set_configuration(&client_config).unwrap(); - println!("Starting wifi"); - controller.start_async().await.unwrap(); - println!("Wifi started!"); - } - println!("About to connect..."); - - match controller.connect_async().await { - Ok(_) => println!("Wifi connected!"), - Err(e) => { - println!("Failed to connect to wifi: {e:?}"); - Timer::after(Duration::from_millis(5000)).await - } - } - } -} - -#[embassy_executor::task] -async fn net_task(mut runner: Runner<'static, WifiDevice<'static, WifiStaDevice>>) { - runner.run().await -} diff --git a/examples/async_server.rs b/examples/async_server.rs index 4cf0d70..f2b33d0 100644 --- a/examples/async_server.rs +++ b/examples/async_server.rs @@ -3,6 +3,28 @@ //! //! This example uses self-signed certificate. Your browser may display an error. //! You have to enable the exception to then proceed, of if using curl, use the flag `-k`. +//! +//! # mTLS +//! Running this example with the feature `mtls` will make the server request a client +//! certificate for the connection. If you send a request, without passing +//! certificates, you will get an error. Theses certificates below are generated +//! to work is the configured CA: +//! +//! certificate.pem +//! ```text +#![doc = include_str!("./certs/certificate.pem")] +//! ``` +//! +//! private_key.pem +//! ```text +#![doc = include_str!("./certs/private_key.pem")] +//! ``` +//! +//! Test with curl: +//! ```bash +//! curl https:/// --cert certificate.pem --key private_key.pem -k +//! ``` +//! #![no_std] #![no_main] #![feature(type_alias_impl_trait)] @@ -151,6 +173,10 @@ async fn main(spawner: Spawner) -> ! { Mode::Server, TlsVersion::Tls1_2, Certificates { + // Provide a ca_chain if you want to enable mTLS for the server. + #[cfg(feature = "mtls")] + ca_chain: X509::pem(concat!(include_str!("./certs/ca_cert.pem"), "\0").as_bytes()) + .ok(), // Use self-signed certificates certificate: X509::pem( concat!(include_str!("./certs/certificate.pem"), "\0").as_bytes(), @@ -212,6 +238,9 @@ async fn main(spawner: Spawner) -> ! { Timer::after(Duration::from_millis(1000)).await; } + Err(TlsError::NoClientCertificate) => { + println!("Error: No client certificates given. Please provide client certificates during your request"); + } Err(TlsError::MbedTlsError(-30592)) => { println!("Fatal message: Please enable the exception for a self-signed certificate in your browser"); } diff --git a/examples/async_server_mTLS.rs b/examples/async_server_mTLS.rs deleted file mode 100644 index f025e44..0000000 --- a/examples/async_server_mTLS.rs +++ /dev/null @@ -1,289 +0,0 @@ -//! Example for an async server. -//! Contains a basic server implementation to test mbedtls in server mode. -//! -//! This example is configured to use mTLS. If you send a request, without passing -//! certificates, you will get an error. Theses certificates below are generated -//! to work is the configured CA: -//! -//! certificate.pem -//! ```text -#![doc = include_str!("./certs/certificate.pem")] -//! ``` -//! -//! private_key.pem -//! ```text -#![doc = include_str!("./certs/private_key.pem")] -//! ``` -//! -//! Test with curl: -//! ```bash -//! curl https:/// --cert certificate.pem --key private_key.pem -k -//! ``` -#![no_std] -#![no_main] -#![feature(type_alias_impl_trait)] -#![feature(impl_trait_in_assoc_type)] -#![allow(non_snake_case)] - -#[doc(hidden)] -pub use esp_hal as hal; - -use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, IpListenEndpoint, Runner, StackResources}; - -use embassy_executor::Spawner; -use embassy_time::{Duration, Timer}; -use esp_alloc as _; -use esp_backtrace as _; -use esp_mbedtls::{asynch::Session, Certificates, Mode, TlsVersion}; -use esp_mbedtls::{Tls, TlsError, X509}; -use esp_println::logger::init_logger; -use esp_println::{print, println}; -use esp_wifi::wifi::{ - ClientConfiguration, Configuration, WifiController, WifiDevice, WifiEvent, WifiStaDevice, - WifiState, -}; -use esp_wifi::{init, EspWifiController}; -use hal::{clock::CpuClock, rng::Rng, timer::timg::TimerGroup}; - -// Patch until https://github.com/embassy-rs/static-cell/issues/16 is fixed -macro_rules! mk_static { - ($t:ty,$val:expr) => {{ - static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); - #[deny(unused_attributes)] - let x = STATIC_CELL.uninit().write(($val)); - x - }}; -} - -const SSID: &str = env!("SSID"); -const PASSWORD: &str = env!("PASSWORD"); - -#[esp_hal_embassy::main] -async fn main(spawner: Spawner) -> ! { - init_logger(log::LevelFilter::Info); - - let peripherals = esp_hal::init({ - let mut config = esp_hal::Config::default(); - config.cpu_clock = CpuClock::max(); - config - }); - - esp_alloc::heap_allocator!(115 * 1024); - - let timg0 = TimerGroup::new(peripherals.TIMG0); - - let init = &*mk_static!( - EspWifiController<'_>, - init( - timg0.timer0, - Rng::new(peripherals.RNG), - peripherals.RADIO_CLK, - ) - .unwrap() - ); - - let wifi = peripherals.WIFI; - let (wifi_interface, controller) = - esp_wifi::wifi::new_with_mode(init, wifi, WifiStaDevice).unwrap(); - - cfg_if::cfg_if! { - if #[cfg(feature = "esp32")] { - let timg1 = TimerGroup::new(peripherals.TIMG1); - esp_hal_embassy::init(timg1.timer0); - } else { - use esp_hal::timer::systimer::SystemTimer; - let systimer = SystemTimer::new(peripherals.SYSTIMER); - esp_hal_embassy::init(systimer.alarm0); - } - } - - let config = Config::dhcpv4(Default::default()); - - let seed = 1234; // very random, very secure seed - - // Init network stack - let (stack, runner) = embassy_net::new( - wifi_interface, - config, - mk_static!(StackResources<3>, StackResources::<3>::new()), - seed, - ); - - spawner.spawn(connection(controller)).ok(); - spawner.spawn(net_task(runner)).ok(); - - let mut tls = Tls::new(peripherals.SHA) - .unwrap() - .with_hardware_rsa(peripherals.RSA); - - tls.set_debug(0); - - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; - - loop { - if stack.is_link_up() { - break; - } - Timer::after(Duration::from_millis(500)).await; - } - - println!("Waiting to get IP address..."); - loop { - if let Some(config) = stack.config_v4() { - println!("Got IP: {}", config.address); - println!( - "Point your browser to https://{}/", - config.address.address() - ); - break; - } - Timer::after(Duration::from_millis(500)).await; - } - - let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(Duration::from_secs(10))); - loop { - println!("Waiting for connection..."); - let r = socket - .accept(IpListenEndpoint { - addr: None, - port: 443, - }) - .await; - println!("Connected..."); - - if let Err(e) = r { - println!("connect error: {:?}", e); - continue; - } - - use embedded_io_async::Write; - - let mut buffer = [0u8; 1024]; - let mut pos = 0; - let mut session = Session::new( - &mut socket, - Mode::Server, - TlsVersion::Tls1_2, - Certificates { - ca_chain: X509::pem(concat!(include_str!("./certs/ca_cert.pem"), "\0").as_bytes()) - .ok(), - // Use self-signed certificates - certificate: X509::pem( - concat!(include_str!("./certs/certificate.pem"), "\0").as_bytes(), - ) - .ok(), - private_key: X509::pem( - concat!(include_str!("./certs/private_key.pem"), "\0").as_bytes(), - ) - .ok(), - ..Default::default() - }, - tls.reference(), - ) - .unwrap(); - - println!("Start tls connect"); - match session.connect().await { - Ok(()) => { - log::info!("Got session"); - loop { - match session.read(&mut buffer).await { - Ok(0) => { - println!("read EOF"); - break; - } - Ok(len) => { - let to_print = - unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) }; - - if to_print.contains("\r\n\r\n") { - print!("{}", to_print); - println!(); - break; - } - - pos += len; - } - Err(e) => { - println!("read error: {:?}", e); - break; - } - }; - } - - let r = session - .write_all( - b"HTTP/1.0 200 OK\r\n\r\n\ - \ - \ -

Hello Rust! Hello esp-mbedtls!

\ - \ - \r\n\ - ", - ) - .await; - if let Err(e) = r { - println!("write error: {:?}", e); - } - - Timer::after(Duration::from_millis(1000)).await; - } - Err(TlsError::NoClientCertificate) => { - println!("Error: No client certificates given. Please provide client certificates during your request"); - } - Err(TlsError::MbedTlsError(-30592)) => { - println!("Fatal message: Please enable the exception for a self-signed certificate in your browser"); - } - Err(error) => { - panic!("{:?}", error); - } - } - drop(session); - println!("Closing socket"); - socket.close(); - Timer::after(Duration::from_millis(1000)).await; - - socket.abort(); - } -} - -#[embassy_executor::task] -async fn connection(mut controller: WifiController<'static>) { - println!("start connection task"); - println!("Device capabilities: {:?}", controller.capabilities()); - loop { - if matches!(esp_wifi::wifi::wifi_state(), WifiState::StaConnected) { - // wait until we're no longer connected - controller.wait_for_event(WifiEvent::StaDisconnected).await; - Timer::after(Duration::from_millis(5000)).await - } - if !matches!(controller.is_started(), Ok(true)) { - let client_config = Configuration::Client(ClientConfiguration { - ssid: SSID.try_into().unwrap(), - password: PASSWORD.try_into().unwrap(), - ..Default::default() - }); - controller.set_configuration(&client_config).unwrap(); - println!("Starting wifi"); - controller.start_async().await.unwrap(); - println!("Wifi started!"); - } - println!("About to connect..."); - - match controller.connect_async().await { - Ok(_) => println!("Wifi connected!"), - Err(e) => { - println!("Failed to connect to wifi: {e:?}"); - Timer::after(Duration::from_millis(5000)).await - } - } - } -} - -#[embassy_executor::task] -async fn net_task(mut runner: Runner<'static, WifiDevice<'static, WifiStaDevice>>) { - runner.run().await -} diff --git a/examples/sync_client.rs b/examples/sync_client.rs index 9047e3b..76cbf3f 100644 --- a/examples/sync_client.rs +++ b/examples/sync_client.rs @@ -1,8 +1,14 @@ //! Example for a client connection to a server. -//! This example connects to Google.com and then prints out the result +//! This example connects to either `Google.com` or `certauth.cryptomix.com` (mTLS) and then prints out the result. +//! +//! # mTLS +//! Use the mTLS feature to enable client authentication and send client certificates when doing a +//! request. Note that this will connect to `certauth.cryptomix.com` instead of `google.com` #![no_std] #![no_main] +use core::ffi::CStr; + #[doc(hidden)] pub use esp_hal as hal; @@ -26,6 +32,19 @@ use smoltcp::{ const SSID: &str = env!("SSID"); const PASSWORD: &str = env!("PASSWORD"); +// Setup configuration based on mTLS feature. +cfg_if::cfg_if! { + if #[cfg(feature = "mtls")] { + const REMOTE_IP: IpAddress = IpAddress::v4(62, 210, 201, 125); // certauth.cryptomix.com + const SERVERNAME: &CStr = c"certauth.cryptomix.com"; + const REQUEST: &[u8] = b"GET /json/ HTTP/1.0\r\nHost: certauth.cryptomix.com\r\n\r\n"; + } else { + const REMOTE_IP: IpAddress = IpAddress::v4(142, 250, 185, 68); // google.com + const SERVERNAME: &CStr = c"www.google.com"; + const REQUEST: &[u8] = b"GET /notfound HTTP/1.0\r\nHost: www.google.com\r\n\r\n"; + } +} + #[main] fn main() -> ! { init_logger(log::LevelFilter::Info); @@ -101,9 +120,31 @@ fn main() -> ! { socket.work(); - socket - .open(IpAddress::v4(142, 250, 185, 68), 443) // google.com - .unwrap(); + socket.open(REMOTE_IP, 443).unwrap(); + + cfg_if::cfg_if! { + if #[cfg(feature = "mtls")] { + let certificates = Certificates { + ca_chain: X509::pem( + concat!(include_str!("./certs/certauth.cryptomix.com.pem"), "\0").as_bytes(), + ) + .ok(), + certificate: X509::pem(concat!(include_str!("./certs/certificate.pem"), "\0").as_bytes()) + .ok(), + private_key: X509::pem(concat!(include_str!("./certs/private_key.pem"), "\0").as_bytes()) + .ok(), + password: None, + }; + } else { + let certificates = Certificates { + ca_chain: X509::pem( + concat!(include_str!("./certs/www.google.com.pem"), "\0").as_bytes(), + ) + .ok(), + ..Default::default() + }; + } + } let mut tls = Tls::new(peripherals.SHA) .unwrap() @@ -114,16 +155,10 @@ fn main() -> ! { let mut session = Session::new( &mut socket, Mode::Client { - servername: c"www.google.com", + servername: SERVERNAME, }, TlsVersion::Tls1_3, - Certificates { - ca_chain: X509::pem( - concat!(include_str!("./certs/www.google.com.pem"), "\0").as_bytes(), - ) - .ok(), - ..Default::default() - }, + certificates, tls.reference(), ) .unwrap(); @@ -132,9 +167,7 @@ fn main() -> ! { session.connect().unwrap(); println!("Write to connection"); - session - .write(b"GET /notfound HTTP/1.0\r\nHost: www.google.com\r\n\r\n") - .unwrap(); + session.write(REQUEST).unwrap(); println!("Read from connection"); let mut buffer = [0u8; 4096]; diff --git a/examples/sync_client_mTLS.rs b/examples/sync_client_mTLS.rs deleted file mode 100644 index b8d1f86..0000000 --- a/examples/sync_client_mTLS.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! Example for a client connection using certificate authentication (mTLS) -#![no_std] -#![no_main] - -#[doc(hidden)] -pub use esp_hal as hal; - -use blocking_network_stack::Stack; - -use esp_alloc as _; -use esp_backtrace as _; -use esp_mbedtls::{Certificates, Session}; -use esp_mbedtls::{Mode, Tls, TlsVersion, X509}; -use esp_println::{logger::init_logger, print, println}; -use esp_wifi::{ - init, - wifi::{utils::create_network_interface, ClientConfiguration, Configuration, WifiStaDevice}, -}; -use hal::{clock::CpuClock, main, rng::Rng, time, timer::timg::TimerGroup}; -use smoltcp::{ - iface::{SocketSet, SocketStorage}, - wire::IpAddress, -}; - -const SSID: &str = env!("SSID"); -const PASSWORD: &str = env!("PASSWORD"); - -#[main] -fn main() -> ! { - init_logger(log::LevelFilter::Info); - let peripherals = esp_hal::init({ - let mut config = esp_hal::Config::default(); - config.cpu_clock = CpuClock::max(); - config - }); - - esp_alloc::heap_allocator!(115 * 1024); - - let timg0 = TimerGroup::new(peripherals.TIMG0); - - let mut rng = Rng::new(peripherals.RNG); - - let init = init(timg0.timer0, rng, peripherals.RADIO_CLK).unwrap(); - - let wifi = peripherals.WIFI; - - let (iface, device, mut controller) = - create_network_interface(&init, wifi, WifiStaDevice).unwrap(); - - let mut socket_set_entries: [SocketStorage; 3] = Default::default(); - let sockets = SocketSet::new(&mut socket_set_entries[..]); - - let now = || time::now().duration_since_epoch().to_millis(); - let wifi_stack = Stack::new(iface, device, sockets, now, rng.random()); - - println!("Call wifi_connect"); - let client_config = Configuration::Client(ClientConfiguration { - ssid: SSID.try_into().unwrap(), - password: PASSWORD.try_into().unwrap(), - ..Default::default() - }); - controller.set_configuration(&client_config).unwrap(); - controller.start().unwrap(); - controller.connect().unwrap(); - - println!("Wait to get connected"); - loop { - let res = controller.is_connected(); - match res { - Ok(connected) => { - if connected { - break; - } - } - Err(err) => { - println!("{:?}", err); - #[allow(clippy::empty_loop)] - loop {} - } - } - } - - // wait for getting an ip address - println!("Wait to get an ip address"); - loop { - wifi_stack.work(); - - if wifi_stack.is_iface_up() { - println!("Got ip {:?}", wifi_stack.get_ip_info()); - break; - } - } - - println!("We are connected!"); - - println!("Making HTTP request"); - let mut rx_buffer = [0u8; 1536]; - let mut tx_buffer = [0u8; 1536]; - let mut socket = wifi_stack.get_socket(&mut rx_buffer, &mut tx_buffer); - - socket.work(); - - socket - .open(IpAddress::v4(62, 210, 201, 125), 443) // certauth.cryptomix.com - .unwrap(); - - let certificates = Certificates { - ca_chain: X509::pem( - concat!(include_str!("./certs/certauth.cryptomix.com.pem"), "\0").as_bytes(), - ) - .ok(), - certificate: X509::pem(concat!(include_str!("./certs/certificate.pem"), "\0").as_bytes()) - .ok(), - private_key: X509::pem(concat!(include_str!("./certs/private_key.pem"), "\0").as_bytes()) - .ok(), - password: None, - }; - - let mut tls = Tls::new(peripherals.SHA) - .unwrap() - .with_hardware_rsa(peripherals.RSA); - - tls.set_debug(0); - - let mut session = Session::new( - &mut socket, - Mode::Client { - servername: c"certauth.cryptomix.com", - }, - TlsVersion::Tls1_3, - certificates, - tls.reference(), - ) - .unwrap(); - - println!("Start tls connect"); - session.connect().unwrap(); - - println!("Write to connection"); - session - .write(b"GET /json/ HTTP/1.0\r\nHost: certauth.cryptomix.com\r\n\r\n") - .unwrap(); - - println!("Read from connection"); - let mut buffer = [0u8; 4096]; - loop { - match session.read(&mut buffer) { - Ok(len) => { - print!("{}", unsafe { - core::str::from_utf8_unchecked(&buffer[..len]) - }); - } - Err(_) => { - println!(); - break; - } - } - } - println!("Done"); - - #[allow(clippy::empty_loop)] - loop {} -} diff --git a/examples/sync_server.rs b/examples/sync_server.rs index c0da89a..e0a5c0e 100644 --- a/examples/sync_server.rs +++ b/examples/sync_server.rs @@ -3,6 +3,28 @@ //! //! This example uses self-signed certificate. Your browser may display an error. //! You have to enable the exception to then proceed, of if using curl, use the flag `-k`. +//! +//! # mTLS +//! Running this example with the feature `mtls` will make the server request a client +//! certificate for the connection. If you send a request, without passing +//! certificates, you will get an error. Theses certificates below are generated +//! to work is the configured CA: +//! +//! certificate.pem +//! ```text +#![doc = include_str!("./certs/certificate.pem")] +//! ``` +//! +//! private_key.pem +//! ```text +#![doc = include_str!("./certs/private_key.pem")] +//! ``` +//! +//! Test with curl: +//! ```bash +//! curl https:/// --cert certificate.pem --key private_key.pem -k +//! ``` +//! #![no_std] #![no_main] @@ -130,6 +152,12 @@ fn main() -> ! { Mode::Server, TlsVersion::Tls1_2, Certificates { + // Provide a ca_chain if you want to enable mTLS for the server. + #[cfg(feature = "mtls")] + ca_chain: X509::pem( + concat!(include_str!("./certs/ca_cert.pem"), "\0").as_bytes(), + ) + .ok(), // Use self-signed certificates certificate: X509::pem( concat!(include_str!("./certs/certificate.pem"), "\0").as_bytes(), @@ -180,6 +208,9 @@ fn main() -> ! { .unwrap(); } } + Err(TlsError::NoClientCertificate) => { + println!("Error: No client certificates given. Please provide client certificates during your request"); + } Err(TlsError::MbedTlsError(-30592)) => { println!("Fatal message: Please enable the exception for a self-signed certificate in your browser"); } diff --git a/examples/sync_server_mTLS.rs b/examples/sync_server_mTLS.rs deleted file mode 100644 index 7b9cb9d..0000000 --- a/examples/sync_server_mTLS.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Example for a sync server. -//! Contains a basic server implementation to test mbedtls in server mode. -//! -//! This example is configured to use mTLS. If you send a request, without passing -//! certificates, you will get an error. Theses certificates below are generated -//! to work is the configured CA: -//! -//! certificate.pem -//! ```text -#![doc = include_str!("./certs/certificate.pem")] -//! ``` -//! -//! private_key.pem -//! ```text -#![doc = include_str!("./certs/private_key.pem")] -//! ``` -//! -//! Test with curl: -//! ```bash -//! curl https:/// --cert certificate.pem --key private_key.pem -k -//! ``` -#![no_std] -#![no_main] - -#[doc(hidden)] -pub use esp_hal as hal; - -use blocking_network_stack::Stack; - -use embedded_io::*; -use esp_backtrace as _; -use esp_mbedtls::{Certificates, Session}; -use esp_mbedtls::{Mode, Tls, TlsError, TlsVersion, X509}; -use esp_println::{logger::init_logger, print, println}; -use esp_wifi::{ - init, - wifi::{utils::create_network_interface, ClientConfiguration, Configuration, WifiStaDevice}, -}; -use hal::{clock::CpuClock, main, rng::Rng, time, timer::timg::TimerGroup}; -use smoltcp::iface::{SocketSet, SocketStorage}; - -const SSID: &str = env!("SSID"); -const PASSWORD: &str = env!("PASSWORD"); - -#[main] -fn main() -> ! { - init_logger(log::LevelFilter::Info); - let peripherals = esp_hal::init({ - let mut config = esp_hal::Config::default(); - config.cpu_clock = CpuClock::max(); - config - }); - - esp_alloc::heap_allocator!(115 * 1024); - - let timg0 = TimerGroup::new(peripherals.TIMG0); - - let mut rng = Rng::new(peripherals.RNG); - - let init = init(timg0.timer0, rng, peripherals.RADIO_CLK).unwrap(); - - let wifi = peripherals.WIFI; - - let (iface, device, mut controller) = - create_network_interface(&init, wifi, WifiStaDevice).unwrap(); - - let mut socket_set_entries: [SocketStorage; 3] = Default::default(); - let sockets = SocketSet::new(&mut socket_set_entries[..]); - - let now = || time::now().duration_since_epoch().to_millis(); - let wifi_stack = Stack::new(iface, device, sockets, now, rng.random()); - - println!("Call wifi_connect"); - let client_config = Configuration::Client(ClientConfiguration { - ssid: SSID.try_into().unwrap(), - password: PASSWORD.try_into().unwrap(), - ..Default::default() - }); - controller.set_configuration(&client_config).unwrap(); - controller.start().unwrap(); - controller.connect().unwrap(); - - println!("Wait to get connected"); - loop { - let res = controller.is_connected(); - match res { - Ok(connected) => { - if connected { - break; - } - } - Err(err) => { - println!("{:?}", err); - #[allow(clippy::empty_loop)] - loop {} - } - } - } - - // wait for getting an ip address - println!("Wait to get an ip address"); - loop { - wifi_stack.work(); - - if wifi_stack.is_iface_up() { - println!("Got ip {:?}", wifi_stack.get_ip_info()); - break; - } - } - - println!("We are connected!"); - - println!( - "Point your browser to https://{:?}/", - wifi_stack.get_ip_info().unwrap().ip - ); - let mut rx_buffer = [0u8; 1536]; - let mut tx_buffer = [0u8; 1536]; - let mut socket = wifi_stack.get_socket(&mut rx_buffer, &mut tx_buffer); - - socket.listen(443).unwrap(); - - let mut tls = Tls::new(peripherals.SHA) - .unwrap() - .with_hardware_rsa(peripherals.RSA); - - tls.set_debug(0); - - loop { - socket.work(); - - if !socket.is_open() { - socket.listen(443).unwrap(); - } - - if socket.is_connected() { - println!("New connection"); - - let mut time_out = false; - let wait_end = now() + 20 * 1000; - let mut buffer = [0u8; 1024]; - let mut pos = 0; - - let mut session = Session::new( - &mut socket, - Mode::Server, - TlsVersion::Tls1_2, - Certificates { - ca_chain: X509::pem( - concat!(include_str!("./certs/ca_cert.pem"), "\0").as_bytes(), - ) - .ok(), - // Use self-signed certificates - certificate: X509::pem( - concat!(include_str!("./certs/certificate.pem"), "\0").as_bytes(), - ) - .ok(), - private_key: X509::pem( - concat!(include_str!("./certs/private_key.pem"), "\0").as_bytes(), - ) - .ok(), - ..Default::default() - }, - tls.reference(), - ) - .unwrap(); - - match session.connect() { - Ok(_) => { - while let Ok(len) = session.read(&mut buffer[pos..]) { - let to_print = - unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) }; - - if to_print.contains("\r\n\r\n") { - print!("{}", to_print); - println!(); - break; - } - - pos += len; - - if now() > wait_end { - println!("Timed out"); - time_out = true; - break; - } - } - - if !time_out { - session - .write_all( - b"HTTP/1.0 200 OK\r\n\r\n\ - \ - \ -

Hello Rust! Hello esp-mbedtls!

\ - \ - \r\n\ - ", - ) - .unwrap(); - } - } - Err(TlsError::NoClientCertificate) => { - println!("Error: No client certificates given. Please provide client certificates during your request"); - } - Err(TlsError::MbedTlsError(-30592)) => { - println!("Fatal message: Please enable the exception for a self-signed certificate in your browser"); - } - Err(error) => { - panic!("{:?}", error); - } - } - - drop(session); - socket.close(); - - println!("Done\n"); - println!(); - } - - // This seems to delay after a connection. Removed to allow instant connections - // - // let wait_end = current_millis() + 5 * 1000; - // while current_millis() < wait_end { - // socket.work(); - // } - } -} diff --git a/justfile b/justfile index 3314205..3e379d6 100644 --- a/justfile +++ b/justfile @@ -7,13 +7,13 @@ all: (check "esp32" "esp") (check "esp32s3" "esp") (check "esp32c3" "nightly") [private] check arch toolchain: cargo +{{ toolchain }} b{{ arch }} --example sync_client --features="examples" - cargo +{{ toolchain }} b{{ arch }} --example sync_client_mTLS --features="examples" + cargo +{{ toolchain }} b{{ arch }} --example sync_client --features="examples, mtls" cargo +{{ toolchain }} b{{ arch }} --example async_client --features="examples-async" - cargo +{{ toolchain }} b{{ arch }} --example async_client_mTLS --features="examples-async" + cargo +{{ toolchain }} b{{ arch }} --example async_client --features="examples-async, mtls" cargo +{{ toolchain }} b{{ arch }} --example sync_server --features="examples" - cargo +{{ toolchain }} b{{ arch }} --example sync_server_mTLS --features="examples" + cargo +{{ toolchain }} b{{ arch }} --example sync_server --features="examples, mtls" cargo +{{ toolchain }} b{{ arch }} --example async_server --features="examples-async" - cargo +{{ toolchain }} b{{ arch }} --example async_server_mTLS --features="examples-async" + cargo +{{ toolchain }} b{{ arch }} --example async_server --features="examples-async, mtls" cargo +{{ toolchain }} b{{ arch }} --example edge_server --features="examples-async" cargo +{{ toolchain }} b{{ arch }} --example crypto_self_test --features="examples" cargo +{{ toolchain }} b --example crypto_self_test_std --features="examples-std" --target x86_64-unknown-linux-gnu -Z build-std=std,panic_abort