Skip to content

Commit

Permalink
Merge pull request #68 from AnthonyGrondin/main
Browse files Browse the repository at this point in the history
feat(esp-mbedtls): Add initial support for using esp-mbedtls in the client instead of embedded_tls
  • Loading branch information
lulf committed Mar 4, 2024
2 parents d93adfd + da2fc3e commit 0e6eb16
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 15 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ defmt = { version = "0.3", optional = true }
embedded-tls = { version = "0.17", default-features = false, optional = true }
rand_chacha = { version = "0.3", default-features = false }
nourl = "0.1.1"
esp-mbedtls = { git = "https://github.com/esp-rs/esp-mbedtls.git", features = ["async"], optional = true }

[dev-dependencies]
hyper = { version = "0.14.23", features = ["full"] }
Expand Down
62 changes: 58 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ traits from the `embedded-io` crate. No alloc or std lib required!
It offers two sets of APIs:

* A low-level `request` API which allows you to construct HTTP requests and write them to a `embedded-io` transport.
* A higher level `client` API which uses the `embedded-nal-async` (+ optional `embedded-tls`) crates to establish TCP + TLS connections.
* A higher level `client` API which uses the `embedded-nal-async` (+ optional `embedded-tls` / `esp-mbedtls`) crates to establish TCP + TLS connections.

## example

Expand All @@ -30,14 +30,68 @@ let response = client
.unwrap();
```

The client is still lacking many features, but can perform basic HTTP GET/PUT/POST/DELETE requests with payloads. However, not all content types and status codes are implemented, and are added on a need basis. For TLS, it uses `embedded-tls` as the transport.
The client is still lacking many features, but can perform basic HTTP GET/PUT/POST/DELETE requests with payloads. However, not all content types and status codes are implemented, and are added on a need basis. For TLS, it uses either `embedded-tls` or `esp-mbedtls` as the transport.

NOTE: TLS verification is not supported in no_std environments for `embedded-tls`.

If you are missing a feature or would like an improvement, please raise an issue or a PR.

## TLS 1.3 and Supported Cipher Suites
`reqwless` uses `embedded-tls` to establish secure TLS connections for `https://..` urls.
## TLS 1.2*, 1.3 and Supported Cipher Suites
`reqwless` uses `embedded-tls` or `esp-mbedtls` to establish secure TLS connections for `https://..` urls.

*TLS 1.2 is only supported with `esp-mbedtls`

:warning: Note that both features cannot be used together and will cause a compilation error.

### esp-mbedtls
**Can only be used on esp32 boards**
`esp-mbedtls` supports TLS 1.2 and 1.3. It uses espressif's Rust wrapper over mbedtls, alongside optimizations such as hardware acceleration.

To use, you need to enable the transitive dependency of `esp-mbedtls` for your SoC.
Currently, the supported SoCs are:

- `esp32`
- `esp32c3`
- `esp32s2`
- `esp32s3`

Cargo.toml:

```toml
reqwless = { version = "0.12.0", default-features = false, features = ["esp-mbedtls", "log"] }
esp-mbedtls = { git = "https://github.com/esp-rs/esp-mbedtls.git", features = ["esp32s3"] }
```
<!-- TODO: Update this when esp-mbedtls switches to the unified hal -->

#### Example
```rust,ignore
/// ... [initialization code. See esp-wifi]
let state = TcpClientState::<1, 4096, 4096>::new();
let mut tcp_client = TcpClient::new(stack, &state);
let dns_socket = DnsSocket::new(&stack);
let mut rsa = Rsa::new(peripherals.RSA);
let config = TlsConfig::new(
reqwless::TlsVersion::Tls1_3,
reqwless::Certificates {
ca_chain: reqwless::X509::pem(CERT.as_bytes()).ok(),
..Default::default()
},
Some(&mut rsa), // Will use hardware acceleration
);
let mut client = HttpClient::new_with_tls(&tcp_client, &dns_socket, config);
let mut request = client
.request(reqwless::request::Method::GET, "https://www.google.com")
.await
.unwrap()
.content_type(reqwless::headers::ContentType::TextPlain)
.headers(&[("Host", "google.com")])
.send(&mut buffer)
.await
.unwrap();
```

### embedded-tls
`embedded-tls` only supports TLS 1.3, so to establish a connection the server must have this ssl protocol enabled.

An addition to the tls version requirement, there is also a negotiation of supported algorithms during the establishing phase of the secure communication between the client and server.
Expand Down
70 changes: 59 additions & 11 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,23 @@ where
{
client: &'a T,
dns: &'a D,
#[cfg(feature = "embedded-tls")]
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
tls: Option<TlsConfig<'a>>,
}

/// Type for TLS configuration of HTTP client.
#[cfg(feature = "esp-mbedtls")]
pub struct TlsConfig<'a> {
/// Minimum TLS version for the connection
version: crate::TlsVersion,

/// Client certificates. See [esp_mbedtls::Certificates]
certificates: crate::Certificates<'a>,

/// Will use hardware acceleration on the ESP32 if it contains the RSA peripheral.
rsa: Option<&'a mut esp_mbedtls::Rsa<'a>>,
}

/// Type for TLS configuration of HTTP client.
#[cfg(feature = "embedded-tls")]
pub struct TlsConfig<'a> {
Expand Down Expand Up @@ -54,6 +67,21 @@ impl<'a> TlsConfig<'a> {
}
}

#[cfg(feature = "esp-mbedtls")]
impl<'a> TlsConfig<'a> {
pub fn new(
version: crate::TlsVersion,
certificates: crate::Certificates<'a>,
rsa: Option<&'a mut esp_mbedtls::Rsa<'a>>,
) -> Self {
Self {
version,
certificates,
rsa,
}
}
}

impl<'a, T, D> HttpClient<'a, T, D>
where
T: TcpConnect + 'a,
Expand All @@ -64,13 +92,13 @@ where
Self {
client,
dns,
#[cfg(feature = "embedded-tls")]
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
tls: None,
}
}

/// Create a new HTTP client for a given connection handle and a target host.
#[cfg(feature = "embedded-tls")]
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
pub fn new_with_tls(client: &'a T, dns: &'a D, tls: TlsConfig<'a>) -> Self {
Self {
client,
Expand Down Expand Up @@ -99,6 +127,24 @@ where
.map_err(|e| e.kind())?;

if url.scheme() == UrlScheme::HTTPS {
#[cfg(feature = "esp-mbedtls")]
if let Some(tls) = self.tls.as_mut() {
let session = esp_mbedtls::asynch::Session::new(
conn,
host,
esp_mbedtls::Mode::Client,
tls.version,
tls.certificates,
// Create a inner Some(&mut Rsa) because Rsa doesn't implement Copy
tls.rsa.as_mut().map(|inner| inner as &mut esp_mbedtls::Rsa),
)?
.connect()
.await?;
Ok(HttpConnection::Tls(session))
} else {
Ok(HttpConnection::Plain(conn))
}

#[cfg(feature = "embedded-tls")]
if let Some(tls) = self.tls.as_mut() {
use embedded_tls::{TlsConfig, TlsContext};
Expand All @@ -118,7 +164,7 @@ where
} else {
Ok(HttpConnection::Plain(conn))
}
#[cfg(not(feature = "embedded-tls"))]
#[cfg(all(not(feature = "embedded-tls"), not(feature = "esp-mbedtls")))]
Err(Error::InvalidUrl(nourl::Error::UnsupportedScheme))
} else {
#[cfg(feature = "embedded-tls")]
Expand Down Expand Up @@ -172,9 +218,11 @@ where
{
Plain(C),
PlainBuffered(BufferedWrite<'conn, C>),
#[cfg(feature = "esp-mbedtls")]
Tls(esp_mbedtls::asynch::AsyncConnectedSession<C, 4096>),
#[cfg(feature = "embedded-tls")]
Tls(embedded_tls::TlsConnection<'conn, C, embedded_tls::Aes128GcmSha256>),
#[cfg(not(feature = "embedded-tls"))]
#[cfg(all(not(feature = "embedded-tls"), not(feature = "esp-mbedtls")))]
Tls((&'conn mut (), core::convert::Infallible)), // Variant is impossible to create, but we need it to avoid "unused lifetime" warning
}

Expand Down Expand Up @@ -255,9 +303,9 @@ where
match self {
Self::Plain(conn) => conn.read(buf).await.map_err(|e| e.kind()),
Self::PlainBuffered(conn) => conn.read(buf).await.map_err(|e| e.kind()),
#[cfg(feature = "embedded-tls")]
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
Self::Tls(conn) => conn.read(buf).await.map_err(|e| e.kind()),
#[cfg(not(feature = "embedded-tls"))]
#[cfg(not(any(feature = "embedded-tls", feature = "esp-mbedtls")))]
_ => unreachable!(),
}
}
Expand All @@ -271,9 +319,9 @@ where
match self {
Self::Plain(conn) => conn.write(buf).await.map_err(|e| e.kind()),
Self::PlainBuffered(conn) => conn.write(buf).await.map_err(|e| e.kind()),
#[cfg(feature = "embedded-tls")]
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
Self::Tls(conn) => conn.write(buf).await.map_err(|e| e.kind()),
#[cfg(not(feature = "embedded-tls"))]
#[cfg(not(any(feature = "embedded-tls", feature = "esp-mbedtls")))]
_ => unreachable!(),
}
}
Expand All @@ -282,9 +330,9 @@ where
match self {
Self::Plain(conn) => conn.flush().await.map_err(|e| e.kind()),
Self::PlainBuffered(conn) => conn.flush().await.map_err(|e| e.kind()),
#[cfg(feature = "embedded-tls")]
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
Self::Tls(conn) => conn.flush().await.map_err(|e| e.kind()),
#[cfg(not(feature = "embedded-tls"))]
#[cfg(not(any(feature = "embedded-tls", feature = "esp-mbedtls")))]
_ => unreachable!(),
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub enum Error {
/// Tls Error
#[cfg(feature = "embedded-tls")]
Tls(embedded_tls::TlsError),
/// Tls Error
#[cfg(feature = "esp-mbedtls")]
Tls(esp_mbedtls::TlsError),
/// The provided buffer is too small
BufferTooSmall,
/// The request is already sent
Expand Down Expand Up @@ -70,6 +73,17 @@ impl From<embedded_tls::TlsError> for Error {
}
}

/// Re-export those members since they're used for [client::TlsConfig].
#[cfg(feature = "esp-mbedtls")]
pub use esp_mbedtls::{Certificates, Rsa, TlsVersion, X509};

#[cfg(feature = "esp-mbedtls")]
impl From<esp_mbedtls::TlsError> for Error {
fn from(e: esp_mbedtls::TlsError) -> Error {
Error::Tls(e)
}
}

impl From<ParseIntError> for Error {
fn from(_: ParseIntError) -> Error {
Error::Codec
Expand Down

0 comments on commit 0e6eb16

Please sign in to comment.