-
Notifications
You must be signed in to change notification settings - Fork 251
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
Add Rustls provider including examples #1899
Open
bjoernQ
wants to merge
5
commits into
esp-rs:main
Choose a base branch
from
bjoernQ:rustls-example
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
[package] | ||
name = "esp-rustls-provider" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
chacha20poly1305 = { version = "0.10", default-features = false, features = [ | ||
"alloc", | ||
] } | ||
der = "0.7" | ||
ecdsa = "0.16.8" | ||
hmac = "0.12" | ||
p256 = { version = "0.13.2", default-features = false, features = [ | ||
"alloc", | ||
"ecdsa", | ||
"pkcs8", | ||
] } | ||
pkcs8 = "0.10.2" | ||
pki-types = { package = "rustls-pki-types", version = "1" } | ||
rand_core = { version = "0.6", default-features = false } | ||
|
||
rustls = { version = "0.23.21", default-features = false, features = ["tls12", "custom-provider"] } | ||
rsa = { version = "0.9", features = ["sha2"], default-features = false } | ||
sha2 = { version = "0.10", default-features = false } | ||
signature = "2" | ||
webpki = { package = "rustls-webpki", version = "0.102", features = [ | ||
"alloc", | ||
], default-features = false } | ||
x25519-dalek = "2" | ||
|
||
# should this be a feature? - not really needed by this crate but re-exported for convenience | ||
webpki-roots = "0.26.1" | ||
|
||
esp-hal = { path = "../esp-hal", version = "0.23.1", default-features = false } | ||
embedded-io = { version = "0.6.1", default-features = false } | ||
log = "0.4.20" | ||
|
||
[features] | ||
log = ["rustls/logging"] | ||
defmt = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# esp-rustls-provider | ||
|
||
## NO support for targets w/o atomics | ||
|
||
While most dependencies can be used with `portable-atomic` that's unfortunately not true for Rustls itself. It needs `alloc::sync::Arc` in a lot of places. | ||
|
||
This means that ESP32-S2, ESP32-C2 and ESP32-C3 are NOT supported. | ||
|
||
## Status | ||
|
||
This crate is currently experimental/preview. It's not available on crates.io and might be limited in functionality. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
//! Client connection wrappers | ||
|
||
use super::ConnectionError; | ||
|
||
/// Wrapper for [embedded_io] to be used as a client connection | ||
pub struct ClientConnection<'s, S> | ||
where | ||
S: embedded_io::Read + embedded_io::Write, | ||
{ | ||
socket: S, | ||
conn: rustls::client::UnbufferedClientConnection, | ||
incoming_tls: &'s mut [u8], | ||
outgoing_tls: &'s mut [u8], | ||
incoming_used: usize, | ||
outgoing_used: usize, | ||
|
||
plaintext_in: &'s mut [u8], | ||
plaintext_in_used: usize, | ||
plaintext_out: &'s mut [u8], | ||
plaintext_out_used: usize, | ||
} | ||
|
||
impl<'s, S> ClientConnection<'s, S> | ||
where | ||
S: embedded_io::Read + embedded_io::Write, | ||
{ | ||
pub fn new( | ||
config: alloc::sync::Arc<rustls::client::ClientConfig>, | ||
server: rustls::pki_types::ServerName<'static>, | ||
socket: S, | ||
incoming_tls: &'s mut [u8], | ||
outgoing_tls: &'s mut [u8], | ||
plaintext_in: &'s mut [u8], | ||
plaintext_out: &'s mut [u8], | ||
) -> Result<Self, (S, ConnectionError<S::Error>)> { | ||
match rustls::client::UnbufferedClientConnection::new(config, server) { | ||
Ok(conn) => Ok(Self { | ||
socket, | ||
conn, | ||
incoming_tls, | ||
outgoing_tls, | ||
incoming_used: 0, | ||
outgoing_used: 0, | ||
|
||
plaintext_in, | ||
plaintext_in_used: 0, | ||
plaintext_out, | ||
plaintext_out_used: 0, | ||
}), | ||
Err(err) => Err((socket, ConnectionError::Rustls(err))), | ||
} | ||
} | ||
|
||
pub fn free(self) -> S { | ||
self.socket | ||
} | ||
|
||
fn work(&mut self) -> Result<(), ConnectionError<S::Error>> { | ||
use rustls::unbuffered::{AppDataRecord, ConnectionState}; | ||
|
||
let mut done = false; | ||
loop { | ||
if done { | ||
debug!("Done work for now"); | ||
break; | ||
} | ||
|
||
debug!( | ||
"Incoming used {}, outgoing used {}, plaintext_in used {}, plaintext_out used {}", | ||
self.incoming_used, | ||
self.outgoing_used, | ||
self.plaintext_in_used, | ||
self.plaintext_out_used | ||
); | ||
|
||
let rustls::unbuffered::UnbufferedStatus { mut discard, state } = self | ||
.conn | ||
.process_tls_records(&mut self.incoming_tls[..self.incoming_used]); | ||
|
||
debug!("State {:?}", state); | ||
|
||
match state.map_err(ConnectionError::Rustls)? { | ||
ConnectionState::ReadTraffic(mut state) => { | ||
while let Some(res) = state.next_record() { | ||
let AppDataRecord { | ||
discard: new_discard, | ||
payload, | ||
} = res.map_err(ConnectionError::Rustls)?; | ||
discard += new_discard; | ||
|
||
self.plaintext_in[self.plaintext_in_used..][..payload.len()] | ||
.copy_from_slice(payload); | ||
self.plaintext_in_used += payload.len(); | ||
|
||
done = true; | ||
} | ||
} | ||
|
||
ConnectionState::EncodeTlsData(mut state) => { | ||
let written = state | ||
.encode(&mut self.outgoing_tls[self.outgoing_used..]) | ||
.map_err(ConnectionError::RustlsEncodeError)?; | ||
self.outgoing_used += written; | ||
} | ||
|
||
ConnectionState::TransmitTlsData(mut state) => { | ||
if let Some(_may_encrypt_early_data) = state.may_encrypt_early_data() { | ||
panic!("Early data unsupported"); | ||
} | ||
|
||
if let Some(_may_encrypt) = state.may_encrypt_app_data() { | ||
debug!("time to sent app data") | ||
} | ||
|
||
debug!("Send tls"); | ||
self.socket | ||
.write_all(&self.outgoing_tls[..self.outgoing_used])?; | ||
self.socket.flush()?; | ||
self.outgoing_used = 0; | ||
state.done(); | ||
} | ||
|
||
ConnectionState::BlockedHandshake { .. } => { | ||
debug!("Receive tls"); | ||
|
||
let read = self | ||
.socket | ||
.read(&mut self.incoming_tls[self.incoming_used..])?; | ||
debug!("Received {read}B of data"); | ||
self.incoming_used += read; | ||
} | ||
|
||
ConnectionState::WriteTraffic(mut may_encrypt) => { | ||
if self.plaintext_out_used != 0 { | ||
let written = may_encrypt | ||
.encrypt( | ||
&self.plaintext_out[..self.plaintext_out_used], | ||
&mut self.outgoing_tls[self.outgoing_used..], | ||
) | ||
.expect("encrypted request does not fit in `outgoing_tls`"); | ||
self.outgoing_used += written; | ||
self.plaintext_out_used = 0; | ||
|
||
debug!("Send tls"); | ||
self.socket | ||
.write_all(&self.outgoing_tls[..self.outgoing_used])?; | ||
self.socket.flush()?; | ||
self.outgoing_used = 0; | ||
} | ||
done = true; | ||
} | ||
|
||
ConnectionState::Closed => { | ||
// connection has been cleanly closed - these might be still plaintext data to | ||
// be consumed | ||
done = true; | ||
} | ||
|
||
// other states are not expected here | ||
_ => unreachable!(), | ||
} | ||
|
||
if discard != 0 { | ||
assert!(discard <= self.incoming_used); | ||
|
||
self.incoming_tls | ||
.copy_within(discard..self.incoming_used, 0); | ||
self.incoming_used -= discard; | ||
|
||
debug!("Discarded {discard}B from `incoming_tls`"); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
impl<S> embedded_io::ErrorType for ClientConnection<'_, S> | ||
where | ||
S: embedded_io::Read + embedded_io::Write, | ||
{ | ||
type Error = ConnectionError<S::Error>; | ||
} | ||
|
||
impl<S> embedded_io::Read for ClientConnection<'_, S> | ||
where | ||
S: embedded_io::Read + embedded_io::Write, | ||
{ | ||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> { | ||
let tls_read_res = self | ||
.socket | ||
.read(&mut self.incoming_tls[self.incoming_used..]); | ||
|
||
let tls_read = if let Err(err) = tls_read_res { | ||
if self.plaintext_in_used == 0 { | ||
Err(err) | ||
} else { | ||
Ok(0) | ||
} | ||
} else { | ||
tls_read_res | ||
}?; | ||
self.incoming_used += tls_read; | ||
|
||
self.work()?; | ||
|
||
let l = usize::min(buf.len(), self.plaintext_in_used); | ||
buf[0..l].copy_from_slice(&self.plaintext_in[0..l]); | ||
|
||
self.plaintext_in.copy_within(l..self.plaintext_in_used, 0); | ||
self.plaintext_in_used -= l; | ||
|
||
Ok(l) | ||
} | ||
} | ||
|
||
impl<S> embedded_io::Write for ClientConnection<'_, S> | ||
where | ||
S: embedded_io::Read + embedded_io::Write, | ||
{ | ||
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> { | ||
self.plaintext_out[self.plaintext_out_used..][..buf.len()].copy_from_slice(buf); | ||
self.plaintext_out_used += buf.len(); | ||
self.work()?; | ||
Ok(buf.len()) | ||
} | ||
|
||
fn flush(&mut self) -> Result<(), Self::Error> { | ||
self.socket.flush()?; | ||
self.work()?; | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
//! Useful wrappers | ||
|
||
pub mod client; | ||
pub mod server; | ||
|
||
/// Errors returned by the adapters | ||
#[derive(Debug)] | ||
pub enum ConnectionError<E: embedded_io::Error> { | ||
/// Error from embedded-io | ||
Io(E), | ||
/// Error from Rustls | ||
Rustls(rustls::Error), | ||
/// Error from Rustls' `encode`function | ||
RustlsEncodeError(rustls::unbuffered::EncodeError), | ||
} | ||
|
||
impl<E> embedded_io::Error for ConnectionError<E> | ||
where | ||
E: embedded_io::Error, | ||
{ | ||
fn kind(&self) -> embedded_io::ErrorKind { | ||
match self { | ||
ConnectionError::Io(err) => err.kind(), | ||
_ => embedded_io::ErrorKind::Other, | ||
} | ||
} | ||
} | ||
|
||
impl<E> From<E> for ConnectionError<E> | ||
where | ||
E: embedded_io::Error, | ||
{ | ||
fn from(value: E) -> Self { | ||
Self::Io(value) | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we remove this and break it out into issues instead? I think we'll need a new label for the
esp-rustls-provider
package.My fear is that we'll never check this again :D, if we have it in separate issues we can at least be aware.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can create the issues after the PR is merged of course, there is no rush to do it now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the TODOs here - we shouldn't forget about creating separate issues after the merge (since creating them before is kind of weird?)