Skip to content
Open
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
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ p256 = { version = "0.13", default-features = false, features = [
embedded-tls = { git = "https://github.com/drogue-iot/embedded-tls.git", default-features = false, features = ["rustpki"], optional = true }
rand_chacha = { version = "0.3", default-features = false }
nourl = "0.1.2"
esp-mbedtls = { version = "0.1", git = "https://github.com/esp-rs/esp-mbedtls.git", optional = true }
mbedtls-rs = { version = "0.1", git = "https://github.com/esp-rs/mbedtls-rs.git", optional = true }

[dev-dependencies]
hyper = { version = "0.14.23", features = ["full"] }
Expand All @@ -60,4 +60,5 @@ defmt = [
"embedded-tls?/defmt",
"nourl/defmt",
"heapless/defmt",
"mbedtls-rs?/defmt",
]
66 changes: 31 additions & 35 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` / `esp-mbedtls`) crates to establish TCP + TLS connections.
* A higher level `client` API which uses the `embedded-nal-async` (+ optional `embedded-tls` / `mbedtls-rs`) crates to establish TCP + TLS connections.

## example

Expand All @@ -30,7 +30,7 @@ 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 either `embedded-tls` or `esp-mbedtls` 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 `mbedtls-rs` (formerly known as `esp-mbedtls`) as the transport.

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

Expand All @@ -39,60 +39,56 @@ In addition to common headers like `.content_type()` on requests, broader `.head
If you are missing a feature or would like an improvement, please raise an issue or a PR.

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

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

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

:warning: The released version of `reqwless` does not support `esp-mbedtls`. The reason for this is that `esp-mbedtls` is not yet published to crates.io. One should specify `reqwless` as a git dependency to use `esp-mbedtls`.
:warning: The released version of `reqwless` does not support `mbedtls-rs`. The reason for this is that `mbedtls-rs` is not yet published to crates.io. One should specify `reqwless` as a git dependency to use `mbedtls-rs`.

### 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`
### mbedtls-rs
`mbedtls-rs` supports TLS 1.2 and 1.3. It uses [Espressif's Rust wrapper](https://github.com/esp-rs/mbedtls-rs) over [mbedtls](https://www.trustedfirmware.org/projects/mbed-tls/) and allows for optimizations such as hardware acceleration.

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


#### Example
```rust,ignore
/// ... [initialization code. See esp-wifi]
/// ... [initialization code. See esp-radio and mbedtls-rs examples]
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 dns_socket = DnsSocket::new(stack);

let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1);
let trng = Trng::try_new().unwrap();
let mbedtls_lib_instance = mbedtls_rs::Tls::new(&mut trng).unwrap();
// For debug messages:
// mbedtls_lib_instance.set_debug(1);

let tls_config = TlsConfig::new(
reqwless::TlsVersion::Tls1_2,
reqwless::Certificate::new(reqwless::X509::PEM(CERT)).unwrap(),
None, // optionally, add client certificate for mTLS
mbedtls_lib_instance.reference(),
);
let mut client = HttpClient::new_with_tls(&tcp_client, &dns_socket, config);

let header_buf = [0; 1024];

let client = HttpClient::new_with_tls(&tcp_client, &dns_socket, tls_config);

let mut request = client
.request(reqwless::request::Method::GET, "https://www.google.com")
.request(reqwless::request::Method::GET, "https://example.com")
.await
.unwrap()
.content_type(reqwless::headers::ContentType::TextPlain)
.headers(&[("Host", "google.com")])
.send(&mut buffer)
.await
.unwrap();
.headers(&[("Host", "example.com")]);
let response = request.send(&mut header_buf).await.unwrap();
```

### embedded-tls
Expand Down
69 changes: 40 additions & 29 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(feature = "mbedtls-rs")]
extern crate alloc;

use crate::Error;
/// Client using embedded-nal-async traits to establish connections and perform HTTP requests.
///
Expand Down Expand Up @@ -30,21 +33,24 @@ where
{
client: &'a T,
dns: &'a D,
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
#[cfg(any(feature = "embedded-tls", feature = "mbedtls-rs"))]
tls: Option<TlsConfig<'a>>,
}

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

/// Client certificates. See [esp_mbedtls::Certificates]
certificates: crate::Certificates<'a>,
/// Root certificates to trust. See [mbedtls_rs::Certificates]
certificates: crate::Certificate<'a>,

/// Client certificate and private key for mutual TLS. See [mbedtls_rs::Certificates]
client_credentials: Option<crate::Credentials<'a>>,

/// A reference to instance of the MbedTLS library.
tls_reference: esp_mbedtls::TlsReference<'a>,
tls_reference: mbedtls_rs::TlsReference<'a>,
}

/// Type for TLS configuration of HTTP client.
Expand Down Expand Up @@ -114,16 +120,18 @@ impl<'a> TlsConfig<'a> {
}
}

#[cfg(feature = "esp-mbedtls")]
#[cfg(feature = "mbedtls-rs")]
impl<'a, const RX_SIZE: usize, const TX_SIZE: usize> TlsConfig<'a, RX_SIZE, TX_SIZE> {
pub fn new(
version: crate::TlsVersion,
certificates: crate::Certificates<'a>,
certificates: crate::Certificate<'a>,
client_credentials: Option<crate::Credentials<'a>>,
tls_reference: crate::TlsReference<'a>,
) -> Self {
Self {
version,
certificates,
client_credentials,
tls_reference,
}
}
Expand All @@ -139,13 +147,13 @@ where
Self {
client,
dns,
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
#[cfg(any(feature = "embedded-tls", feature = "mbedtls-rs"))]
tls: None,
}
}

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

if url.scheme() == UrlScheme::HTTPS {
#[cfg(feature = "esp-mbedtls")]
#[cfg(feature = "mbedtls-rs")]
if let Some(tls) = self.tls.as_mut() {
let mut servername = host.as_bytes().to_vec();
servername.push(0);
let mut session = esp_mbedtls::asynch::Session::new(
conn,
esp_mbedtls::Mode::Client {
servername: unsafe { core::ffi::CStr::from_bytes_with_nul_unchecked(&servername) },
},
tls.version,
tls.certificates,
let mut session = mbedtls_rs::Session::new(
tls.tls_reference,
conn,
&mbedtls_rs::SessionConfig::Client(mbedtls_rs::ClientSessionConfig {
ca_chain: Some(tls.certificates.clone()),
creds: tls.client_credentials.clone(),
server_name: None, // don't set it here because it would reference a local variable
auth_mode: mbedtls_rs::AuthMode::Required,
min_version: tls.version,
}),
)?;
session.set_server_name(core::ffi::CStr::from_bytes_with_nul(&servername).unwrap())?;

session.connect().await?;
Ok(HttpConnection::Tls(session))
Expand Down Expand Up @@ -244,7 +255,7 @@ where
} else {
Ok(HttpConnection::Plain(conn))
}
#[cfg(all(not(feature = "embedded-tls"), not(feature = "esp-mbedtls")))]
#[cfg(all(not(feature = "embedded-tls"), not(feature = "mbedtls-rs")))]
Err(Error::InvalidUrl(nourl::Error::UnsupportedScheme))
} else {
#[cfg(feature = "embedded-tls")]
Expand Down Expand Up @@ -298,11 +309,11 @@ where
{
Plain(C),
PlainBuffered(BufferedWrite<'conn, C>),
#[cfg(feature = "esp-mbedtls")]
Tls(esp_mbedtls::asynch::Session<'conn, C>),
#[cfg(feature = "mbedtls-rs")]
Tls(mbedtls_rs::Session<'conn, C>),
#[cfg(feature = "embedded-tls")]
Tls(embedded_tls::TlsConnection<'conn, C, embedded_tls::Aes128GcmSha256>),
#[cfg(all(not(feature = "embedded-tls"), not(feature = "esp-mbedtls")))]
#[cfg(all(not(feature = "embedded-tls"), not(feature = "mbedtls-rs")))]
Tls((&'conn mut (), core::convert::Infallible)), // Variant is impossible to create, but we need it to avoid "unused lifetime" warning
}

Expand Down Expand Up @@ -400,13 +411,13 @@ where
writer.terminate().await.map_err(|e| e.kind())?;
buffered.clear();
}
#[cfg(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
#[cfg(any(feature = "embedded-tls", feature = "mbedtls-rs"))]
HttpConnection::Tls(c) => {
let mut writer = ChunkedBodyWriter::new(c);
body.write(&mut writer).await?;
writer.terminate().await.map_err(|e| e.kind())?;
}
#[cfg(all(not(feature = "embedded-tls"), not(feature = "esp-mbedtls")))]
#[cfg(all(not(feature = "embedded-tls"), not(feature = "mbedtls-rs")))]
HttpConnection::Tls(_) => unreachable!(),
};
}
Expand All @@ -431,9 +442,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(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
#[cfg(any(feature = "embedded-tls", feature = "mbedtls-rs"))]
Self::Tls(conn) => conn.read(buf).await.map_err(|e| e.kind()),
#[cfg(not(any(feature = "embedded-tls", feature = "esp-mbedtls")))]
#[cfg(not(any(feature = "embedded-tls", feature = "mbedtls-rs")))]
_ => unreachable!(),
}
}
Expand All @@ -447,9 +458,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(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
#[cfg(any(feature = "embedded-tls", feature = "mbedtls-rs"))]
Self::Tls(conn) => conn.write(buf).await.map_err(|e| e.kind()),
#[cfg(not(any(feature = "embedded-tls", feature = "esp-mbedtls")))]
#[cfg(not(any(feature = "embedded-tls", feature = "mbedtls-rs")))]
_ => unreachable!(),
}
}
Expand All @@ -458,9 +469,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(any(feature = "embedded-tls", feature = "esp-mbedtls"))]
#[cfg(any(feature = "embedded-tls", feature = "mbedtls-rs"))]
Self::Tls(conn) => conn.flush().await.map_err(|e| e.kind()),
#[cfg(not(any(feature = "embedded-tls", feature = "esp-mbedtls")))]
#[cfg(not(any(feature = "embedded-tls", feature = "mbedtls-rs")))]
_ => unreachable!(),
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub enum Error {
#[cfg(feature = "embedded-tls")]
Tls(embedded_tls::TlsError),
/// Tls Error
#[cfg(feature = "esp-mbedtls")]
Tls(esp_mbedtls::TlsError),
#[cfg(feature = "mbedtls-rs")]
Tls(mbedtls_rs::SessionError),
/// The provided buffer is too small
BufferTooSmall,
/// The request is already sent
Expand Down Expand Up @@ -83,12 +83,12 @@ 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, TlsReference, TlsVersion, X509};
#[cfg(feature = "mbedtls-rs")]
pub use mbedtls_rs::{Certificate, Credentials, TlsReference, TlsVersion, X509};

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