From 526d9f7458da71eef9123330862ca8a459970087 Mon Sep 17 00:00:00 2001 From: Kris Kwiatkowski Date: Sat, 21 Feb 2026 08:20:02 +0000 Subject: [PATCH] Add comments and LICENSE --- LICENSE | 174 ++++++++++++++++++ README.md | 9 + src/asynch.rs | 48 +---- src/blocking.rs | 46 +---- src/buffer.rs | 4 - src/cert_verify.rs | 20 +- src/certificate.rs | 3 - src/change_cipher_spec.rs | 4 +- src/cipher.rs | 2 +- src/common/decrypted_read_handler.rs | 8 - src/config.rs | 62 +------ src/connection.rs | 22 ++- src/extensions/extension_data/alpn.rs | 17 -- src/extensions/extension_data/key_share.rs | 8 +- .../extension_data/max_fragment_length.rs | 11 -- .../extension_data/pre_shared_key.rs | 2 - src/extensions/extension_data/server_name.rs | 9 - .../extension_data/signature_algorithms.rs | 11 -- .../extension_data/supported_groups.rs | 3 - src/extensions/extension_group_macro.rs | 13 +- src/extensions/messages.rs | 9 +- src/handshake/binder.rs | 3 - src/handshake/certificate.rs | 3 - src/handshake/certificate_request.rs | 1 - src/handshake/certificate_verify.rs | 7 - src/handshake/client_hello.rs | 33 +--- src/handshake/finished.rs | 17 +- src/handshake/mod.rs | 8 +- src/handshake/server_hello.rs | 14 +- src/key_schedule.rs | 35 ++-- src/lib.rs | 44 +---- src/native_pki.rs | 1 - src/parse_buffer.rs | 2 - src/read_buffer.rs | 9 - src/record.rs | 5 - src/record_reader.rs | 22 +-- src/send_policy.rs | 24 +-- 37 files changed, 266 insertions(+), 447 deletions(-) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..159fef7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship made available under + the License, as indicated by a copyright notice that is included in + or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other + transformations represent, as a whole, an original work of authorship. + For the purposes of this License, Derivative Works shall not include + works that remain separable from, or merely link (or bind by name) + to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean, as submitted to the Licensor for inclusion + in the Work by the copyright owner or by an individual or Legal Entity + authorized to submit on behalf of the copyright owner. For the + purposes of this definition, "submit" means any form of electronic, + verbal, or written communication sent to the Licensor or its + representatives, including but not limited to communication on + electronic mailing lists, source code control systems, and issue + tracking systems that is managed by, or on behalf of, the Licensor + for the purpose of discussing and improving the Work, but excluding + communication that is conspicuously marked or designated in writing + by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any Legal Entity on behalf of + whom a Contribution has been received by the Licensor and included + within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent contributions + Licensable by such Contributor that are necessarily infringed by + their Contribution(s) alone or by the combination of their + Contribution(s) with the Work to which such Contribution(s) was + submitted. If You institute patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that + the Work or any such Contribution embodied within the Work constitutes + direct or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate as of + the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative + Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, You must include a readable copy of the + attribution notices contained within such NOTICE file, in + at least one of the following places: within a NOTICE text + file distributed as part of the Derivative Works; within + the Source form or documentation, if provided along with the + Derivative Works; or, within a display generated by the + Derivative Works, if and wherever such third-party notices + normally appear. The contents of the NOTICE file are for + informational purposes only and do not modify the License. + You may add Your own attribution notices within Derivative + Works that You distribute, alongside or in addition to the + NOTICE text from the Work, provided that such additional + attribution notices cannot be construed as modifying the + License. + + You may add Your own license statement for Your modifications and + may provide additional grant of rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the + Derivative Works, as separate terms and conditions for use, + reproduction, or distribution of Your modifications, or for such + Derivative Works as a whole, provided Your use, reproduction, and + distribution of the Work otherwise complies with the conditions + stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any conditions of TITLE, + MERCHANTIBILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely + responsible for determining the appropriateness of using or + reproducing the Work and assume any risks associated with Your + exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or exemplary damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or all other + commercial damages or losses), even if such Contributor has been + advised of the possibility of such damages. + + 9. Accepting Warranty or Liability. While redistributing the Work or + Derivative Works thereof, You may choose to offer, and charge a fee + for, acceptance of support, warranty, indemnity, or other liability + obligations and/or rights consistent with this License. However, in + accepting such obligations, You may offer such obligations only on + Your own behalf and on Your sole responsibility, not on behalf of + any other Contributor, and only if You agree to indemnify, defend, + and hold each Contributor harmless for any liability incurred by, + or claims asserted against, such Contributor by reason of your + accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..aefcef8 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# mote-tls + +TLS 1.3 client with `no_std` and no allocator support. + +Based on commit [426f327](https://github.com/drogue-iot/embedded-tls/commit/426f327) from [drogue-iot/embedded-tls](https://github.com/drogue-iot/embedded-tls). + +## License + +Apache-2.0 diff --git a/src/asynch.rs b/src/asynch.rs index 73eed2a..bacaf39 100644 --- a/src/asynch.rs +++ b/src/asynch.rs @@ -17,9 +17,9 @@ use embedded_io_async::{BufRead, Read as AsyncRead, Write as AsyncWrite}; pub use crate::config::*; -/// 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. +/// An async TLS 1.3 client stream wrapping an underlying async transport. +/// +/// Call [`open`](SecureStream::open) to perform the handshake before reading or writing. pub struct SecureStream<'a, Socket, CipherSuite> where Socket: AsyncRead + AsyncWrite + 'a, @@ -42,17 +42,6 @@ where pub fn is_opened(&mut self) -> bool { *self.opened.get_mut() } - /// Create a new TLS connection with the provided context and a async I/O implementation - /// - /// NOTE: The record read buffer should be sized to fit an encrypted TLS record. The size of this record - /// depends on the server configuration, but the maximum allowed value for a TLS record is 16640 bytes, - /// which should be a safe value to use. - /// - /// The write record buffer can be smaller than the read buffer. During writes [`TLS_RECORD_OVERHEAD`] bytes of - /// overhead is added per record, so the buffer must at least be this large. Large writes are split into multiple - /// records if depending on the size of the write buffer. - /// The largest of the two buffers will be used to encode the TLS handshake record, hence either of the - /// buffers must at least be large enough to encode a handshake. pub fn new( delegate: Socket, record_read_buf: &'a mut [u8], @@ -69,29 +58,16 @@ where } } - /// Returns a reference to the current flush policy. - /// - /// The flush policy controls whether the underlying transport is flushed - /// (via its `flush()` method) after writing a TLS record. #[inline] pub fn flush_policy(&self) -> FlushPolicy { self.flush_policy } - /// Replace the current flush policy with the provided one. - /// - /// This sets how and when the connection will call `flush()` on the - /// underlying transport after writing records. #[inline] pub fn set_flush_policy(&mut self, policy: FlushPolicy) { self.flush_policy = policy; } - /// Open a TLS connection, performing the handshake with the configuration provided when - /// creating the connection instance. - /// - /// Returns an error if the handshake does not proceed. If an error occurs, the connection - /// instance must be recreated. pub async fn open( &mut self, mut context: ConnectContext<'_, CP>, @@ -128,16 +104,9 @@ where Ok(()) } - /// Encrypt and send the provided slice over the connection. The connection - /// must be opened before writing. - /// - /// The slice may be buffered internally and not written to the connection immediately. - /// In this case [`Self::flush()`] should be called to force the currently buffered writes - /// to be written to the connection. - /// - /// Returns the number of bytes buffered/written. pub async fn write(&mut self, buf: &[u8]) -> Result { if self.is_opened() { + // Start a new ApplicationData record if none is in progress if !self .record_write_buf .contains(ClientRecordHeader::ApplicationData) @@ -159,8 +128,6 @@ where } } - /// Force all previously written, buffered bytes to be encoded into a tls record and written - /// to the connection. pub async fn flush(&mut self) -> Result<(), ProtocolError> { if !self.record_write_buf.is_empty() { let key_schedule = self.key_schedule.write_state(); @@ -193,7 +160,6 @@ where self.decrypted.create_read_buffer(self.record_reader.buf) } - /// Read and decrypt data filling the provided slice. pub async fn read(&mut self, buf: &mut [u8]) -> Result { if buf.is_empty() { return Ok(0); @@ -206,7 +172,6 @@ where Ok(len) } - /// Reads buffered data. If nothing is in memory, it'll wait for a TLS record and process it. pub async fn read_buffered(&mut self) -> Result, ProtocolError> { if self.is_opened() { while self.decrypted.is_empty() { @@ -240,12 +205,12 @@ where Ok(()) } - /// Close a connection instance, returning the ownership of the config, random generator and the async I/O provider. async fn close_internal(&mut self) -> Result<(), ProtocolError> { self.flush().await?; let is_opened = self.is_opened(); let (write_key_schedule, read_key_schedule) = self.key_schedule.as_split(); + // Send a close_notify alert to signal clean shutdown (RFC 8446 §6.1) let slice = self.record_write_buf.write_record( &ClientRecord::close_notify(is_opened), write_key_schedule, @@ -262,7 +227,6 @@ where self.flush_transport().await } - /// Close a connection instance, returning the ownership of the async I/O provider. pub async fn close(mut self) -> Result { match self.close_internal().await { Ok(()) => Ok(self.delegate), @@ -279,6 +243,7 @@ where where Socket: Clone, { + // Split requires a Clone socket so both halves can independently drive the same connection let (wks, rks) = self.key_schedule.as_split(); let reader = TlsReader { @@ -375,7 +340,6 @@ where self.decrypted.create_read_buffer(self.record_reader.buf) } - /// Reads buffered data. If nothing is in memory, it'll wait for a TLS record and process it. pub async fn read_buffered(&mut self) -> Result, ProtocolError> { if self.opened.load(Ordering::Acquire) { while self.decrypted.is_empty() { diff --git a/src/blocking.rs b/src/blocking.rs index 5f93a2f..c099350 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -17,9 +17,9 @@ use portable_atomic::AtomicBool; pub use crate::ProtocolError; pub use crate::config::*; -/// Type representing a 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. +/// Blocking TLS 1.3 client stream wrapping a synchronous transport. +/// +/// Call [`open`](SecureStream::open) to perform the handshake before reading or writing. pub struct SecureStream<'a, Socket, CipherSuite> where Socket: Read + Write + 'a, @@ -43,17 +43,6 @@ where *self.opened.get_mut() } - /// Create a new TLS connection with the provided context and a blocking I/O implementation - /// - /// NOTE: The record read buffer should be sized to fit an encrypted TLS record. The size of this record - /// depends on the server configuration, but the maximum allowed value for a TLS record is 16640 bytes, - /// which should be a safe value to use. - /// - /// The write record buffer can be smaller than the read buffer. During writes [`TLS_RECORD_OVERHEAD`] bytes of - /// overhead is added per record, so the buffer must at least be this large. Large writes are split into multiple - /// records if depending on the size of the write buffer. - /// The largest of the two buffers will be used to encode the TLS handshake record, hence either of the - /// buffers must at least be large enough to encode a handshake. pub fn new( delegate: Socket, record_read_buf: &'a mut [u8], @@ -70,29 +59,16 @@ where } } - /// Returns a reference to the current flush policy. - /// - /// The flush policy controls whether the underlying transport is flushed - /// (via its `flush()` method) after writing a TLS record. #[inline] pub fn flush_policy(&self) -> FlushPolicy { self.flush_policy } - /// Replace the current flush policy with the provided one. - /// - /// This sets how and when the connection will call `flush()` on the - /// underlying transport after writing records. #[inline] pub fn set_flush_policy(&mut self, policy: FlushPolicy) { self.flush_policy = policy; } - /// Open a TLS connection, performing the handshake with the configuration provided when - /// creating the connection instance. - /// - /// Returns an error if the handshake does not proceed. If an error occurs, the connection - /// instance must be recreated. pub fn open(&mut self, mut context: ConnectContext) -> Result<(), ProtocolError> where CP: CryptoBackend, @@ -124,16 +100,9 @@ where Ok(()) } - /// Encrypt and send the provided slice over the connection. The connection - /// must be opened before writing. - /// - /// The slice may be buffered internally and not written to the connection immediately. - /// In this case [`Self::flush()`] should be called to force the currently buffered writes - /// to be written to the connection. - /// - /// Returns the number of bytes buffered/written. pub fn write(&mut self, buf: &[u8]) -> Result { if self.is_opened() { + // Start a new ApplicationData record if none is in progress if !self .record_write_buf .contains(ClientRecordHeader::ApplicationData) @@ -155,8 +124,6 @@ where } } - /// Force all previously written, buffered bytes to be encoded into a tls record and written - /// to the connection. pub fn flush(&mut self) -> Result<(), ProtocolError> { if !self.record_write_buf.is_empty() { let key_schedule = self.key_schedule.write_state(); @@ -185,7 +152,6 @@ where self.decrypted.create_read_buffer(self.record_reader.buf) } - /// Read and decrypt data filling the provided slice. pub fn read(&mut self, buf: &mut [u8]) -> Result { if buf.is_empty() { return Ok(0); @@ -198,7 +164,6 @@ where Ok(len) } - /// Reads buffered data. If nothing is in memory, it'll wait for a TLS record and process it. pub fn read_buffered(&mut self) -> Result, ProtocolError> { if self.is_opened() { while self.decrypted.is_empty() { @@ -235,6 +200,7 @@ where let is_opened = self.is_opened(); let (write_key_schedule, read_key_schedule) = self.key_schedule.as_split(); + // Send a close_notify alert to signal clean shutdown (RFC 8446 §6.1) let slice = self.record_write_buf.write_record( &ClientRecord::close_notify(is_opened), write_key_schedule, @@ -252,7 +218,6 @@ where Ok(()) } - /// Close a connection instance, returning the ownership of the I/O provider. pub fn close(mut self) -> Result { match self.close_internal() { Ok(()) => Ok(self.delegate), @@ -365,7 +330,6 @@ where self.decrypted.create_read_buffer(self.record_reader.buf) } - /// Reads buffered data. If nothing is in memory, it'll wait for a TLS record and process it. pub fn read_buffered(&mut self) -> Result, ProtocolError> { if self.opened.load(Ordering::Acquire) { while self.decrypted.is_empty() { diff --git a/src/buffer.rs b/src/buffer.rs index ed34bab..87e0690 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -157,10 +157,6 @@ impl<'b> CryptoBuffer<'b> { pub(crate) fn offset(self, offset: usize) -> CryptoBuffer<'b> { let new_len = self.len + self.offset - offset; - /*info!( - "offset({}) len({}) -> offset({}), len({})", - self.offset, self.len, offset, new_len - );*/ CryptoBuffer { buf: self.buf, len: new_len, diff --git a/src/cert_verify.rs b/src/cert_verify.rs index 2094cc3..6946316 100644 --- a/src/cert_verify.rs +++ b/src/cert_verify.rs @@ -14,46 +14,38 @@ use heapless::Vec; impl TryInto<&'static webpki::SignatureAlgorithm> for SignatureScheme { type Error = ProtocolError; fn try_into(self) -> Result<&'static webpki::SignatureAlgorithm, Self::Error> { - // TODO: support other schemes via 'alloc' feature - #[allow(clippy::match_same_arms)] // Style + #[allow(clippy::match_same_arms)] match self { SignatureScheme::RsaPkcs1Sha256 | SignatureScheme::RsaPkcs1Sha384 | SignatureScheme::RsaPkcs1Sha512 => Err(ProtocolError::InvalidSignatureScheme), - /* ECDSA algorithms */ SignatureScheme::EcdsaSecp256r1Sha256 => Ok(&webpki::ECDSA_P256_SHA256), SignatureScheme::EcdsaSecp384r1Sha384 => Ok(&webpki::ECDSA_P384_SHA384), SignatureScheme::EcdsaSecp521r1Sha512 => Err(ProtocolError::InvalidSignatureScheme), - /* RSASSA-PSS algorithms with public key OID rsaEncryption */ SignatureScheme::RsaPssRsaeSha256 | SignatureScheme::RsaPssRsaeSha384 | SignatureScheme::RsaPssRsaeSha512 => Err(ProtocolError::InvalidSignatureScheme), - /* EdDSA algorithms */ SignatureScheme::Ed25519 => Ok(&webpki::ED25519), SignatureScheme::Ed448 | SignatureScheme::Sha224Ecdsa | SignatureScheme::Sha224Rsa | SignatureScheme::Sha224Dsa => Err(ProtocolError::InvalidSignatureScheme), - /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ SignatureScheme::RsaPssPssSha256 | SignatureScheme::RsaPssPssSha384 | SignatureScheme::RsaPssPssSha512 => Err(ProtocolError::InvalidSignatureScheme), - /* Legacy algorithms */ SignatureScheme::RsaPkcs1Sha1 | SignatureScheme::EcdsaSha1 => { Err(ProtocolError::InvalidSignatureScheme) } - /* Ml-DSA */ SignatureScheme::MlDsa44 | SignatureScheme::MlDsa65 | SignatureScheme::MlDsa87 => { Err(ProtocolError::InvalidSignatureScheme) } - /* Brainpool */ SignatureScheme::Sha256BrainpoolP256r1 | SignatureScheme::Sha384BrainpoolP384r1 | SignatureScheme::Sha512BrainpoolP512r1 => Err(ProtocolError::InvalidSignatureScheme), @@ -70,17 +62,14 @@ impl TryInto<&'static webpki::SignatureAlgorithm> for SignatureScheme { 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(ProtocolError::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 */ SignatureScheme::Ed25519 => Ok(&webpki::ED25519), SignatureScheme::Ed448 => Err(ProtocolError::InvalidSignatureScheme), @@ -88,21 +77,17 @@ impl TryInto<&'static webpki::SignatureAlgorithm> for SignatureScheme { SignatureScheme::Sha224Rsa => Err(ProtocolError::InvalidSignatureScheme), SignatureScheme::Sha224Dsa => Err(ProtocolError::InvalidSignatureScheme), - /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ SignatureScheme::RsaPssPssSha256 => Err(ProtocolError::InvalidSignatureScheme), SignatureScheme::RsaPssPssSha384 => Err(ProtocolError::InvalidSignatureScheme), SignatureScheme::RsaPssPssSha512 => Err(ProtocolError::InvalidSignatureScheme), - /* Legacy algorithms */ SignatureScheme::RsaPkcs1Sha1 => Err(ProtocolError::InvalidSignatureScheme), SignatureScheme::EcdsaSha1 => Err(ProtocolError::InvalidSignatureScheme), - /* MlDsa */ SignatureScheme::MlDsa44 => Err(ProtocolError::InvalidSignatureScheme), SignatureScheme::MlDsa65 => Err(ProtocolError::InvalidSignatureScheme), SignatureScheme::MlDsa87 => Err(ProtocolError::InvalidSignatureScheme), - /* Brainpool */ SignatureScheme::Sha256BrainpoolP256r1 => Err(ProtocolError::InvalidSignatureScheme), SignatureScheme::Sha384BrainpoolP384r1 => Err(ProtocolError::InvalidSignatureScheme), SignatureScheme::Sha512BrainpoolP512r1 => Err(ProtocolError::InvalidSignatureScheme), @@ -194,7 +179,6 @@ fn verify_signature( ) -> Result<(), ProtocolError> { let mut verified = false; 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!("ProtocolError loading cert: {:?}", e); @@ -240,7 +224,6 @@ fn verify_certificate( 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!("ProtocolError loading cert: {:?}", e); @@ -250,7 +233,6 @@ fn verify_certificate( let time = if let Some(now) = now { webpki::Time::from_seconds_since_unix_epoch(now) } else { - // If no clock is provided, the validity check will fail webpki::Time::from_seconds_since_unix_epoch(0) }; info!("Certificate is loaded!"); diff --git a/src/certificate.rs b/src/certificate.rs index 9563408..c276b7e 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -52,11 +52,8 @@ pub const RSA_PKCS1_SHA512: AlgorithmIdentifier = AlgorithmIdentifier { #[asn1(type = "INTEGER")] #[repr(u8)] pub enum Version { - /// Version 1 (default) V1 = 0, - /// Version 2 V2 = 1, - /// Version 3 V3 = 2, } diff --git a/src/change_cipher_spec.rs b/src/change_cipher_spec.rs index 069ff3a..9386b0f 100644 --- a/src/change_cipher_spec.rs +++ b/src/change_cipher_spec.rs @@ -6,15 +6,13 @@ use crate::parse_buffer::ParseBuffer; #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ChangeCipherSpec {} -#[allow(clippy::unnecessary_wraps)] // TODO +#[allow(clippy::unnecessary_wraps)] impl ChangeCipherSpec { pub fn new() -> Self { Self {} } pub fn read(_rx_buf: &mut [u8]) -> Result { - // info!("change cipher spec of len={}", rx_buf.len()); - // TODO: Decode data Ok(Self {}) } diff --git a/src/cipher.rs b/src/cipher.rs index 0dd9d39..f6cfc19 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -4,7 +4,7 @@ use p256::ecdh::SharedSecret; pub struct CryptoEngine {} -#[allow(clippy::unused_self, clippy::needless_pass_by_value)] // TODO +#[allow(clippy::unused_self, clippy::needless_pass_by_value)] impl CryptoEngine { pub fn new(_group: NamedGroup, _shared: SharedSecret) -> Self { Self {} diff --git a/src/common/decrypted_read_handler.rs b/src/common/decrypted_read_handler.rs index a440c45..5c8e2a2 100644 --- a/src/common/decrypted_read_handler.rs +++ b/src/common/decrypted_read_handler.rs @@ -27,12 +27,7 @@ impl DecryptedReadHandler<'_> { ); let offset = unsafe { - // SAFETY: The assertion above ensures `slice` is a subslice of the read buffer. - // This, in turn, ensures we don't violate safety constraints of `offset_from`. - // TODO: We are only assuming here that the pointers are derived from the read - // buffer. While this is reasonable, and we don't do any pointer magic, - // it's not an invariant. slice_ptrs.start.offset_from(self.source_buffer.start) as usize }; @@ -51,9 +46,6 @@ impl DecryptedReadHandler<'_> { } ServerRecord::ChangeCipherSpec(_) => Err(ProtocolError::InternalError), ServerRecord::Handshake(ServerHandshake::NewSessionTicket(_)) => { - // TODO: we should validate extensions and abort. We can do this automatically - // as long as the connection is unsplit, however, split connections must be aborted - // by the user. Ok(()) } ServerRecord::Handshake(_) => { diff --git a/src/config.rs b/src/config.rs index 98505b0..35171fe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,9 +19,9 @@ use typenum::{Sum, U10, U12, U16, U32}; pub use crate::extensions::extension_data::max_fragment_length::MaxFragmentLength; +/// Extra bytes required per record for the TLS 1.3 header, authentication tag, and inner content type. pub const TLS_RECORD_OVERHEAD: usize = 128; -// longest label is 12b -> buf <= 2 + 1 + 6 + longest + 1 + hash_out = hash_out + 22 type LongestLabel = U12; type LabelOverhead = U10; type LabelBuffer = Sum< @@ -29,7 +29,7 @@ type LabelBuffer = Sum< Sum, >; -/// Represents a TLS 1.3 cipher suite +/// Associates a cipher, key/IV lengths, hash algorithm, and label buffer size for a TLS 1.3 cipher suite. pub trait TlsCipherSuite { const CODE_POINT: u16; type Cipher: KeyInit + AeadInPlace; @@ -62,35 +62,23 @@ impl TlsCipherSuite for Aes256GcmSha384 { type LabelBufferSize = LabelBuffer; } -/// A TLS 1.3 verifier. -/// -/// The verifier is responsible for verifying certificates and signatures. Since certificate verification is -/// an expensive process, this trait allows clients to choose how much verification should take place, -/// and also to skip the verification if the server is verified through other means (I.e. a pre-shared key). +/// Certificate and server-identity verification interface. Implement to enforce PKI validation. pub trait Verifier where CipherSuite: TlsCipherSuite, { - /// Host verification is enabled by passing a server hostname. fn set_hostname_verification(&mut self, hostname: &str) -> Result<(), crate::ProtocolError>; - /// Verify a certificate. - /// - /// The handshake transcript up to this point and the server certificate is provided - /// for the implementation to use. The verifier is responsible for resolving the CA - /// certificate internally. fn verify_certificate( &mut self, transcript: &CipherSuite::Hash, cert: CertificateRef, ) -> Result<(), ProtocolError>; - /// Verify the certificate signature. - /// - /// The signature verification uses the transcript and certificate provided earlier to decode the provided signature. fn verify_signature(&mut self, verify: HandshakeVerifyRef) -> Result<(), crate::ProtocolError>; } +/// A [`Verifier`] that accepts any certificate without validation. Useful for testing only. pub struct NoVerify; impl Verifier for NoVerify @@ -114,12 +102,14 @@ where } } +/// Configuration for a single TLS client connection: server name, PSK, cipher preferences, etc. #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[must_use = "ConnectConfig does nothing unless consumed"] pub struct ConnectConfig<'a> { pub(crate) server_name: Option<&'a str>, pub(crate) alpn_protocols: Option<&'a [&'a [u8]]>, + // PSK value and the list of identity labels to offer in the ClientHello pub(crate) psk: Option<(&'a [u8], Vec<&'a [u8], 4>)>, pub(crate) signature_schemes: Vec, pub(crate) named_groups: Vec, @@ -138,6 +128,7 @@ impl TlsClock for NoClock { } } +/// Provides the RNG, cipher suite, optional certificate verifier, and optional client signing key. pub trait CryptoBackend { type CipherSuite: TlsCipherSuite; type Signature: AsRef<[u8]>; @@ -148,10 +139,6 @@ pub trait CryptoBackend { Err::<&mut NoVerify, _>(crate::ProtocolError::Unimplemented) } - /// Provide a signing key for client certificate authentication. - /// - /// The provider resolves the private key internally (e.g. from memory, flash, or a hardware - /// crypto module such as an HSM/TPM/secure element). fn signer( &mut self, ) -> Result<(impl signature::SignerMut, SignatureScheme), crate::ProtocolError> @@ -159,12 +146,6 @@ pub trait CryptoBackend { Err::<(NoSign, _), crate::ProtocolError>(crate::ProtocolError::Unimplemented) } - /// Resolve the client certificate for mutual TLS authentication. - /// - /// Return `None` if no client certificate is available (an empty certificate message will - /// be sent to the server). The data type `D` can be borrowed (`&[u8]`) or owned - /// (e.g. `heapless::Vec`) — the certificate is only needed long enough to encode - /// into the TLS message. fn client_cert(&mut self) -> Option>> { None::> } @@ -203,6 +184,7 @@ impl signature::Signer for NoSign { } } +/// A [`CryptoBackend`] that skips certificate verification. Suitable for testing or constrained environments. pub struct SkipVerifyProvider<'a, CipherSuite, RNG> { rng: RNG, priv_key: Option<&'a [u8]>, @@ -278,7 +260,6 @@ impl<'a, CP> ConnectContext<'a, CP> where CP: CryptoBackend, { - /// Create a new context with a given config and a crypto provider. pub fn new(config: &'a ConnectConfig<'a>, crypto_provider: CP) -> Self { Self { config, @@ -298,6 +279,7 @@ impl<'a> ConnectConfig<'a> { alpn_protocols: None, }; + // RSA signature schemes are disabled by default to save code size; opt in via `alloc` feature if cfg!(feature = "alloc") { config = config.enable_rsa_signatures(); } @@ -321,7 +303,6 @@ impl<'a> ConnectConfig<'a> { config } - /// Enable RSA ciphers even if they might not be supported. pub fn enable_rsa_signatures(mut self) -> Self { unwrap!( self.signature_schemes @@ -361,47 +342,22 @@ impl<'a> ConnectConfig<'a> { self } - /// Configure ALPN protocol names to send in the ClientHello. - /// - /// The server will select one of the offered protocols and echo it back - /// in EncryptedExtensions. This is required for endpoints that multiplex - /// protocols on a single port (e.g. AWS IoT Core MQTT over port 443). pub fn with_alpn(mut self, protocols: &'a [&'a [u8]]) -> Self { self.alpn_protocols = Some(protocols); self } - /// Configures the maximum plaintext fragment size. - /// - /// This option may help reduce memory size, as smaller fragment lengths require smaller - /// read/write buffers. Note that mote-tls does not currently use this option to fragment - /// writes. Note that the buffers need to include some overhead over the configured fragment - /// length. - /// - /// From [RFC 6066, Section 4. Maximum Fragment Length Negotiation](https://www.rfc-editor.org/rfc/rfc6066#page-8): - /// - /// > Without this extension, TLS specifies a fixed maximum plaintext - /// > fragment length of 2^14 bytes. It may be desirable for constrained - /// > clients to negotiate a smaller maximum fragment length due to memory - /// > limitations or bandwidth limitations. - /// - /// > For example, if the negotiated length is 2^9=512, then, when using currently defined - /// > cipher suites ([...]) and null compression, the record-layer output can be at most - /// > 805 bytes: 5 bytes of headers, 512 bytes of application data, 256 bytes of padding, - /// > and 32 bytes of MAC. pub fn with_max_fragment_length(mut self, max_fragment_length: MaxFragmentLength) -> Self { self.max_fragment_length = Some(max_fragment_length); self } - /// Resets the max fragment length to 14 bits (16384). pub fn reset_max_fragment_length(mut self) -> Self { self.max_fragment_length = None; self } pub fn with_psk(mut self, psk: &'a [u8], identities: &[&'a [u8]]) -> Self { - // TODO: Remove potential panic self.psk = Some((psk, unwrap!(Vec::from_slice(identities).ok()))); self } diff --git a/src/connection.rs b/src/connection.rs index 9b956ec..5f273d7 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -25,6 +25,8 @@ use crate::content_types::ContentType; use crate::parse_buffer::ParseBuffer; use aes_gcm::aead::{AeadCore, AeadInPlace, KeyInit}; +// Decrypts an ApplicationData record in-place, then dispatches the inner content type to `cb`. +// Plaintext records (Handshake, ChangeCipherSpec) are forwarded to `cb` without decryption. pub(crate) fn decrypt_record( key_schedule: &mut ReadKeySchedule, record: ServerRecord<'_, CipherSuite>, @@ -49,6 +51,7 @@ where .decrypt_in_place(&nonce, header.data(), &mut app_data) .map_err(|_| ProtocolError::CryptoError)?; + // Strip TLS 1.3 inner-content padding: trailing zero bytes before the real content type let padding = app_data .as_slice() .iter() @@ -58,18 +61,17 @@ where app_data.truncate(index + 1); }; + // The last byte of the decrypted payload is the actual ContentType (RFC 8446 §5.4) let content_type = ContentType::of(*app_data.as_slice().last().unwrap()).ok_or(ProtocolError::InvalidRecord)?; trace!("Decrypting: content type = {:?}", content_type); - // Remove the content type app_data.truncate(app_data.len() - 1); let mut buf = ParseBuffer::new(app_data.as_slice()); match content_type { ContentType::Handshake => { - // Decode potentially coalesced handshake messages while buf.remaining() > 0 { let inner = ServerHandshake::read(&mut buf, key_schedule.transcript_hash())?; cb(key_schedule, ServerRecord::Handshake(inner))?; @@ -102,10 +104,6 @@ where { let client_key = key_schedule.get_key()?; let nonce = key_schedule.get_nonce()?; - // trace!("encrypt key {:02x?}", client_key); - // trace!("encrypt nonce {:02x?}", nonce); - // trace!("plaintext {} {:02x?}", buf.len(), buf.as_slice(),); - //let crypto = Aes128Gcm::new_varkey(&self.key_schedule.get_client_key()).unwrap(); let crypto = ::new(client_key); let len = buf.len() + ::TagSize::to_usize(); @@ -115,6 +113,7 @@ where trace!("output size {}", len); let len_bytes = (len as u16).to_be_bytes(); + // Additional data is the TLS record header (type=ApplicationData, legacy version 0x0303, length) let additional_data = [ ContentType::ApplicationData as u8, 0x03, @@ -128,10 +127,12 @@ where .map_err(|_| ProtocolError::InvalidApplicationData) } +/// Ephemeral state held between handshake steps — discarded once the handshake completes. pub struct Handshake where CipherSuite: TlsCipherSuite, { + // Saved pre-master transcript hash used for Finished after a certificate exchange traffic_hash: Option, secret: Option, certificate_request: Option, @@ -150,6 +151,7 @@ where } } +/// TLS handshake state machine. Drives the client through all stages of the TLS 1.3 handshake. #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum State { @@ -470,13 +472,13 @@ where ServerHandshake::CertificateRequest(request) => { handshake.certificate_request.replace(request.try_into()?); } - ServerHandshake::Finished(finished) => { + ServerHandshake::Finished(finished) => { if !key_schedule.verify_server_finished(&finished)? { warn!("Server signature verification failed"); return Err(ProtocolError::InvalidSignature); } - // trace!("server verified {}", verified); + // If the server sent a CertificateRequest we must respond with a cert before Finished state = if handshake.certificate_request.is_some() { State::ClientCert } else { @@ -517,7 +519,6 @@ where .ok_or(ProtocolError::InvalidHandshake)? .request_context; - // Declare cert before certificate so owned data outlives the CertificateRef that borrows it let cert = crypto_provider.client_cert(); let mut certificate = CertificateRef::with_context(request_context); let next_state = if let Some(ref cert) = cert { @@ -547,9 +548,9 @@ where { let (result, record) = match crypto_provider.signer() { Ok((mut signing_key, signature_scheme)) => { + // CertificateVerify message format: 64 spaces + context string + \0 + transcript hash (RFC 8446 §4.4.3) let ctx_str = b"TLS 1.3, client CertificateVerify\x00"; - // 64 (pad) + 34 (ctx) + 48 (SHA-384) = 146 bytes required let mut msg: heapless::Vec = heapless::Vec::new(); msg.resize(64, 0x20).map_err(|_| ProtocolError::EncodeError)?; msg.extend_from_slice(ctx_str) @@ -624,6 +625,7 @@ fn client_finished_finalize( where CipherSuite: TlsCipherSuite, { + // Restore the transcript hash captured before the client cert exchange, then derive app traffic secrets key_schedule.replace_transcript_hash( handshake .traffic_hash diff --git a/src/extensions/extension_data/alpn.rs b/src/extensions/extension_data/alpn.rs index 48444aa..2624e3a 100644 --- a/src/extensions/extension_data/alpn.rs +++ b/src/extensions/extension_data/alpn.rs @@ -4,16 +4,6 @@ use crate::{ parse_buffer::{ParseBuffer, ParseError}, }; -/// ALPN protocol name list per RFC 7301, Section 3.1. -/// -/// Wire format: -/// ```text -/// opaque ProtocolName<1..2^8-1>; -/// -/// struct { -/// ProtocolName protocol_name_list<2..2^16-1> -/// } ProtocolNameList; -/// ``` #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct AlpnProtocolNameList<'a> { @@ -22,12 +12,6 @@ pub struct AlpnProtocolNameList<'a> { impl<'a> AlpnProtocolNameList<'a> { pub fn parse(buf: &mut ParseBuffer<'a>) -> Result { - // We parse but don't store the individual protocol names in a heapless - // container — just validate the wire format. The slice reference is kept - // for the lifetime of the parse buffer, but since we can't reconstruct - // `&[&[u8]]` from a flat buffer without allocation, we store an empty - // slice. Callers that need the parsed protocols (server-side) would need - // a different approach; for our client-side use we only need encode(). let list_len = buf.read_u16()? as usize; let mut list_buf = buf.slice(list_len)?; @@ -43,7 +27,6 @@ impl<'a> AlpnProtocolNameList<'a> { } pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> { - // Outer u16 length prefix for the ProtocolNameList buf.with_u16_length(|buf| { for protocol in self.protocols { buf.push(protocol.len() as u8) diff --git a/src/extensions/extension_data/key_share.rs b/src/extensions/extension_data/key_share.rs index 31638c7..3b8144f 100644 --- a/src/extensions/extension_data/key_share.rs +++ b/src/extensions/extension_data/key_share.rs @@ -109,8 +109,8 @@ mod tests { fn test_parse_empty() { setup(); let buffer = [ - 0x00, 0x17, // Secp256r1 - 0x00, 0x00, // key_exchange length = 0 bytes + 0x00, 0x17, + 0x00, 0x00, ]; let result = KeyShareEntry::parse(&mut ParseBuffer::new(&buffer)).unwrap(); @@ -122,8 +122,8 @@ mod tests { fn test_parse() { setup(); let buffer = [ - 0x00, 0x17, // Secp256r1 - 0x00, 0x02, // key_exchange length = 2 bytes + 0x00, 0x17, + 0x00, 0x02, 0xAA, 0xBB, ]; let result = KeyShareEntry::parse(&mut ParseBuffer::new(&buffer)).unwrap(); diff --git a/src/extensions/extension_data/max_fragment_length.rs b/src/extensions/extension_data/max_fragment_length.rs index 881b27a..d8a30a9 100644 --- a/src/extensions/extension_data/max_fragment_length.rs +++ b/src/extensions/extension_data/max_fragment_length.rs @@ -4,23 +4,12 @@ use crate::{ parse_buffer::{ParseBuffer, ParseError}, }; -/// Maximum plaintext fragment length -/// -/// RFC 6066, Section 4. Maximum Fragment Length Negotiation -/// Without this extension, TLS specifies a fixed maximum plaintext -/// fragment length of 2^14 bytes. It may be desirable for constrained -/// clients to negotiate a smaller maximum fragment length due to memory -/// limitations or bandwidth limitations. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum MaxFragmentLength { - /// 512 bytes Bits9 = 1, - /// 1024 bytes Bits10 = 2, - /// 2048 bytes Bits11 = 3, - /// 4096 bytes Bits12 = 4, } diff --git a/src/extensions/extension_data/pre_shared_key.rs b/src/extensions/extension_data/pre_shared_key.rs index 49f4875..89836c1 100644 --- a/src/extensions/extension_data/pre_shared_key.rs +++ b/src/extensions/extension_data/pre_shared_key.rs @@ -23,14 +23,12 @@ impl PreSharedKeyClientHello<'_, N> { buf.with_u16_length(|buf| buf.extend_from_slice(identity)) .map_err(|_| ProtocolError::EncodeError)?; - // NOTE: No support for ticket age, set to 0 as recommended by RFC buf.push_u32(0).map_err(|_| ProtocolError::EncodeError)?; } Ok(()) }) .map_err(|_| ProtocolError::EncodeError)?; - // NOTE: We encode binders later after computing the transcript. let binders_len = (1 + self.hash_size) * self.identities.len(); buf.push_u16(binders_len as u16) .map_err(|_| ProtocolError::EncodeError)?; diff --git a/src/extensions/extension_data/server_name.rs b/src/extensions/extension_data/server_name.rs index 14cc25c..bbe22fe 100644 --- a/src/extensions/extension_data/server_name.rs +++ b/src/extensions/extension_data/server_name.rs @@ -42,9 +42,6 @@ impl<'a> ServerName<'a> { let name_len = buf.read_u16()?; let name = buf.slice(name_len as usize)?.as_slice(); - // RFC 6066, Section 3. Server Name Indication - // The hostname is represented as a byte - // string using ASCII encoding without a trailing dot. if name.is_ascii() { Ok(ServerName { name_type, @@ -107,12 +104,6 @@ impl<'a, const N: usize> ServerNameList<'a, N> { } } -// RFC 6066, Section 3. Server Name Indication -// A server that receives a client hello containing the "server_name" -// extension [..]. In this event, the server -// SHALL include an extension of type "server_name" in the (extended) -// server hello. The "extension_data" field of this extension SHALL be -// empty. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ServerNameResponse; diff --git a/src/extensions/extension_data/signature_algorithms.rs b/src/extensions/extension_data/signature_algorithms.rs index 5a3a2c5..6471e2b 100644 --- a/src/extensions/extension_data/signature_algorithms.rs +++ b/src/extensions/extension_data/signature_algorithms.rs @@ -9,26 +9,21 @@ use heapless::Vec; #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SignatureScheme { - /* RSASSA-PKCS1-v1_5 algorithms */ RsaPkcs1Sha256, RsaPkcs1Sha384, RsaPkcs1Sha512, - /* ECDSA algorithms */ EcdsaSecp256r1Sha256, EcdsaSecp384r1Sha384, EcdsaSecp521r1Sha512, - /* RSASSA-PSS algorithms with public key OID rsaEncryption */ RsaPssRsaeSha256, RsaPssRsaeSha384, RsaPssRsaeSha512, - /* EdDSA algorithms */ Ed25519, Ed448, - /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ RsaPssPssSha256, RsaPssPssSha384, RsaPssPssSha512, @@ -37,22 +32,16 @@ pub enum SignatureScheme { Sha224Rsa, Sha224Dsa, - /* Legacy algorithms */ RsaPkcs1Sha1, EcdsaSha1, - /* Brainpool */ Sha256BrainpoolP256r1, Sha384BrainpoolP384r1, Sha512BrainpoolP512r1, - /* ML-DSA */ MlDsa44, MlDsa65, MlDsa87, - /* Reserved Code Points */ - //private_use(0xFE00..0xFFFF), - //(0xFFFF) } impl SignatureScheme { diff --git a/src/extensions/extension_data/supported_groups.rs b/src/extensions/extension_data/supported_groups.rs index 3e3c69c..db9ea8e 100644 --- a/src/extensions/extension_data/supported_groups.rs +++ b/src/extensions/extension_data/supported_groups.rs @@ -9,21 +9,18 @@ use crate::{ #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum NamedGroup { - /* Elliptic Curve Groups (ECDHE) */ Secp256r1, Secp384r1, Secp521r1, X25519, X448, - /* Finite Field Groups (DHE) */ Ffdhe2048, Ffdhe3072, Ffdhe4096, Ffdhe6144, Ffdhe8192, - /* Post-quantum hybrid groups */ X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, diff --git a/src/extensions/extension_group_macro.rs b/src/extensions/extension_group_macro.rs index 3d3e362..a370b49 100644 --- a/src/extensions/extension_group_macro.rs +++ b/src/extensions/extension_group_macro.rs @@ -4,12 +4,12 @@ macro_rules! extension_group { }) => { #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[allow(dead_code)] // extension_data may not be used + #[allow(dead_code)] pub enum $name$(<$lt>)? { $($extension($extension_data)),+ } - #[allow(dead_code)] // not all methods are used + #[allow(dead_code)] impl$(<$lt>)? $name$(<$lt>)? { pub fn extension_type(&self) -> crate::extensions::ExtensionType { match self { @@ -26,7 +26,6 @@ macro_rules! extension_group { } pub fn parse(buf: &mut crate::parse_buffer::ParseBuffer$(<$lt>)?) -> Result { - // Consume extension data even if we don't recognize the extension let extension_type = crate::extensions::ExtensionType::parse(buf); let data_len = buf.read_u16().map_err(|_| crate::ProtocolError::DecodeError)? as usize; let mut ext_data = buf.slice(data_len).map_err(|_| crate::ProtocolError::DecodeError)?; @@ -51,11 +50,6 @@ macro_rules! extension_group { #[allow(unreachable_patterns)] other => { warn!("Read unexpected ExtensionType: {:?}", other); - // Section 4.2. Extensions - // If an implementation receives an extension - // which it recognizes and which is not specified for the message in - // which it appears, it MUST abort the handshake with an - // "illegal_parameter" alert. Err(crate::ProtocolError::AbortHandshake( crate::alert::AlertLevel::Fatal, crate::alert::AlertDescription::IllegalParameter, @@ -84,7 +78,6 @@ macro_rules! extension_group { .map_err(|_| crate::ProtocolError::DecodeError)?; } Err(crate::ProtocolError::UnknownExtensionType) => { - // ignore unrecognized extension type } Err(err) => return Err(err), } @@ -97,6 +90,4 @@ macro_rules! extension_group { }; } -// This re-export makes it possible to omit #[macro_export] -// https://stackoverflow.com/a/67140319 pub(crate) use extension_group; diff --git a/src/extensions/messages.rs b/src/extensions/messages.rs index a133550..e2e6e25 100644 --- a/src/extensions/messages.rs +++ b/src/extensions/messages.rs @@ -15,7 +15,6 @@ use crate::extensions::{ extension_group_macro::extension_group, }; -// Source: https://www.rfc-editor.org/rfc/rfc8446#section-4.2 table, rows marked with CH extension_group! { pub enum ClientHelloExtension<'a> { ServerName(ServerNameList<'a, 1>), @@ -43,17 +42,15 @@ extension_group! { } } -// Source: https://www.rfc-editor.org/rfc/rfc8446#section-4.2 table, rows marked with SH extension_group! { pub enum ServerHelloExtension<'a> { KeyShare(KeyShareServerHello<'a>), PreSharedKey(PreSharedKeyServerHello), - Cookie(Unimplemented<'a>), // temporary so we don't trip up on HelloRetryRequests + Cookie(Unimplemented<'a>), SupportedVersions(SupportedVersionsServerHello) } } -// Source: https://www.rfc-editor.org/rfc/rfc8446#section-4.2 table, rows marked with EE extension_group! { pub enum EncryptedExtensionsExtension<'a> { ServerName(ServerNameResponse), @@ -68,7 +65,6 @@ extension_group! { } } -// Source: https://www.rfc-editor.org/rfc/rfc8446#section-4.2 table, rows marked with CR extension_group! { pub enum CertificateRequestExtension<'a> { StatusRequest(Unimplemented<'a>), @@ -81,7 +77,6 @@ extension_group! { } } -// Source: https://www.rfc-editor.org/rfc/rfc8446#section-4.2 table, rows marked with CT extension_group! { pub enum CertificateExtension<'a> { StatusRequest(Unimplemented<'a>), @@ -89,14 +84,12 @@ extension_group! { } } -// Source: https://www.rfc-editor.org/rfc/rfc8446#section-4.2 table, rows marked with NST extension_group! { pub enum NewSessionTicketExtension<'a> { EarlyData(Unimplemented<'a>) } } -// Source: https://www.rfc-editor.org/rfc/rfc8446#section-4.2 table, rows marked with HRR extension_group! { pub enum HelloRetryRequestExtension<'a> { KeyShare(Unimplemented<'a>), diff --git a/src/handshake/binder.rs b/src/handshake/binder.rs index 645bc85..0d44a8e 100644 --- a/src/handshake/binder.rs +++ b/src/handshake/binder.rs @@ -1,9 +1,7 @@ use crate::ProtocolError; use crate::buffer::CryptoBuffer; use core::fmt::{Debug, Formatter}; -//use digest::generic_array::{ArrayLength, GenericArray}; use generic_array::{ArrayLength, GenericArray}; -// use heapless::Vec; pub struct PskBinder> { pub verify: GenericArray, @@ -25,7 +23,6 @@ impl> Debug for PskBinder { impl> PskBinder { pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> { let len = self.verify.len() as u8; - //buf.extend_from_slice(&[len[1], len[2], len[3]]); buf.push(len).map_err(|_| ProtocolError::EncodeError)?; buf.extend_from_slice(&self.verify[..self.verify.len()]) .map_err(|_| ProtocolError::EncodeError)?; diff --git a/src/handshake/certificate.rs b/src/handshake/certificate.rs index c3ef5b7..c8d8bed 100644 --- a/src/handshake/certificate.rs +++ b/src/handshake/certificate.rs @@ -79,7 +79,6 @@ impl<'a> CertificateEntryRef<'a> { let entry = CertificateEntryRef::X509(cert.as_slice()); - // Validate extensions CertificateExtension::parse_vector::<2>(buf)?; Ok(entry) @@ -103,14 +102,12 @@ impl<'a> CertificateEntryRef<'a> { match *self { CertificateEntryRef::RawPublicKey(_key) => { todo!("ASN1_subjectPublicKeyInfo encoding?"); - // buf.with_u24_length(|buf| buf.extend_from_slice(key))?; } CertificateEntryRef::X509(cert) => { buf.with_u24_length(|buf| buf.extend_from_slice(cert))?; } } - // Zero extensions for now buf.push_u16(0)?; Ok(()) } diff --git a/src/handshake/certificate_request.rs b/src/handshake/certificate_request.rs index c17d83d..32b1a38 100644 --- a/src/handshake/certificate_request.rs +++ b/src/handshake/certificate_request.rs @@ -18,7 +18,6 @@ impl<'a> CertificateRequestRef<'a> { .slice(request_context_len as usize) .map_err(|_| ProtocolError::InvalidCertificateRequest)?; - // Validate extensions let extensions = CertificateRequestExtension::parse_vector::<6>(buf)?; unused(extensions); diff --git a/src/handshake/certificate_verify.rs b/src/handshake/certificate_verify.rs index 167a028..baf151c 100644 --- a/src/handshake/certificate_verify.rs +++ b/src/handshake/certificate_verify.rs @@ -28,13 +28,6 @@ impl<'a> HandshakeVerifyRef<'a> { } } -// Calculations for max. signature sizes: -// ecdsaSHA256 -> 6 bytes (ASN.1 structure) + 32-33 bytes (r) + 32-33 bytes (s) = 70..72 bytes -// ecdsaSHA384 -> 6 bytes (ASN.1 structure) + 48-49 bytes (r) + 48-49 bytes (s) = 102..104 bytes -// Ed25519 -> 6 bytes (ASN.1 structure) + 32-33 bytes (r) + 32-33 bytes (s) = 70..72 bytes -// RSA2048 -> 256 bytes -// RSA3072 -> 384 bytee -// RSA4096 -> 512 bytes #[cfg(feature = "rsa")] const SIGNATURE_SIZE: usize = 512; #[cfg(not(feature = "rsa"))] diff --git a/src/handshake/client_hello.rs b/src/handshake/client_hello.rs index 6ddc26d..8965253 100644 --- a/src/handshake/client_hello.rs +++ b/src/handshake/client_hello.rs @@ -62,28 +62,19 @@ where buf.extend_from_slice(&self.random) .map_err(|_| ProtocolError::EncodeError)?; - // session id (empty) + // Empty legacy session ID — TLS 1.3 doesn't use it, but the field must be present buf.push(0).map_err(|_| ProtocolError::EncodeError)?; - // cipher suites (2+) - //buf.extend_from_slice(&((self.config.cipher_suites.len() * 2) as u16).to_be_bytes()); - //for c in self.config.cipher_suites.iter() { - //buf.extend_from_slice(&(*c as u16).to_be_bytes()); - //} + // Exactly one cipher suite entry (2-byte length prefix + 2-byte code point) buf.push_u16(2).map_err(|_| ProtocolError::EncodeError)?; buf.push_u16(CipherSuite::CODE_POINT) .map_err(|_| ProtocolError::EncodeError)?; - // compression methods, 1 byte of 0 + // Legacy compression methods: one entry, 0x00 = no compression buf.push(1).map_err(|_| ProtocolError::EncodeError)?; buf.push(0).map_err(|_| ProtocolError::EncodeError)?; - // extensions (1+) buf.with_u16_length(|buf| { - // Section 4.2.1. Supported Versions - // Implementations of this specification MUST send this extension in the - // ClientHello containing all versions of TLS which they are prepared to - // negotiate ClientHelloExtension::SupportedVersions(SupportedVersionsClientHello { versions: Vec::from_slice(&[TLS13]).unwrap(), }) @@ -129,11 +120,6 @@ where .encode(buf)?; } - // Section 4.2 - // When multiple extensions of different types are present, the - // extensions MAY appear in any order, with the exception of - // "pre_shared_key" which MUST be the last extension in - // the ClientHello. if let Some((_, identities)) = &self.config.psk { ClientHelloExtension::PreSharedKey(PreSharedKeyClientHello { identities: identities.clone(), @@ -154,25 +140,16 @@ where transcript: &mut CipherSuite::Hash, write_key_schedule: &mut WriteKeySchedule, ) -> Result<(), ProtocolError> { - // Special case for PSK which needs to: - // - // 1. Add the client hello without the binders to the transcript - // 2. Create the binders for each identity using the transcript - // 3. Add the rest of the client hello. - // - // This causes a few issues since lengths must be correctly inside the payload, - // but won't actually be added to the record buffer until the end. if let Some((_, identities)) = &self.config.psk { + // PSK binders depend on the transcript up to (but not including) the binder values, + // so we hash the partial message, compute binders, then hash the remainder (RFC 8446 §4.2.11.2) let binders_len = identities.len() * (1 + HashOutputSize::::to_usize()); let binders_pos = enc_buf.len() - binders_len; - // NOTE: Exclude the binders_len itself from the digest transcript.update(&enc_buf[0..binders_pos - 2]); - // Append after the client hello data. Sizes have already been set. let mut buf = CryptoBuffer::wrap(&mut enc_buf[binders_pos..]); - // Create a binder and encode for each identity for _id in identities { let binder = write_key_schedule.create_psk_binder(transcript)?; binder.encode(&mut buf)?; diff --git a/src/handshake/finished.rs b/src/handshake/finished.rs index d4246de..c1e13ea 100644 --- a/src/handshake/finished.rs +++ b/src/handshake/finished.rs @@ -2,10 +2,12 @@ use crate::ProtocolError; use crate::buffer::CryptoBuffer; use crate::parse_buffer::ParseBuffer; use core::fmt::{Debug, Formatter}; -//use digest::generic_array::{ArrayLength, GenericArray}; use generic_array::{ArrayLength, GenericArray}; -// use heapless::Vec; +/// TLS Finished message: contains an HMAC over the handshake transcript (RFC 8446 §4.4.4). +/// +/// `hash` holds the transcript hash snapshot taken just before this message was received; +/// it is `None` when the struct is used for a locally-generated Finished message. pub struct Finished> { pub verify: GenericArray, pub hash: Option>, @@ -28,23 +30,12 @@ impl> Debug for Finished { impl> Finished { pub fn parse(buf: &mut ParseBuffer, _len: u32) -> Result { - // info!("finished len: {}", len); let mut verify = GenericArray::default(); buf.fill(&mut verify)?; - //let hash = GenericArray::from_slice() - //let hash: Result, ()> = buf - //.slice(len as usize) - //.map_err(|_| ProtocolError::InvalidHandshake)? - //.into(); - // info!("hash {:?}", verify); - //let hash = hash.map_err(|_| ProtocolError::InvalidHandshake)?; - // info!("hash ng {:?}", verify); Ok(Self { verify, hash: None }) } pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> { - //let len = self.verify.len().to_be_bytes(); - //buf.extend_from_slice(&[len[1], len[2], len[3]]); buf.extend_from_slice(&self.verify[..self.verify.len()]) .map_err(|_| ProtocolError::EncodeError)?; Ok(()) diff --git a/src/handshake/mod.rs b/src/handshake/mod.rs index 902bf93..f692d0e 100644 --- a/src/handshake/mod.rs +++ b/src/handshake/mod.rs @@ -1,4 +1,3 @@ -//use p256::elliptic_curve::AffinePoint; use crate::ProtocolError; use crate::config::TlsCipherSuite; use crate::handshake::certificate::CertificateRef; @@ -25,6 +24,7 @@ pub mod finished; pub mod new_session_ticket; pub mod server_hello; +// TLS legacy_record_version field — always 0x0303 for TLS 1.3 compatibility (RFC 8446 §5.1) const LEGACY_VERSION: u16 = 0x0303; type Random = [u8; 32]; @@ -101,6 +101,7 @@ where buf.push(self.handshake_type() as u8) .map_err(|_| ProtocolError::EncodeError)?; + // Handshake message body is preceded by a 3-byte (u24) length (RFC 8446 §4) buf.with_u24_length(|buf| self.encode_inner(buf)) } @@ -190,6 +191,7 @@ impl<'a, CipherSuite: TlsCipherSuite> ServerHandshake<'a, CipherSuite> { let mut handshake = Self::parse(buf)?; let handshake_end = buf.offset(); + // Capture the current transcript hash into Finished before we update it with this message if let ServerHandshake::Finished(finished) = &mut handshake { finished.hash.replace(digest.clone().finalize()); } @@ -207,12 +209,10 @@ impl<'a, CipherSuite: TlsCipherSuite> ServerHandshake<'a, CipherSuite> { let content_len = buf.read_u24().map_err(|_| ProtocolError::InvalidHandshake)?; let handshake = match handshake_type { - //HandshakeType::ClientHello => {} HandshakeType::ServerHello => ServerHandshake::ServerHello(ServerHello::parse(buf)?), HandshakeType::NewSessionTicket => { ServerHandshake::NewSessionTicket(NewSessionTicket::parse(buf)?) } - //HandshakeType::EndOfEarlyData => {} HandshakeType::EncryptedExtensions => { ServerHandshake::EncryptedExtensions(EncryptedExtensions::parse(buf)?) } @@ -228,8 +228,6 @@ impl<'a, CipherSuite: TlsCipherSuite> ServerHandshake<'a, CipherSuite> { HandshakeType::Finished => { ServerHandshake::Finished(Finished::parse(buf, content_len)?) } - //HandshakeType::KeyUpdate => {} - //HandshakeType::MessageHash => {} t => { warn!("Unimplemented handshake type: {:?}", t); return Err(ProtocolError::Unimplemented); diff --git a/src/handshake/server_hello.rs b/src/handshake/server_hello.rs index 77ecc82..f524941 100644 --- a/src/handshake/server_hello.rs +++ b/src/handshake/server_hello.rs @@ -17,9 +17,7 @@ pub struct ServerHello<'a> { impl<'a> ServerHello<'a> { pub fn parse(buf: &mut ParseBuffer<'a>) -> Result, ProtocolError> { - //let mut buf = ParseBuffer::new(&buf[0..content_length]); - //let mut buf = ParseBuffer::new(&buf); - + // legacy_version is always 0x0303 in TLS 1.3; actual version is negotiated via extensions let _version = buf.read_u16().map_err(|_| ProtocolError::InvalidHandshake)?; let mut random = [0; 32]; @@ -29,23 +27,18 @@ impl<'a> ServerHello<'a> { .read_u8() .map_err(|_| ProtocolError::InvalidSessionIdLength)?; - //info!("sh 1"); - + // Legacy session ID echo: TLS 1.3 servers echo the client's session ID for middlebox compatibility let session_id = buf .slice(session_id_length as usize) .map_err(|_| ProtocolError::InvalidSessionIdLength)?; - //info!("sh 2"); let cipher_suite = CipherSuite::parse(buf).map_err(|_| ProtocolError::InvalidCipherSuite)?; - ////info!("sh 3"); - // skip compression method, it's 0. + // compression_method: always 0x00 in TLS 1.3 buf.read_u8()?; let extensions = ServerHelloExtension::parse_vector(buf)?; - // debug!("server random {:x}", random); - // debug!("server session-id {:x}", session_id.as_slice()); debug!("server cipher_suite {:?}", cipher_suite); debug!("server extensions {:?}", extensions); @@ -63,6 +56,7 @@ impl<'a> ServerHello<'a> { }) } + /// Performs ECDH with the server's key share to derive the shared secret used in the handshake. pub fn calculate_shared_secret(&self, secret: &EphemeralSecret) -> Option { let server_key_share = self.key_share()?; let server_public_key = PublicKey::from_sec1_bytes(server_key_share.opaque).ok()?; diff --git a/src/key_schedule.rs b/src/key_schedule.rs index 5cae611..77b5eba 100644 --- a/src/key_schedule.rs +++ b/src/key_schedule.rs @@ -13,6 +13,7 @@ pub type LabelBufferSize = ::LabelBu pub type IvArray = GenericArray::IvLen>; pub type KeyArray = GenericArray::KeyLen>; +/// Hash-sized byte array, used as the HKDF secret at each key schedule stage. pub type HashArray = GenericArray>; type Hkdf = hkdf::Hkdf< @@ -43,17 +44,19 @@ where } } + // HKDF-Expand-Label as defined in RFC 8446 §7.1 fn make_expanded_hkdf_label>( &self, label: &[u8], context_type: ContextType, ) -> Result, ProtocolError> { - //info!("make label {:?} {}", label, len); let mut hkdf_label = heapless_typenum::Vec::>::new(); + // Length field: desired output length as u16 big-endian hkdf_label .extend_from_slice(&N::to_u16().to_be_bytes()) .map_err(|()| ProtocolError::InternalError)?; + // TLS 1.3 labels are prefixed with "tls13 " (6 bytes) let label_len = 6 + label.len() as u8; hkdf_label .extend_from_slice(&label_len.to_be_bytes()) @@ -80,11 +83,9 @@ where } let mut okm = GenericArray::default(); - //info!("label {:x?}", label); self.as_ref()? .expand(&hkdf_label, &mut okm) .map_err(|_| ProtocolError::CryptoError)?; - //info!("expand {:x?}", okm); Ok(okm) } } @@ -108,6 +109,7 @@ where } } + // HKDF-Extract, chaining the previous stage's secret as the salt (RFC 8446 §7.1) fn initialize(&mut self, ikm: &[u8]) { let (secret, hkdf) = Hkdf::::extract(Some(self.secret.as_ref()), ikm); self.hkdf.replace(hkdf); @@ -123,6 +125,7 @@ where .make_expanded_hkdf_label::>(label, context_type) } + // "Derive-Secret(Secret, "derived", "")" — used to chain stages per RFC 8446 §7.1 fn derived(&mut self) -> Result<(), ProtocolError> { self.secret = self.derive_secret(b"derived", ContextType::empty_hash())?; Ok(()) @@ -178,6 +181,7 @@ where Hkdf::::from_prk(&secret).map_err(|_| ProtocolError::InternalError)?; self.traffic_secret.replace(traffic_secret); + // Derive per-record key and IV from the traffic secret (RFC 8446 §7.3) self.key = self .traffic_secret .make_expanded_hkdf_label(b"key", ContextType::None)?; @@ -294,11 +298,9 @@ where } fn get_nonce(counter: u64, iv: &IvArray) -> IvArray { - //info!("counter = {} {:x?}", counter, &counter.to_be_bytes(),); + // Per-record nonce: XOR the static IV with the zero-padded sequence counter (RFC 8446 §5.3) let counter = Self::pad::(&counter.to_be_bytes()); - //info!("counter = {:x?}", counter); - // info!("iv = {:x?}", iv); let mut nonce = GenericArray::default(); @@ -310,21 +312,14 @@ where nonce[index] = l ^ r; } - //debug!("nonce {:x?}", nonce); nonce } + // Right-aligns `input` bytes in a zero-padded array of length N (big-endian padding) fn pad>(input: &[u8]) -> GenericArray { - // info!("padding input = {:x?}", input); let mut padded = GenericArray::default(); for (index, byte) in input.iter().rev().enumerate() { - /*info!( - "{} pad {}={:x?}", - index, - ((N::to_usize() - index) - 1), - *byte - );*/ padded[(N::to_usize() - index) - 1] = *byte; } padded @@ -334,8 +329,8 @@ where GenericArray::default() } - // Initializes the early secrets with a callback for any PSK binders pub fn initialize_early_secret(&mut self, psk: Option<&[u8]>) -> Result<(), ProtocolError> { + // IKM is 0-bytes when no PSK is used — still required to derive the binder key self.shared.initialize( #[allow(clippy::or_fun_call)] psk.unwrap_or(Self::zero().as_slice()), @@ -358,10 +353,9 @@ where } pub fn initialize_master_secret(&mut self) -> Result<(), ProtocolError> { + // IKM is all-zeros at the master secret stage (RFC 8446 §7.1) self.shared.initialize(Self::zero().as_slice()); - //let context = self.transcript_hash.as_ref().unwrap().clone().finalize(); - //info!("Derive keys, hash: {:x?}", context); self.calculate_traffic_secrets(b"c ap traffic", b"s ap traffic")?; self.shared.derived() @@ -471,9 +465,6 @@ where &self, finished: &Finished>, ) -> Result { - //info!("verify server finished: {:x?}", finished.verify); - //self.client_traffic_secret.as_ref().unwrap().expand() - //info!("size ===> {}", D::OutputSize::to_u16()); let key = self .state .traffic_secret @@ -481,7 +472,6 @@ where b"finished", ContextType::None, )?; - // info!("hmac sign key {:x?}", key); let mut hmac = SimpleHmac::::new_from_slice(&key) .map_err(|_| ProtocolError::InternalError)?; Mac::update( @@ -491,9 +481,6 @@ where ProtocolError::InternalError })?, ); - //let code = hmac.clone().finalize().into_bytes(); Ok(hmac.verify(&finished.verify).is_ok()) - //info!("verified {:?}", verified); - //unimplemented!() } } diff --git a/src/lib.rs b/src/lib.rs index 0787858..4fd1d39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,49 +4,10 @@ clippy::module_name_repetitions, clippy::cast_possible_truncation, clippy::cast_sign_loss, - clippy::missing_errors_doc // TODO + clippy::missing_errors_doc )] -/*! -# Example -``` -use mote_tls::*; -use embedded_io_adapters::tokio_1::FromTokio; -use rand::rngs::OsRng; -use tokio::net::TcpStream; - -#[tokio::main] -async fn main() { - let stream = TcpStream::connect("google.com:443") - .await - .expect("error creating TCP connection"); - - println!("TCP connection opened"); - let mut read_record_buffer = [0; 16384]; - let mut write_record_buffer = [0; 16384]; - let config = ConnectConfig::new().with_server_name("google.com").enable_rsa_signatures(); - let mut tls = SecureStream::new( - FromTokio::new(stream), - &mut read_record_buffer, - &mut write_record_buffer, - ); - - // Allows disabling cert verification, in case you are using PSK and don't need it, or are just testing. - // otherwise, use mote_tls::cert_verify::CertVerifier, which only works on std for now. - tls.open(ConnectContext::new( - &config, - SkipVerifyProvider::new::(OsRng), - )) - .await - .expect("error establishing TLS connection"); - - println!("TLS session opened"); -} -``` -*/ - -// This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; use parse_buffer::ParseError; @@ -163,7 +124,4 @@ mod stdlib { } } -/// An internal function to mark an unused value. -/// -/// All calls to this should be removed before 1.x. fn unused(_: T) {} diff --git a/src/native_pki.rs b/src/native_pki.rs index ab6abf5..c1bbfc8 100644 --- a/src/native_pki.rs +++ b/src/native_pki.rs @@ -268,7 +268,6 @@ fn get_certificate_tlv_bytes<'a>(input: &[u8]) -> der::Result<&[u8]> { let header = der::Header::peek(&mut reader)?; header.tag().assert_eq(der::Tag::Sequence)?; - // Should we read the remaining two fields and call reader.finish() just be certain here? reader.tlv_bytes() } diff --git a/src/parse_buffer.rs b/src/parse_buffer.rs index afe4275..f8b6f74 100644 --- a/src/parse_buffer.rs +++ b/src/parse_buffer.rs @@ -68,7 +68,6 @@ impl<'b> ParseBuffer<'b> { } pub fn read_u16(&mut self) -> Result { - //info!("pos={} len={}", self.pos, self.buffer.len()); if self.pos + 2 <= self.buffer.len() { let value = u16::from_be_bytes([self.buffer[self.pos], self.buffer[self.pos + 1]]); self.pos += 2; @@ -112,7 +111,6 @@ impl<'b> ParseBuffer<'b> { if self.pos + dest.len() <= self.buffer.len() { dest.copy_from_slice(&self.buffer[self.pos..self.pos + dest.len()]); self.pos += dest.len(); - // info!("Copied {} bytes", dest.len()); Ok(()) } else { Err(ParseError::InsufficientBytes) diff --git a/src/read_buffer.rs b/src/read_buffer.rs index 99bfdff..13118a3 100644 --- a/src/read_buffer.rs +++ b/src/read_buffer.rs @@ -1,4 +1,3 @@ -/// A reference to consume bytes from the internal buffer. #[must_use] pub struct ReadBuffer<'a> { data: &'a [u8], @@ -31,25 +30,21 @@ impl<'a> ReadBuffer<'a> { self.len() == 0 } - /// Consumes and returns a slice of at most `count` bytes. #[inline] pub fn peek(&mut self, count: usize) -> &'a [u8] { let count = self.len().min(count); let start = self.consumed; - // We mark the buffer used to prevent dropping unconsumed bytes. self.used = true; &self.data[start..start + count] } - /// Consumes and returns a slice of at most `count` bytes. #[inline] pub fn peek_all(&mut self) -> &'a [u8] { self.peek(self.len()) } - /// Consumes and returns a slice of at most `count` bytes. #[inline] pub fn pop(&mut self, count: usize) -> &'a [u8] { let count = self.len().min(count); @@ -60,19 +55,16 @@ impl<'a> ReadBuffer<'a> { &self.data[start..start + count] } - /// Consumes and returns the internal buffer. #[inline] pub fn pop_all(&mut self) -> &'a [u8] { self.pop(self.len()) } - /// Drops the reference and restores internal buffer. #[inline] pub fn revert(self) { core::mem::forget(self); } - /// Tries to fills the buffer by consuming and copying bytes into it. #[inline] pub fn pop_into(&mut self, buf: &mut [u8]) -> usize { let to_copy = self.pop(buf.len()); @@ -89,7 +81,6 @@ impl Drop for ReadBuffer<'_> { *self.decrypted_consumed += if self.used { self.consumed } else { - // Consume all if dropped unused self.data.len() }; } diff --git a/src/record.rs b/src/record.rs index e4f23dc..cc5a38b 100644 --- a/src/record.rs +++ b/src/record.rs @@ -18,7 +18,6 @@ pub type Encrypted = bool; #[allow(clippy::large_enum_variant)] pub enum ClientRecord<'config, 'a, CipherSuite> where - // N: ArrayLength, CipherSuite: TlsCipherSuite, { Handshake(ClientHandshake<'config, 'a, CipherSuite>, Encrypted), @@ -80,7 +79,6 @@ impl ClientRecordHeader { impl<'config, CipherSuite> ClientRecord<'config, '_, CipherSuite> where - //N: ArrayLength, CipherSuite: TlsCipherSuite, { pub fn header(&self) -> ClientRecordHeader { @@ -158,12 +156,10 @@ impl RecordHeader { pub const LEN: usize = 5; pub fn content_type(&self) -> ContentType { - // Content type already validated in read unwrap!(ContentType::of(self.header[0])) } pub fn content_length(&self) -> usize { - // Content length already validated in read u16::from_be_bytes([self.header[3], self.header[4]]) as usize } @@ -220,5 +216,4 @@ impl<'a, CipherSuite: TlsCipherSuite> ServerRecord<'a, CipherSuite> { } } - //pub fn parse(buf: &[u8]) -> Result {} } diff --git a/src/record_reader.rs b/src/record_reader.rs index e19e673..52d9210 100644 --- a/src/record_reader.rs +++ b/src/record_reader.rs @@ -8,24 +8,25 @@ use crate::{ record::{RecordHeader, ServerRecord}, }; +/// Stateful reader that reassembles TLS records from a byte stream into the shared receive buffer. +/// +/// `decoded` tracks how many bytes at the start of `buf` have already been handed to the caller; +/// `pending` tracks bytes that have been read from the transport but not yet consumed as a record. pub struct RecordReader<'a> { pub(crate) buf: &'a mut [u8], - /// The number of decoded bytes in the buffer decoded: usize, - /// The number of read but not yet decoded bytes in the buffer pending: usize, } pub struct RecordReaderBorrowMut<'a> { pub(crate) buf: &'a mut [u8], - /// The number of decoded bytes in the buffer decoded: &'a mut usize, - /// The number of read but not yet decoded bytes in the buffer pending: &'a mut usize, } impl<'a> RecordReader<'a> { pub fn new(buf: &'a mut [u8]) -> Self { + // TLS 1.3 max plaintext record is 16 384 bytes + 256 bytes overhead = 16 640 bytes if buf.len() < 16640 { warn!("Read buffer is smaller than 16640 bytes, which may cause problems!"); } @@ -248,6 +249,7 @@ fn ensure_contiguous( pending: &mut usize, len: usize, ) -> Result<(), ProtocolError> { + // If the next record would overflow the end of the buffer, rotate unconsumed bytes to the front if *decoded + len > buf.len() { if len > buf.len() { error!( @@ -310,24 +312,20 @@ mod tests { fn can_read_blocking_case(chunk_size: usize) { let mut transport = ChunkRead( &[ - // Header ContentType::ApplicationData as u8, 0x03, 0x03, 0x00, 0x04, - // Data 0xde, 0xad, 0xbe, 0xef, - // Header ContentType::ApplicationData as u8, 0x03, 0x03, 0x00, 0x02, - // Data 0xaa, 0xbb, ], @@ -370,30 +368,26 @@ mod tests { #[test] fn can_read_blocking_must_rotate_buffer() { let mut transport = [ - // Header ContentType::ApplicationData as u8, 0x03, 0x03, 0x00, 0x04, - // Data 0xde, 0xad, 0xbe, 0xef, - // Header ContentType::ApplicationData as u8, 0x03, 0x03, 0x00, 0x02, - // Data 0xaa, 0xbb, ] .as_slice(); - let mut buf = [0; 4]; // cannot contain both data portions + let mut buf = [0; 4]; let mut reader = RecordReader::new(&mut buf); let mut key_schedule = KeySchedule::::new(); @@ -429,13 +423,11 @@ mod tests { #[test] fn can_read_empty_record() { let mut transport = [ - // Header ContentType::ApplicationData as u8, 0x03, 0x03, 0x00, 0x00, - // Header ContentType::ApplicationData as u8, 0x03, 0x03, diff --git a/src/send_policy.rs b/src/send_policy.rs index 68e4ba8..5f6048b 100644 --- a/src/send_policy.rs +++ b/src/send_policy.rs @@ -1,36 +1,24 @@ -//! Flush policy for TLS sockets. -//! -//! Two strategies are provided: -//! - `Relaxed`: close the TLS encryption buffer and hand the data to the transport -//! delegate without forcing a transport-level flush. -//! - `Strict`: in addition to handing the data to the transport delegate, also -//! request a flush of the transport. For TCP transports this typically means -//! waiting for an ACK (e.g. on embassy TCP sockets) before considering the -//! data fully flushed. -/// Policy controlling how TLS layer flushes encrypted data to the transport. +/// Controls whether `flush()` calls also flush the underlying transport. +/// +/// `Strict` (the default) ensures bytes reach the network immediately after every record. +/// `Relaxed` leaves transport flushing to the caller, which can reduce syscall overhead. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FlushPolicy { - /// Close the TLS encryption buffer and pass bytes to the transport delegate. - /// Do not force a transport-level flush or wait for an ACK. + /// Only encrypt and hand bytes to the transport; do not call `transport.flush()`. Relaxed, - /// In addition to passing bytes to the transport delegate, request a - /// transport-level flush and wait for confirmation (ACK) before returning. + /// Call `transport.flush()` after writing each TLS record. Strict, } impl FlushPolicy { - /// Returns true when the transport delegate should be explicitly flushed. - /// - /// Relaxed -> false, Strict -> true. pub fn flush_transport(&self) -> bool { matches!(self, Self::Strict) } } impl Default for FlushPolicy { - /// Default to `Strict` for compatibility with mote-tls 0.17.0. fn default() -> Self { FlushPolicy::Strict }