Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(esp-mbedtls): Add initial support for using esp-mbedtls in the client instead of embedded_tls #68

Merged
merged 1 commit into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading