Skip to content

Commit

Permalink
Add support for validating server certificates
Browse files Browse the repository at this point in the history
* Add support for validating server certificates and signatures
* Add 'alloc' feature to support RSA-based certificate validation
* Support elliptic curve-based certificates without alloc
* Add configuration options for enabling/disabling cert and hostname validation
* Add TlsClock trait that if enabled will ensure cert validation check

Fixes #13
  • Loading branch information
lulf committed Oct 13, 2021
1 parent a177705 commit 7c88e33
Show file tree
Hide file tree
Showing 28 changed files with 734 additions and 142 deletions.
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ typenum = "1.13.0"
heapless = { version = "0.7", features = ["defmt-impl"] }
generic-array = "0.14"
nom = { version = "6.1.2", default-features = false }
#webpki = { version = "0.22.0", default-features = false }
#webpki = { path = "../../webpki", default-features = false }
webpki = { git = "https://github.com/lulf/webpki", rev = "d5188bd8c0a2c9cb14ec7835e63253e793e720a1", default-features = false }

# Logging alternatives
log = { version = "0.4", optional = true }
Expand All @@ -48,9 +51,11 @@ rustls-pemfile = "0.2.1"
serde = { version = "1.0", features = ["derive"] }
rand = "0.8"
log = "0.4"
pem-parser = "0.1.1"

[features]
default = ["std", "async", "tokio"]
default = ["std", "async", "tokio", "log", "alloc"]
alloc = ["webpki/alloc"]
std = []
async = []
defmt-trace = [ ]
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ The client supports both async and blocking modes. By default, the `async` and `

To use the async mode, import `drogue_tls::*`. To use the blocking mode, import `drogue_tls::blocking::*`.

Some features like certificate validation are still not implemented, have a look at [open issues](https://github.com/drogue-iot/drogue-tls/issues).
Some features and extensions are not yet implemented, have a look at [open issues](https://github.com/drogue-iot/drogue-tls/issues).

Only supports writing/receiving one frame at a time, hence using a frame buffer larger than 16k is not currently needed. You may use a lower frame buffer size, but there is no guarantee that it will be able to parse any TLS 1.3 frame.

Usage of this crate should fit in 20 kB of RAM assuming a frame buffer of 16 kB (max TLS record size). This is not including the space used to hold the CA and any client certificates, which is not yet supported.
Usage of this crate should fit in 21 kB of RAM assuming a frame buffer of 17 kB (max TLS record size + EC server certificate size). This is not including the space used to hold the CA and any client certificates.

Some memory usage statistics for async operation:

NOTE: This is very fresh and is probably not meeting all parts of the TLS 1.3 spec. Things like certificate validation and client certificate support is not complete.
If you find anything you'd like to get implemented, feel free to raise an issue.
* TlsConnection: frame_buffer size + 2kB for the rest. This can probably be reduced with some additional tuning.
* Handshake stack usage: currently at 2 kB
* Write stack usage: currently at 560 B
* Read stack usage: currently at 232 B

## Community

Expand Down
1 change: 1 addition & 0 deletions examples/blocking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ authors = [

[dependencies]
drogue-tls = { path = "../..", features = ["log", "std"], default-features = false }
pem-parser = "0.1"
env_logger = "0.8"
rand = "0.8"
log = "0.4"
11 changes: 9 additions & 2 deletions examples/blocking/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use drogue_tls::blocking::*;
use rand::rngs::OsRng;
use std::net::TcpStream;
use std::time::SystemTime;

const CA: &str = include_str!("/home/lulf/dev/rustls/rustls-mio/ca-cert.pem");

fn main() {
env_logger::init();
let stream = TcpStream::connect("127.0.0.1:12345").expect("error connecting to server");

let ca: Vec<u8> = pem_parser::pem_to_der(CA);

log::info!("Connected");
let mut record_buffer = [0; 16384];
let tls_context = TlsContext::new(OsRng, &mut record_buffer).with_server_name("example.com");
let mut tls: TlsConnection<OsRng, TcpStream, Aes128GcmSha256> =
let tls_context = TlsContext::new(OsRng, &mut record_buffer)
.with_server_name("localhost")
.with_ca(Certificate::X509(&ca[..]));
let mut tls: TlsConnection<OsRng, SystemTime, TcpStream, Aes128GcmSha256> =
TlsConnection::new(tls_context, stream);

tls.open().expect("error establishing TLS connection");
Expand Down
2 changes: 2 additions & 0 deletions examples/embassy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ pub struct Transport<W: AsyncWriteExt + AsyncBufReadExt + Unpin> {
transport: W,
}

pub struct Clock;

impl<W: AsyncWriteExt + AsyncBufReadExt + Unpin> AsyncWrite for Transport<W> {
#[rustfmt::skip]
type WriteFuture<'m> where Self: 'm = impl Future<Output = core::result::Result<usize, TlsError>> + 'm;
Expand Down
3 changes: 2 additions & 1 deletion examples/tokio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ drogue-tls = { path = "../..", features = ["log", "tokio"] }
env_logger = "0.8"
tokio = { version = "1.7", features = ["full"] }
rand = "0.8"
log = "0.4"
log = "0.4"
pem-parser = "0.1.1"
12 changes: 11 additions & 1 deletion examples/tokio/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,21 @@ use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();
let pem = include_str!("ca-cert.pem");
log::info!("Pem size: {}", pem.len());

let der = pem_parser::pem_to_der(pem);

log::info!("DER length: {}", der.len());
let stream = TcpStream::connect("127.0.0.1:12345").await?;

log::info!("Connected");
let mut record_buffer = [0; 16384];
let tls_context = TlsContext::new(OsRng, &mut record_buffer).with_server_name("example.com");
let tls_context = TlsContext::new(OsRng, &mut record_buffer)
.with_ca(Certificate::X509(&der[..]))
.with_server_name("localhost")
.verify_hostname(true)
.verify_cert(true);
let mut tls: TlsConnection<OsRng, TcpStream, Aes128GcmSha256> =
TlsConnection::new(tls_context, stream);

Expand Down
22 changes: 13 additions & 9 deletions src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ use crate::connection::*;
use crate::handshake::ServerHandshake;
use crate::key_schedule::KeySchedule;
use crate::record::{ClientRecord, ServerRecord};
use crate::{
traits::{Read, Write},
};
use crate::traits::{Read, Write};
use rand_core::{CryptoRng, RngCore};

use crate::application_data::ApplicationData;
Expand All @@ -20,32 +18,36 @@ const TLS_RECORD_OVERHEAD: usize = 128;
/// Type representing an async TLS connection. An instance of this type can
/// be used to establish a TLS connection, write and read encrypted data over this connection,
/// and closing to free up the underlying resources.
pub struct TlsConnection<'a, RNG, Socket, CipherSuite>
pub struct TlsConnection<'a, RNG, Clock, Socket, CipherSuite>
where
RNG: CryptoRng + RngCore + 'static,
Clock: TlsClock + 'static,
Socket: Read + Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
delegate: Socket,
rng: RNG,
clock: core::marker::PhantomData<&'a Clock>,
config: TlsConfig<'a, CipherSuite>,
key_schedule: KeySchedule<CipherSuite::Hash, CipherSuite::KeyLen, CipherSuite::IvLen>,
record_buf: &'a mut [u8],
opened: bool,
}

impl<'a, RNG, Socket, CipherSuite> TlsConnection<'a, RNG, Socket, CipherSuite>
impl<'a, RNG, Clock, Socket, CipherSuite> TlsConnection<'a, RNG, Clock, Socket, CipherSuite>
where
RNG: CryptoRng + RngCore + 'static,
Clock: TlsClock + 'static,
Socket: Read + Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
/// Create a new TLS connection with the provided context and a I/O implementation
pub fn new(context: TlsContext<'a, CipherSuite, RNG>, delegate: Socket) -> Self {
pub fn new(context: TlsContext<'a, CipherSuite, RNG, Clock>, delegate: Socket) -> Self {
Self {
delegate,
config: context.config,
rng: context.rng,
clock: core::marker::PhantomData,
opened: false,
key_schedule: KeySchedule::new(),
record_buf: context.record_buf,
Expand All @@ -65,7 +67,7 @@ where
let mut state = State::ClientHello;

loop {
let next_state = state.process_blocking(
let next_state = state.process_blocking::<_, _, _, Clock>(
&mut self.delegate,
&mut handshake,
&mut self.record_buf,
Expand Down Expand Up @@ -173,15 +175,17 @@ where
}

/// Close a connection instance, returning the ownership of the config, random generator and the I/O provider.
pub fn close(self) -> Result<(TlsContext<'a, CipherSuite, RNG>, Socket), TlsError> {
pub fn close(self) -> Result<(TlsContext<'a, CipherSuite, RNG, Clock>, Socket), TlsError> {
let record = ClientRecord::Alert(
Alert::new(AlertLevel::Warning, AlertDescription::CloseNotify),
self.opened);
self.opened,
);

let mut key_schedule = self.key_schedule;
let mut delegate = self.delegate;
let mut record_buf = self.record_buf;
let rng = self.rng;
let clock = self.clock;
let config = self.config;

let (_, len) = encode_record::<CipherSuite>(&mut record_buf, &mut key_schedule, &record)?;
Expand Down
18 changes: 18 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ impl<'b> CryptoBuffer<'b> {
}
}

pub fn push_u24(&mut self, num: u32) -> Result<(), TlsError> {
if self.capacity - (self.len + self.offset) > 2 {
let data = num.to_be_bytes();
self.extend_from_slice(&[data[0], data[1], data[2]])
} else {
Err(TlsError::InsufficientSpace)
}
}

pub fn set(&mut self, idx: usize, val: u8) -> Result<(), TlsError> {
if idx < self.len {
self.buf[self.offset + idx] = val;
Expand Down Expand Up @@ -150,6 +159,15 @@ impl<'b> Buffer for CryptoBuffer<'b> {
mod test {
use super::CryptoBuffer;

#[test]
fn encode() {
let mut buf = [0; 4];
let mut c = CryptoBuffer::wrap(&mut buf);
c.push_u24(1024).unwrap();
let decoded = u32::from_be_bytes(buf);
assert_eq!(1024, decoded);
}

#[test]
fn offset_calc() {
let mut buf = [0; 8];
Expand Down
Loading

0 comments on commit 7c88e33

Please sign in to comment.