diff --git a/Cargo.toml b/Cargo.toml index ad7f37c5..a633aa48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } @@ -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 = [ ] diff --git a/README.md b/README.md index fd8ce6b4..13aa07cb 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/examples/blocking/Cargo.toml b/examples/blocking/Cargo.toml index 5fe79eef..bce49140 100644 --- a/examples/blocking/Cargo.toml +++ b/examples/blocking/Cargo.toml @@ -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" diff --git a/examples/blocking/src/main.rs b/examples/blocking/src/main.rs index ca919f84..ccf9bfff 100644 --- a/examples/blocking/src/main.rs +++ b/examples/blocking/src/main.rs @@ -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 = 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 = + let tls_context = TlsContext::new(OsRng, &mut record_buffer) + .with_server_name("localhost") + .with_ca(Certificate::X509(&ca[..])); + let mut tls: TlsConnection = TlsConnection::new(tls_context, stream); tls.open().expect("error establishing TLS connection"); diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index 0706859a..ddd17e5a 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -130,6 +130,8 @@ pub struct Transport { transport: W, } +pub struct Clock; + impl AsyncWrite for Transport { #[rustfmt::skip] type WriteFuture<'m> where Self: 'm = impl Future> + 'm; diff --git a/examples/tokio/Cargo.toml b/examples/tokio/Cargo.toml index 5a939eea..79d27010 100644 --- a/examples/tokio/Cargo.toml +++ b/examples/tokio/Cargo.toml @@ -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" \ No newline at end of file +log = "0.4" +pem-parser = "0.1.1" \ No newline at end of file diff --git a/examples/tokio/src/main.rs b/examples/tokio/src/main.rs index 6376bb2b..e9bd97f9 100644 --- a/examples/tokio/src/main.rs +++ b/examples/tokio/src/main.rs @@ -10,11 +10,21 @@ use tokio::net::TcpStream; #[tokio::main] async fn main() -> Result<(), Box> { 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 = TlsConnection::new(tls_context, stream); diff --git a/src/blocking.rs b/src/blocking.rs index 02df722c..b6bf69c3 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -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; @@ -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, 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, @@ -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, @@ -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::(&mut record_buf, &mut key_schedule, &record)?; diff --git a/src/buffer.rs b/src/buffer.rs index 0c8510c8..4426831f 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -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; @@ -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]; diff --git a/src/config.rs b/src/config.rs index 3a0acbfe..f8d4b363 100644 --- a/src/config.rs +++ b/src/config.rs @@ -47,24 +47,43 @@ where pub(crate) signature_schemes: Vec, pub(crate) named_groups: Vec, pub(crate) max_fragment_length: MaxFragmentLength, + pub(crate) ca: Option>, + pub(crate) cert: Option>, + pub(crate) verify_host: bool, + pub(crate) verify_cert: bool, +} + +pub trait TlsClock { + fn now() -> Option; +} + +pub struct NoClock; + +impl TlsClock for NoClock { + fn now() -> Option { + None + } } #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TlsContext<'a, CipherSuite, RNG> +pub struct TlsContext<'a, CipherSuite, RNG, Clock> where CipherSuite: TlsCipherSuite, RNG: CryptoRng + RngCore + 'static, + Clock: TlsClock + 'static, { pub(crate) config: TlsConfig<'a, CipherSuite>, pub(crate) rng: RNG, + clock: core::marker::PhantomData<&'a Clock>, pub(crate) record_buf: &'a mut [u8], } -impl<'a, CipherSuite, RNG> TlsContext<'a, CipherSuite, RNG> +impl<'a, CipherSuite, RNG, Clock> TlsContext<'a, CipherSuite, RNG, Clock> where CipherSuite: TlsCipherSuite, RNG: CryptoRng + RngCore + 'static, + Clock: TlsClock + 'static, { /// Create a new context with a given random number generator, record buffer and config. /// @@ -81,6 +100,7 @@ where Self { config, rng, + clock: core::marker::PhantomData, record_buf, } } @@ -99,6 +119,30 @@ where self.config = self.config.with_server_name(server_name); self } + + /// Enable/disable verification of server certificate. + pub fn verify_cert(mut self, verify_cert: bool) -> Self { + self.config = self.config.verify_cert(verify_cert); + self + } + + /// Enable/disable verification of server hostname. + pub fn verify_hostname(mut self, verify_hostname: bool) -> Self { + self.config = self.config.verify_hostname(verify_hostname); + self + } + + /// Trust the provided CA. + pub fn with_ca(mut self, ca: Certificate<'a>) -> Self { + self.config = self.config.with_ca(ca); + self + } + + /// Use provided cert as client certificate. + pub fn with_cert(mut self, cert: Certificate<'a>) -> Self { + self.config = self.config.with_cert(cert); + self + } } impl<'a, CipherSuite> TlsConfig<'a, CipherSuite> @@ -112,21 +156,53 @@ where named_groups: Vec::new(), max_fragment_length: MaxFragmentLength::Bits10, server_name: None, + verify_cert: true, + verify_host: true, + ca: None, + cert: None, }; //config.cipher_suites.push(CipherSuite::TlsAes128GcmSha256); + #[cfg(feature = "alloc")] + { + config + .signature_schemes + .push(SignatureScheme::RsaPkcs1Sha256) + .unwrap(); + config + .signature_schemes + .push(SignatureScheme::RsaPkcs1Sha384) + .unwrap(); + config + .signature_schemes + .push(SignatureScheme::RsaPkcs1Sha512) + .unwrap(); + config + .signature_schemes + .push(SignatureScheme::RsaPssRsaeSha256) + .unwrap(); + config + .signature_schemes + .push(SignatureScheme::RsaPssRsaeSha384) + .unwrap(); + config + .signature_schemes + .push(SignatureScheme::RsaPssRsaeSha512) + .unwrap(); + } + config .signature_schemes - .push(SignatureScheme::RsaPssRsaeSha256) + .push(SignatureScheme::EcdsaSecp256r1Sha256) .unwrap(); config .signature_schemes - .push(SignatureScheme::RsaPssRsaeSha384) + .push(SignatureScheme::EcdsaSecp384r1Sha384) .unwrap(); config .signature_schemes - .push(SignatureScheme::RsaPssRsaeSha512) + .push(SignatureScheme::Ed25519) .unwrap(); config.named_groups.push(NamedGroup::Secp256r1).unwrap(); @@ -138,6 +214,26 @@ where self.server_name = Some(server_name); self } + + pub fn verify_hostname(mut self, verify_host: bool) -> Self { + self.verify_host = verify_host; + self + } + + pub fn verify_cert(mut self, verify_cert: bool) -> Self { + self.verify_cert = verify_cert; + self + } + + pub fn with_ca(mut self, ca: Certificate<'a>) -> Self { + self.ca = Some(ca); + self + } + + pub fn with_cert(mut self, cert: Certificate<'a>) -> Self { + self.cert = Some(cert); + self + } } impl<'a, CipherSuite> Default for TlsConfig<'a, CipherSuite> @@ -148,3 +244,10 @@ where TlsConfig::new() } } + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Certificate<'a> { + X509(&'a [u8]), + RawPublicKey(&'a [u8]), +} diff --git a/src/connection.rs b/src/connection.rs index a7349be4..b47e1559 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,13 +1,21 @@ -use crate::config::{TlsCipherSuite, TlsConfig}; +use crate::config::{TlsCipherSuite, TlsClock, TlsConfig}; use crate::handshake::{ClientHandshake, ServerHandshake}; use crate::key_schedule::KeySchedule; use crate::record::{ClientRecord, RecordHeader, ServerRecord}; -use crate::{alert::*, handshake::certificate::Certificate}; +use crate::verify::{verify_certificate, verify_signature}; +use crate::{ + alert::*, + handshake::{ + certificate::{Certificate, CertificateRef}, + certificate_request::CertificateRequest, + }, +}; use crate::{ traits::{Read, Write}, TlsError, }; -use core::fmt::Debug; +use core::{convert::TryInto, fmt::Debug}; +use heapless::Vec; use rand_core::{CryptoRng, RngCore}; #[cfg(feature = "async")] @@ -264,6 +272,9 @@ where { traffic_hash: Option, secret: Option, + certificate_transcript: Option, + certificate_request: Option, + certificate: Option, } impl<'a, CipherSuite> Handshake @@ -274,6 +285,9 @@ where Handshake { traffic_hash: None, secret: None, + certificate_transcript: None, + certificate: None, + certificate_request: None, } } } @@ -290,7 +304,7 @@ pub enum State { impl<'a> State { #[cfg(feature = "async")] - pub async fn process( + pub async fn process( self, transport: &mut Transport, handshake: &mut Handshake, @@ -303,6 +317,7 @@ impl<'a> State { Transport: AsyncRead + AsyncWrite + 'a, RNG: CryptoRng + RngCore + 'static, CipherSuite: TlsCipherSuite + 'static, + Clock: TlsClock + 'static, { match self { State::ClientHello => { @@ -338,14 +353,30 @@ impl<'a> State { decode_record::(transport, record_buf, key_schedule) .await?; - Ok(process_server_verify(handshake, key_schedule, record)?) + Ok(process_server_verify::<_, Clock>( + handshake, + key_schedule, + config, + record, + )?) } State::ClientCert => { handshake .traffic_hash .replace(key_schedule.transcript_hash().clone()); - let client_handshake = ClientHandshake::ClientCert(Certificate::new()); + let request_context = handshake + .certificate + .as_ref() + .ok_or(TlsError::InvalidHandshake)? + .request_context(); + + let mut certificate = CertificateRef::with_context(request_context); + + if let Some(cert) = &config.cert { + certificate.add(cert.into())?; + } + let client_handshake = ClientHandshake::ClientCert(certificate); let client_cert: ClientRecord<'a, '_, CipherSuite> = ClientRecord::Handshake(client_handshake, true); @@ -381,7 +412,7 @@ impl<'a> State { } } - pub fn process_blocking( + pub fn process_blocking( self, transport: &mut Transport, handshake: &mut Handshake, @@ -394,6 +425,7 @@ impl<'a> State { Transport: Read + Write + 'a, RNG: CryptoRng + RngCore + 'static, CipherSuite: TlsCipherSuite + 'static, + Clock: TlsClock + 'static, { match self { State::ClientHello => { @@ -433,14 +465,29 @@ impl<'a> State { key_schedule, )?; - Ok(process_server_verify(handshake, key_schedule, record)?) + Ok(process_server_verify::<_, Clock>( + handshake, + key_schedule, + config, + record, + )?) } State::ClientCert => { handshake .traffic_hash .replace(key_schedule.transcript_hash().clone()); - let client_handshake = ClientHandshake::ClientCert(Certificate::new()); + let request_context = handshake + .certificate + .as_ref() + .ok_or(TlsError::InvalidHandshake)? + .request_context(); + + let mut certificate = CertificateRef::with_context(request_context); + if let Some(cert) = &config.cert { + certificate.add(cert.into())?; + } + let client_handshake = ClientHandshake::ClientCert(certificate); let client_cert: ClientRecord<'a, '_, CipherSuite> = ClientRecord::Handshake(client_handshake, true); @@ -499,18 +546,23 @@ where } _ => Err(TlsError::InvalidHandshake), }, + ServerRecord::Alert(alert) => { + Err(TlsError::HandshakeAborted(alert.level, alert.description)) + } _ => Err(TlsError::InvalidRecord), } } } -fn process_server_verify( +fn process_server_verify<'a, CipherSuite, Clock>( handshake: &mut Handshake, key_schedule: &mut KeySchedule, + config: &TlsConfig<'a, CipherSuite>, record: ServerRecord<'_, ::OutputSize>, ) -> Result where CipherSuite: TlsCipherSuite + 'static, + Clock: TlsClock + 'static, { let mut records = Queue::new(); let mut cert_requested = false; @@ -522,10 +574,34 @@ where let result = match record { ServerRecord::Handshake(server_handshake) => match server_handshake { ServerHandshake::EncryptedExtensions(_) => Ok(State::ServerVerify), - ServerHandshake::Certificate(_) => Ok(State::ServerVerify), - ServerHandshake::CertificateVerify(_) => Ok(State::ServerVerify), - ServerHandshake::CertificateRequest(_) => { + ServerHandshake::Certificate(certificate) => { + trace!("Verifying certificate!"); + verify_certificate(config, &certificate, Clock::now())?; + handshake.certificate.replace(certificate.try_into()?); + handshake + .certificate_transcript + .replace(key_schedule.transcript_hash().clone()); + trace!("Certificate verified!"); + Ok(State::ServerVerify) + } + ServerHandshake::CertificateVerify(verify) => { + let certificate = handshake.certificate.as_ref().unwrap().try_into()?; + let handshake_hash = handshake.certificate_transcript.take().unwrap(); + let ctx_str = b"TLS 1.3, server CertificateVerify\x00"; + let mut msg: Vec = Vec::new(); + msg.resize(64, 0x20u8).map_err(|_| TlsError::EncodeError)?; + msg.extend_from_slice(ctx_str) + .map_err(|_| TlsError::EncodeError)?; + msg.extend_from_slice(&handshake_hash.finalize()) + .map_err(|_| TlsError::EncodeError)?; + verify_signature(config, &msg[..], certificate, verify)?; + Ok(State::ServerVerify) + } + ServerHandshake::CertificateRequest(request) => { + trace!("Certificate request"); + handshake.certificate_request.replace(request.into()); cert_requested = true; + trace!("Certificate requested: {}", cert_requested); Ok(State::ServerVerify) } ServerHandshake::Finished(finished) => { diff --git a/src/handshake/certificate.rs b/src/handshake/certificate.rs index e35a99e2..701edfcc 100644 --- a/src/handshake/certificate.rs +++ b/src/handshake/certificate.rs @@ -1,69 +1,92 @@ -use crate::buffer::CryptoBuffer; use crate::parse_buffer::ParseBuffer; use crate::TlsError; +use crate::{buffer::CryptoBuffer, parse_buffer::ParseError}; +use core::convert::TryFrom; use heapless::Vec; #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Certificate<'a> { - entries: Vec, 16>, +pub struct CertificateRef<'a> { + raw_entries: &'a [u8], + request_context: &'a [u8], + + pub(crate) entries: Vec, 16>, } -impl<'a> Certificate<'a> { - pub fn new() -> Self { +impl<'a> CertificateRef<'a> { + pub fn with_context(request_context: &'a [u8]) -> Self { Self { + raw_entries: &[], + request_context, entries: Vec::new(), } } + pub fn add(&mut self, entry: CertificateEntryRef<'a>) -> Result<(), TlsError> { + self.entries + .push(entry) + .map_err(|_| TlsError::InsufficientSpace) + } + pub fn parse(buf: &mut ParseBuffer<'a>) -> Result { let request_context_len = buf.read_u8().map_err(|_| TlsError::InvalidCertificate)?; - let _request_context = buf + let request_context = buf .slice(request_context_len as usize) .map_err(|_| TlsError::InvalidCertificate)?; let entries_len = buf.read_u24().map_err(|_| TlsError::InvalidCertificate)?; - let mut entries = buf + let mut raw_entries = buf .slice(entries_len as usize) .map_err(|_| TlsError::InvalidCertificate)?; - let entries = CertificateEntry::parse_vector(&mut entries)?; + let entries = CertificateEntryRef::parse_vector(&mut raw_entries)?; - Ok(Self { entries }) + Ok(Self { + raw_entries: raw_entries.as_slice(), + request_context: request_context.as_slice(), + entries, + }) } pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), TlsError> { - // TODO: Implement - buf.push(0).map_err(|_| TlsError::EncodeError)?; - buf.extend_from_slice(&[0x00, 0x00, 0x00]) + buf.push(self.request_context.len() as u8) + .map_err(|_| TlsError::EncodeError)?; + buf.extend_from_slice(self.request_context) .map_err(|_| TlsError::EncodeError)?; + + buf.push_u24(self.entries.len() as u32)?; + for entry in self.entries.iter() { + entry.encode(buf)?; + } Ok(()) } } #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CertificateEntry<'a> { +pub enum CertificateEntryRef<'a> { X509(&'a [u8]), RawPublicKey(&'a [u8]), } -impl<'a> CertificateEntry<'a> { +impl<'a> CertificateEntryRef<'a> { pub fn parse_vector( buf: &mut ParseBuffer<'a>, - ) -> Result, 16>, TlsError> { + ) -> Result, 16>, TlsError> { let mut entries = Vec::new(); loop { - let entry_len = buf.read_u24().map_err(|_| TlsError::InvalidCertificate)?; - // info!("cert len: {}", entry_len); + let entry_len = buf + .read_u24() + .map_err(|_| TlsError::InvalidCertificateEntry)?; + //info!("cert len: {}", entry_len); let cert = buf .slice(entry_len as usize) - .map_err(|_| TlsError::InvalidCertificate)?; + .map_err(|e| TlsError::InvalidCertificateEntry)?; //let cert: Result, ()> = cert.into(); // let cert: Result, ()> = Ok(Vec::new()); entries - .push(CertificateEntry::X509(cert.as_slice())) + .push(CertificateEntryRef::X509(cert.as_slice())) .map_err(|_| TlsError::DecodeError)?; let _extensions_len = buf @@ -76,10 +99,77 @@ impl<'a> CertificateEntry<'a> { } Ok(entries) } + + pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), TlsError> { + /* + match self { + CertificateEntry::RawPublicKey(key) => { + let entry_len = (key.len() as u32).to_be_bytes(); + } + CertificateEntry::X509(cert) => { + let entry_len = (cert.len() as u32).to_be_bytes(); + } + } + */ + Ok(()) + } +} + +impl<'a> From<&crate::config::Certificate<'a>> for CertificateEntryRef<'a> { + fn from(cert: &crate::config::Certificate<'a>) -> Self { + match cert { + crate::config::Certificate::X509(data) => CertificateEntryRef::X509(data), + crate::config::Certificate::RawPublicKey(data) => { + CertificateEntryRef::RawPublicKey(data) + } + } + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Certificate { + request_context: Vec, + num_entries: usize, + entries_data: Vec, +} + +impl Certificate { + pub fn request_context(&self) -> &[u8] { + &self.request_context[..] + } +} + +impl<'a> TryFrom> for Certificate { + type Error = TlsError; + fn try_from(cert: CertificateRef<'a>) -> Result { + let mut request_context = Vec::new(); + request_context + .extend_from_slice(cert.request_context) + .map_err(|_| TlsError::OutOfMemory)?; + let mut entries_data = Vec::new(); + entries_data + .extend_from_slice(cert.raw_entries) + .map_err(|_| TlsError::OutOfMemory)?; + + Ok(Self { + request_context, + num_entries: cert.entries.len(), + entries_data, + }) + } } -impl<'a> Default for Certificate<'a> { - fn default() -> Self { - Certificate::new() +impl<'a> TryFrom<&'a Certificate> for CertificateRef<'a> { + type Error = TlsError; + fn try_from(cert: &'a Certificate) -> Result { + let request_context = cert.request_context(); + let entries = + CertificateEntryRef::parse_vector(&mut ParseBuffer::from(&cert.entries_data[..]))?; + Ok(Self { + raw_entries: &cert.entries_data[..], + request_context, + entries, + }) } } diff --git a/src/handshake/certificate_request.rs b/src/handshake/certificate_request.rs index f1e1e31f..71c4233f 100644 --- a/src/handshake/certificate_request.rs +++ b/src/handshake/certificate_request.rs @@ -1,16 +1,21 @@ use crate::parse_buffer::ParseBuffer; use crate::TlsError; +use heapless::Vec; #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct CertificateRequest {} +pub struct CertificateRequestRef<'a> { + pub(crate) request_context: &'a [u8], +} -impl CertificateRequest { - pub fn parse(buf: &mut ParseBuffer) -> Result { - let request_context_len = buf.read_u8().map_err(|_| TlsError::InvalidCertificate)?; - let _request_context = buf +impl<'a> CertificateRequestRef<'a> { + pub fn parse(buf: &mut ParseBuffer<'a>) -> Result, TlsError> { + let request_context_len = buf + .read_u8() + .map_err(|_| TlsError::InvalidCertificateRequest)?; + let request_context = buf .slice(request_context_len as usize) - .map_err(|_| TlsError::InvalidCertificate)?; + .map_err(|_| TlsError::InvalidCertificateRequest)?; let _extensions_length = buf .read_u16() @@ -24,6 +29,22 @@ impl CertificateRequest { //let extensions = ServerExtension::parse_vector(buf)?; //info!("Cert request parsing done"); - Ok(Self {}) + Ok(Self { + request_context: request_context.as_slice(), + }) + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CertificateRequest { + pub(crate) request_context: Vec, +} + +impl<'a> From> for CertificateRequest { + fn from(cert: CertificateRequestRef<'a>) -> Self { + let mut request_context = Vec::new(); + request_context.extend_from_slice(cert.request_context); + Self { request_context } } } diff --git a/src/handshake/certificate_verify.rs b/src/handshake/certificate_verify.rs index 06b2c138..246892b5 100644 --- a/src/handshake/certificate_verify.rs +++ b/src/handshake/certificate_verify.rs @@ -5,8 +5,8 @@ use crate::TlsError; #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CertificateVerify<'a> { - signature_scheme: SignatureScheme, - signature: &'a [u8], + pub(crate) signature_scheme: SignatureScheme, + pub(crate) signature: &'a [u8], } impl<'a> CertificateVerify<'a> { diff --git a/src/handshake/mod.rs b/src/handshake/mod.rs index f15c9ede..0d2c185c 100644 --- a/src/handshake/mod.rs +++ b/src/handshake/mod.rs @@ -3,8 +3,8 @@ use generic_array::ArrayLength; //use p256::elliptic_curve::AffinePoint; use crate::buffer::*; use crate::config::TlsCipherSuite; -use crate::handshake::certificate::Certificate; -use crate::handshake::certificate_request::CertificateRequest; +use crate::handshake::certificate::CertificateRef; +use crate::handshake::certificate_request::CertificateRequestRef; use crate::handshake::certificate_verify::CertificateVerify; use crate::handshake::client_hello::ClientHello; use crate::handshake::encrypted_extensions::EncryptedExtensions; @@ -74,7 +74,7 @@ pub enum ClientHandshake<'config, 'a, CipherSuite> where CipherSuite: TlsCipherSuite, { - ClientCert(Certificate<'a>), + ClientCert(CertificateRef<'a>), ClientHello(ClientHello<'config, CipherSuite>), Finished(Finished<::OutputSize>), } @@ -121,8 +121,8 @@ pub enum ServerHandshake<'a, N: ArrayLength> { ServerHello(ServerHello<'a>), EncryptedExtensions(EncryptedExtensions<'a>), NewSessionTicket(NewSessionTicket<'a>), - Certificate(Certificate<'a>), - CertificateRequest(CertificateRequest), + Certificate(CertificateRef<'a>), + CertificateRequest(CertificateRequestRef<'a>), CertificateVerify(CertificateVerify<'a>), Finished(Finished), } @@ -215,11 +215,11 @@ impl<'a, N: ArrayLength> ServerHandshake<'a, N> { )) } HandshakeType::Certificate => { - Ok(ServerHandshake::Certificate(Certificate::parse(buf)?)) + Ok(ServerHandshake::Certificate(CertificateRef::parse(buf)?)) } HandshakeType::CertificateRequest => Ok(ServerHandshake::CertificateRequest( - CertificateRequest::parse(buf)?, + CertificateRequestRef::parse(buf)?, )), HandshakeType::CertificateVerify => Ok(ServerHandshake::CertificateVerify( diff --git a/src/lib.rs b/src/lib.rs index 7278a45f..18ff54bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,15 +9,23 @@ //! implementation is work in progress, but the [example clients](https://github.com/drogue-iot/drogue-tls/tree/main/examples) should work against the [rustls](https://github.com/ctz/rustls) echo server. //! //! The client supports both async and blocking modes. By default, the `async` and `std` features are enabled. The `async` feature requires Rust nightly, while the blocking feature works on Rust stable. -//! +//! //! 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). //! 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. -//! -//! 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. +//! +//! 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. +//! +//! Some memory usage statistics for async operation: +//! +//! * 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 +//! +//! +//! NOTE: This is very fresh and is probably not meeting all parts of the TLS 1.3 spec. If you find anything you'd like to get implemented, feel free to raise an issue. //! # Example //! @@ -36,8 +44,9 @@ //! println!("TCP connection opened"); //! let mut record_buffer = [0; 16384]; //! let tls_context = TlsContext::new(OsRng, &mut record_buffer) +//! .verify_cert(false) //! .with_server_name("http.sandbox.drogue.cloud"); -//! let mut tls: TlsConnection = +//! let mut tls: TlsConnection = //! TlsConnection::new(tls_context, stream); //! //! tls.open().await.expect("error establishing TLS connection"); @@ -49,7 +58,7 @@ pub(crate) mod fmt; use parse_buffer::ParseError; -mod alert; +pub mod alert; mod application_data; pub mod blocking; mod buffer; @@ -70,6 +79,7 @@ mod record; mod signature_schemes; mod supported_versions; pub mod traits; +mod verify; #[cfg(feature = "async")] mod tls_connection; @@ -83,6 +93,7 @@ pub enum TlsError { ConnectionClosed, Unimplemented, MissingHandshake, + HandshakeAborted(alert::AlertLevel, alert::AlertDescription), IoError, InternalError, InvalidRecord, @@ -101,8 +112,11 @@ pub enum TlsError { InvalidApplicationData, InvalidKeyShare, InvalidCertificate, + InvalidCertificateEntry, + InvalidCertificateRequest, UnableToInitializeCryptoEngine, ParseError(ParseError), + OutOfMemory, CryptoError, EncodeError, DecodeError, @@ -184,6 +198,7 @@ pub use runtime::*; mod stdlib { extern crate std; use crate::{ + config::TlsClock, traits::{Read as TlsRead, Write as TlsWrite}, TlsError, }; @@ -208,6 +223,18 @@ mod stdlib { Ok(len) } } + + use std::time::SystemTime; + impl TlsClock for SystemTime { + fn now() -> Option { + Some( + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(), + ) + } + } } #[cfg(feature = "std")] diff --git a/src/record.rs b/src/record.rs index 8fdfd2a4..7cd82758 100644 --- a/src/record.rs +++ b/src/record.rs @@ -1,4 +1,3 @@ -use crate::alert::*; use crate::application_data::ApplicationData; use crate::buffer::*; use crate::change_cipher_spec::ChangeCipherSpec; @@ -7,6 +6,7 @@ use crate::content_types::ContentType; use crate::handshake::client_hello::ClientHello; use crate::handshake::{ClientHandshake, ServerHandshake}; use crate::TlsError; +use crate::{alert::*, parse_buffer::ParseBuffer}; use core::fmt::Debug; use core::ops::Range; use digest::{BlockInput, FixedOutput, Reset, Update}; @@ -212,7 +212,11 @@ impl<'a, N: ArrayLength> ServerRecord<'a, N> { ContentType::ChangeCipherSpec => Ok(ServerRecord::ChangeCipherSpec( ChangeCipherSpec::read(&mut rx_buf[..content_length])?, )), - ContentType::Alert => Err(TlsError::Unimplemented), + ContentType::Alert => { + let mut parse = ParseBuffer::from(&rx_buf[..content_length]); + let alert = Alert::parse(&mut parse)?; + Ok(ServerRecord::Alert(alert)) + } ContentType::Handshake => Ok(ServerRecord::Handshake(ServerHandshake::read( &mut rx_buf[..content_length], digest, diff --git a/src/signature_schemes.rs b/src/signature_schemes.rs index 37aa1ca9..7ed00f27 100644 --- a/src/signature_schemes.rs +++ b/src/signature_schemes.rs @@ -1,3 +1,6 @@ +use crate::TlsError; +use core::convert::TryInto; + #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SignatureScheme { @@ -61,3 +64,74 @@ impl SignatureScheme { } } } + +#[cfg(not(feature = "alloc"))] +impl TryInto<&'static webpki::SignatureAlgorithm> for SignatureScheme { + type Error = TlsError; + fn try_into(self) -> Result<&'static webpki::SignatureAlgorithm, Self::Error> { + // TODO: support other schemes via 'alloc' feature + match self { + SignatureScheme::RsaPkcs1Sha256 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::RsaPkcs1Sha384 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::RsaPkcs1Sha512 => Err(TlsError::InvalidSignatureScheme), + + /* ECDSA algorithms */ + SignatureScheme::EcdsaSecp256r1Sha256 => Ok(&webpki::ECDSA_P256_SHA256), + SignatureScheme::EcdsaSecp384r1Sha384 => Ok(&webpki::ECDSA_P384_SHA384), + SignatureScheme::EcdsaSecp521r1Sha512 => Err(TlsError::InvalidSignatureScheme), + + /* RSASSA-PSS algorithms with public key OID rsaEncryption */ + SignatureScheme::RsaPssRsaeSha256 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::RsaPssRsaeSha384 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::RsaPssRsaeSha512 => Err(TlsError::InvalidSignatureScheme), + + /* EdDSA algorithms */ + Ed25519 => Ok(&webpki::ED25519), + Ed448 => Err(TlsError::InvalidSignatureScheme), + + /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ + SignatureScheme::RsaPssPssSha256 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::RsaPssPssSha384 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::RsaPssPssSha512 => Err(TlsError::InvalidSignatureScheme), + + /* Legacy algorithms */ + SignatureScheme::RsaPkcs1Sha1 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::EcdsaSha1 => Err(TlsError::InvalidSignatureScheme), + } + } +} + +#[cfg(feature = "alloc")] +impl TryInto<&'static webpki::SignatureAlgorithm> for SignatureScheme { + type Error = TlsError; + fn try_into(self) -> Result<&'static webpki::SignatureAlgorithm, Self::Error> { + match self { + SignatureScheme::RsaPkcs1Sha256 => Ok(&webpki::RSA_PKCS1_2048_8192_SHA256), + SignatureScheme::RsaPkcs1Sha384 => Ok(&webpki::RSA_PKCS1_2048_8192_SHA384), + SignatureScheme::RsaPkcs1Sha512 => Ok(&webpki::RSA_PKCS1_2048_8192_SHA512), + + /* ECDSA algorithms */ + SignatureScheme::EcdsaSecp256r1Sha256 => Ok(&webpki::ECDSA_P256_SHA256), + SignatureScheme::EcdsaSecp384r1Sha384 => Ok(&webpki::ECDSA_P384_SHA384), + SignatureScheme::EcdsaSecp521r1Sha512 => Err(TlsError::InvalidSignatureScheme), + + /* RSASSA-PSS algorithms with public key OID rsaEncryption */ + SignatureScheme::RsaPssRsaeSha256 => Ok(&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY), + SignatureScheme::RsaPssRsaeSha384 => Ok(&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY), + SignatureScheme::RsaPssRsaeSha512 => Ok(&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY), + + /* EdDSA algorithms */ + Ed25519 => Ok(&webpki::ED25519), + Ed448 => Err(TlsError::InvalidSignatureScheme), + + /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ + SignatureScheme::RsaPssPssSha256 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::RsaPssPssSha384 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::RsaPssPssSha512 => Err(TlsError::InvalidSignatureScheme), + + /* Legacy algorithms */ + SignatureScheme::RsaPkcs1Sha1 => Err(TlsError::InvalidSignatureScheme), + SignatureScheme::EcdsaSha1 => Err(TlsError::InvalidSignatureScheme), + } + } +} diff --git a/src/tls_connection.rs b/src/tls_connection.rs index 05bff627..59e10178 100644 --- a/src/tls_connection.rs +++ b/src/tls_connection.rs @@ -20,9 +20,10 @@ 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: AsyncRead + AsyncWrite + 'a, CipherSuite: TlsCipherSuite + 'static, { @@ -32,16 +33,18 @@ where key_schedule: KeySchedule, record_buf: &'a mut [u8], opened: bool, + clock: core::marker::PhantomData<&'a Clock>, } -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, Socket: AsyncRead + AsyncWrite + 'a, CipherSuite: TlsCipherSuite + 'static, { /// Create a new TLS connection with the provided context and a async 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, @@ -49,6 +52,7 @@ where opened: false, key_schedule: KeySchedule::new(), record_buf: context.record_buf, + clock: core::marker::PhantomData, } } @@ -66,7 +70,7 @@ where loop { let next_state = state - .process( + .process::<_, _, _, Clock>( &mut self.delegate, &mut handshake, &mut self.record_buf, @@ -176,10 +180,13 @@ where } /// Close a connection instance, returning the ownership of the config, random generator and the async I/O provider. - pub async fn close(self) -> Result<(TlsContext<'a, CipherSuite, RNG>, Socket), TlsError> { + pub async 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; diff --git a/src/verify.rs b/src/verify.rs new file mode 100644 index 00000000..0413c52f --- /dev/null +++ b/src/verify.rs @@ -0,0 +1,135 @@ +use crate::config::{Certificate, TlsCipherSuite, TlsConfig}; +use crate::handshake::{ + certificate::{CertificateEntryRef, CertificateRef as ServerCertificate}, + certificate_verify::CertificateVerify, +}; +use crate::TlsError; +use core::convert::TryFrom; +use core::convert::TryInto; +use webpki::{DnsNameRef, Error as PkiError}; + +static ALL_SIGALGS: &[&webpki::SignatureAlgorithm] = &[ + &webpki::ECDSA_P256_SHA256, + &webpki::ECDSA_P256_SHA384, + &webpki::ECDSA_P384_SHA256, + &webpki::ECDSA_P384_SHA384, + &webpki::ED25519, +]; + +pub(crate) fn verify_signature<'a, CipherSuite>( + config: &TlsConfig<'a, CipherSuite>, + message: &[u8], + certificate: ServerCertificate, + verify: CertificateVerify, +) -> Result<(), TlsError> +where + CipherSuite: TlsCipherSuite + 'static, +{ + let mut verified = false; + if config.verify_cert { + if !certificate.entries.is_empty() { + // TODO: Support intermediates... + if let CertificateEntryRef::X509(certificate) = certificate.entries[0] { + let cert = webpki::EndEntityCert::try_from(certificate).map_err(|e| { + warn!("Error loading cert: {:?}", e); + TlsError::DecodeError + })?; + + trace!( + "Verifying with signature scheme {:?}", + verify.signature_scheme + ); + info!("Signature: {:x?}", verify.signature); + let pkisig = verify.signature_scheme.try_into()?; + match cert.verify_signature(pkisig, message, verify.signature) { + Ok(_) => { + verified = true; + } + Err(e) => { + info!("Error verifying signature: {:?}", e); + } + } + } + } + } + if !verified && config.verify_cert { + return Err(TlsError::InvalidSignature); + } + Ok(()) +} + +pub(crate) fn verify_certificate<'a, CipherSuite>( + config: &TlsConfig<'a, CipherSuite>, + certificate: &ServerCertificate, + now: Option, +) -> Result<(), TlsError> +where + CipherSuite: TlsCipherSuite + 'static, +{ + let mut verified = false; + let mut host_verified = false; + if config.verify_cert { + if let Some(Certificate::X509(ca)) = config.ca { + let trust = webpki::TrustAnchor::try_from_cert_der(ca).map_err(|e| { + warn!("Error loading CA: {:?}", e); + TlsError::DecodeError + })?; + let anchors = &[trust]; + let anchors = webpki::TLSServerTrustAnchors(anchors); + + trace!("We got {} certificate entries", certificate.entries.len()); + + if !certificate.entries.is_empty() { + // TODO: Support intermediates... + if let CertificateEntryRef::X509(certificate) = certificate.entries[0] { + let cert = webpki::EndEntityCert::try_from(certificate).map_err(|e| { + warn!("Error loading cert: {:?}", e); + TlsError::DecodeError + })?; + + let time = if let Some(now) = now { + webpki::Time::from_seconds_since_unix_epoch(now) + } else { + // If no clock is provided, use certificate notAfter as the timestamp, if available + if let Ok(validity) = cert.validity() { + validity.not_after + } else { + webpki::Time::from_seconds_since_unix_epoch(0) + } + }; + info!("Certificate is loaded!"); + match cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[], time) { + Ok(_) => verified = true, + Err(e) => { + warn!("Error verifying certificate: {:?}", e); + } + } + + if config.verify_host && config.server_name.is_some() { + match cert.verify_is_valid_for_dns_name( + DnsNameRef::try_from_ascii_str(config.server_name.unwrap()).unwrap(), + ) { + Ok(_) => host_verified = true, + Err(e) => { + warn!("Error verifying host: {:?}", e); + } + } + } + } + } + } + } else { + // Disable host verification if cert verification is disabled + host_verified = true; + } + if !verified && config.verify_cert { + panic!("CERT NOT VERIFIED"); + return Err(TlsError::InvalidCertificate); + } + + if !host_verified && config.verify_host { + panic!("HOST NOT VERIFIED"); + return Err(TlsError::InvalidCertificate); + } + Ok(()) +} diff --git a/tests/client_test.rs b/tests/client_test.rs index 8efac60b..d58163f8 100644 --- a/tests/client_test.rs +++ b/tests/client_test.rs @@ -34,19 +34,26 @@ fn setup() -> SocketAddr { #[tokio::test] async fn test_ping() { use drogue_tls::*; + use std::time::SystemTime; use tokio::net::TcpStream; let addr = setup(); + let pem = include_str!("data/ca-cert.pem"); + let der = pem_parser::pem_to_der(pem); + let stream = TcpStream::connect(addr) .await .expect("error connecting to server"); log::info!("Connected"); let mut record_buffer = [0; 16384]; - let tls_context = TlsContext::new(OsRng, &mut record_buffer); - let mut tls: TlsConnection = + let tls_context = TlsContext::new(OsRng, &mut record_buffer) + .with_ca(Certificate::X509(&der[..])) + .with_server_name("localhost"); + + let mut tls: TlsConnection = TlsConnection::new(tls_context, stream); - let sz = core::mem::size_of::>(); + let sz = core::mem::size_of::>(); log::info!("SIZE of connection is {}", sz); let open_fut = tls.open(); @@ -75,14 +82,19 @@ async fn test_ping() { fn test_blocking_ping() { use drogue_tls::blocking::*; use std::net::TcpStream; + use std::time::SystemTime; let addr = setup(); + let pem = include_str!("data/ca-cert.pem"); + let der = pem_parser::pem_to_der(pem); let stream = TcpStream::connect(addr).expect("error connecting to server"); log::info!("Connected"); let mut record_buffer = [0; 16384]; - let tls_context = TlsContext::new(OsRng, &mut record_buffer); - let mut tls: TlsConnection = + let tls_context = TlsContext::new(OsRng, &mut record_buffer) + .with_ca(Certificate::X509(&der[..])) + .with_server_name("localhost"); + let mut tls: TlsConnection = TlsConnection::new(tls_context, stream); tls.open().expect("error establishing TLS connection"); diff --git a/tests/data/ca-cert.pem b/tests/data/ca-cert.pem new file mode 100644 index 00000000..1015a352 --- /dev/null +++ b/tests/data/ca-cert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB2TCCAX+gAwIBAgIUZ+9dtzTSadaW+FC4m8dOGL40eBMwCgYIKoZIzj0EAwIw +QjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwT +RGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yMTEwMTMwODIwNDJaFw0zMTEwMTEwODIw +NDJaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNV +BAoME0RlZmF1bHQgQ29tcGFueSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AARwQ/jWAMuCH4qbcYVntGyq4RCYKiWiN9cVXKOnnDbSfIXS8IGnF7PFrCOck9yx +4A7Pfo/00rTf0x1/NKNOV5nio1MwUTAdBgNVHQ4EFgQU7HQ64pisg1MasN9wSLE/ +LC6PcjowHwYDVR0jBBgwFoAU7HQ64pisg1MasN9wSLE/LC6PcjowDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBMip1366r3oWgJFzkkmh3Sf2te54G5 +KWs0PcVLaoNiuAIhAIx5dhXti2FEQ4mkZUKaqxfH5GdboZa6JEv2yYTd6ZvK +-----END CERTIFICATE----- diff --git a/tests/data/ca-key.pem b/tests/data/ca-key.pem new file mode 100644 index 00000000..d29266b4 --- /dev/null +++ b/tests/data/ca-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgb2Ff7kE1XJA3FKLl +sNqHvI6ALhbh3pZjzeWTa+BrfvKhRANCAARwQ/jWAMuCH4qbcYVntGyq4RCYKiWi +N9cVXKOnnDbSfIXS8IGnF7PFrCOck9yx4A7Pfo/00rTf0x1/NKNOV5ni +-----END PRIVATE KEY----- diff --git a/tests/data/server-cert.pem b/tests/data/server-cert.pem new file mode 100644 index 00000000..34f54a37 --- /dev/null +++ b/tests/data/server-cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICBjCCAa2gAwIBAgIULDyCYYteka2S6hEC2890o7k+0Z0wCgYIKoZIzj0EAwIw +QjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwT +RGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yMTEwMTMwODIwNDJaFw0zMTEwMTEwODIw +NDJaMHIxCzAJBgNVBAYTAk5PMQ4wDAYDVQQIDAVIYW1hcjEOMAwGA1UEBwwFSGFt +YXIxGDAWBgNVBAoMD0dsb2JhbCBTZWN1cml0eTEVMBMGA1UECwwMSG9sc2V0YmFr +a2VuMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AATEmfbzqqHiZwCKXgEfjAWjk6zPlK9Fs3bXfjo2gt1NuqA4yCdOULKa6aIFHyAv +fM3zHNiL5vk5pbBtzja6vaIjo1EwTzAfBgNVHSMEGDAWgBTsdDrimKyDUxqw33BI +sT8sLo9yOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAUBgNVHREEDTALgglsb2Nh +bGhvc3QwCgYIKoZIzj0EAwIDRwAwRAIgbzQX/FohpbrME+QE6bo0UrYdXI1hSaSs +8yjdM7dr4HoCIEM+dbqDGm+QG+tkhH7jB35czbBWmC/Y5ObMM29i/u2h +-----END CERTIFICATE----- diff --git a/tests/data/server-key.pem b/tests/data/server-key.pem new file mode 100644 index 00000000..693c93cf --- /dev/null +++ b/tests/data/server-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKoaBrAdXxdzKFph6 +tXe2+WYYMV0HUz9KWdnz81f38YKhRANCAATEmfbzqqHiZwCKXgEfjAWjk6zPlK9F +s3bXfjo2gt1NuqA4yCdOULKa6aIFHyAvfM3zHNiL5vk5pbBtzja6vaIj +-----END PRIVATE KEY----- diff --git a/tests/testcert.pem b/tests/testcert.pem deleted file mode 100644 index 4f27d3b7..00000000 --- a/tests/testcert.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICvjCCAaYCCQCpzc/AszCgYDANBgkqhkiG9w0BAQsFADAhMQswCQYDVQQGEwJO -TzESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTIxMDYyMTE2MTIyNloXDTIyMDYyMTE2 -MTIyNlowITELMAkGA1UEBhMCTk8xEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMU8T8rqsSUjVSzKIgurRq443crEGGHA -hsVkLblATpBgx4Pmp6BJStj0NLTBwNGHrNZhV9asbLntbOoKw3kThuTrQr+ty4O5 -4u15jTtBbvoyVxQLlJW2rVs20tftHG/Kx87A3QQbXz54uR1ygzKDv5atGQMaETHg -Dse29PQ5TXDxPS5feVjanq8cEiItaYhPtgJzI0EWt16VJUOysyBNXSB8prtauW5O -kWm/RIy1m9kDQZXbugpvbcdKT3fRrBDhJgEiCKMhkglDg1w4tmccr43Q/ABnmptK -gSWgrBVp1onz++fA84q4eQPr8JGpxM3nt6la6Se6mr4hr9WRhuQ7zo0CAwEAATAN -BgkqhkiG9w0BAQsFAAOCAQEAb0v340a6Yt00xFBB4W1q6YTN7H4YlZqUP2XMyTjv -7X4s/gIkRNFnRuttCCnTSz2vvs5DFDcVTJlLtkNZyFgsvyI0Qur0Ox7M1eFY/pYa -hDOEImVXtndooEDpee7bWd8tAss/SjM4Kw8wP0aj/JYF6KOwORNfMq/DelCcKcAB -pDaLo7jQz07o0vUHK07bOx/+zUK4H0YP8lx1pkqFPZt1bsYyxoCakxJNbblwgbpA -ia+JuPJh15WNUU00VswIjrp9utZtjIuMJ1Il4DlnsKwDPUuy0SgNUwO1SxbNYP5i -Xd5Cto3i90FLX9T3QmwQ5zsNMqVLGkSTUMZ7VRvPnO8A0A== ------END CERTIFICATE----- diff --git a/tests/testkey.pem b/tests/testkey.pem deleted file mode 100644 index 26c4deab..00000000 --- a/tests/testkey.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFPE/K6rElI1Us -yiILq0auON3KxBhhwIbFZC25QE6QYMeD5qegSUrY9DS0wcDRh6zWYVfWrGy57Wzq -CsN5E4bk60K/rcuDueLteY07QW76MlcUC5SVtq1bNtLX7RxvysfOwN0EG18+eLkd -coMyg7+WrRkDGhEx4A7HtvT0OU1w8T0uX3lY2p6vHBIiLWmIT7YCcyNBFrdelSVD -srMgTV0gfKa7WrluTpFpv0SMtZvZA0GV27oKb23HSk930awQ4SYBIgijIZIJQ4Nc -OLZnHK+N0PwAZ5qbSoEloKwVadaJ8/vnwPOKuHkD6/CRqcTN57epWuknupq+Ia/V -kYbkO86NAgMBAAECggEBALjcQ2lLuQa27BETzv8i5BdNA0wVqUiNe4Os1lIoHLqj -irxsxjWsHDPmBzRX2RJJ2MwB5qM1bp6BErD1r6pei/MpMaGYEgR/iFrNr3qXZJ2p -6wXFqNj9o9O3ObzZ5uo8pDN7fmtoAEf8PHyGJMOGyl4+FRBcMTuO5it3pnT09Eli -m7Nb7uTzlD7u1QWl5pqOsSK6DRpafXGfS0lnSQ4J3OS4D24K5N4Kcj/X3OTh+sCj -n6y+Zi7m9pwyiGY0H0LC7pVxhFPCBobOboItuuzsKbrvPhUE+qgMEyfrXsu4auEq -Gbo7ix8qd2BI4R6kzHGK+Lo21Ruk/g39dFBagzGAT/0CgYEA8AikYgyn6lub1Scf -fSwJPSdEO8Iq6yguOIJDrC+Nw+h6HF6NwxzEiV5CxeDceZtVTTWv/FyKJynohvNo -gwqaDGZC3IB5PpXW1QM7POs4r8Au9saxE9ae9KAfuMC54MoDaWygKE6rT5rSQC+L -HwEA3NV8uXD7vT8rglHR/FNkVAsCgYEA0lrkIzFUyXIkNBOV8FOARmVzFHjEYW6K -to5ebOFw3OdaWUqw0XSjW3OXJfNf31FeyLFQu8109XLN5yvw8QP30RTfOS7pyE4b -dbxEfc+7RzqGuNW5z9pGyTUJOgS975VVZbKj+XSVZq5DEonUaqr5n/qojASKBS5C -uXD3Ffy9rscCgYB3OkO0tEaf4VsZWHKKsVnxd1SsPjklT3MN/J5yo7hOLGajOtA9 -r0dsWFbghV3eRarsTBObTbgN1gI7LiKmnJiCOjVR47rVvIXWjuFY1Bn3KoXpbC4/ -BWc1aWf4VDrADnf9YMh43QK46tUurq7y+oOcLarjTdytVvhcphmgtSdrmwKBgH7V -xxFhQVpltGaiGJpb/tBzgzwoxWBqIsGIfm7wT/killuYqZzh003lGJINeRVSHvCr -2Z4jeA43NufYmuL4HYi4KRB1tQjN78jByBqEEswUAf+Y/vzRBDV2ASY2jb/ULzWu -zSgwn5TWJiMbcDUQ+or3vIEQi5gsGJdwAcwEJOspAoGAbMYwVzZ0upsxqMAlVyXI -Z1AVU1QAVhB+lBPBfHNTM6UP/FfIc9Q08srSKTbKSpTcIUWFDp3VK25aa1p5D6Uh -C8sPgDiOHb7ntyaEY47MVNxVGN53tia6e5xBCa1a74MWmapLR/Ugm3Y2z1JkFxQq -YdP4SFz2tpsY+LyAreKRLNY= ------END PRIVATE KEY----- diff --git a/tests/tlsserver.rs b/tests/tlsserver.rs index 5c446c6f..788b6611 100644 --- a/tests/tlsserver.rs +++ b/tests/tlsserver.rs @@ -368,8 +368,8 @@ pub fn run(mut listener: TcpListener) { let test_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests"); - let certs = load_certs(&test_dir.join("testcert.pem")); - let privkey = load_private_key(&test_dir.join("testkey.pem")); + let certs = load_certs(&test_dir.join("data").join("server-cert.pem")); + let privkey = load_private_key(&test_dir.join("data").join("server-key.pem")); let mut config = rustls::ServerConfig::new(client_auth);