Add comments and LICENSE
This commit is contained in:
@@ -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<CP>(
|
||||
&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<usize, ProtocolError> {
|
||||
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<usize, ProtocolError> {
|
||||
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<ReadBuffer<'_>, 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<Socket, (Socket, ProtocolError)> {
|
||||
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<ReadBuffer<'_>, ProtocolError> {
|
||||
if self.opened.load(Ordering::Acquire) {
|
||||
while self.decrypted.is_empty() {
|
||||
|
||||
@@ -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<CP>(&mut self, mut context: ConnectContext<CP>) -> Result<(), ProtocolError>
|
||||
where
|
||||
CP: CryptoBackend<CipherSuite = CipherSuite>,
|
||||
@@ -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<usize, ProtocolError> {
|
||||
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<usize, ProtocolError> {
|
||||
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<ReadBuffer<'_>, 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<Socket, (Socket, ProtocolError)> {
|
||||
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<ReadBuffer<'_>, ProtocolError> {
|
||||
if self.opened.load(Ordering::Acquire) {
|
||||
while self.decrypted.is_empty() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Self, ProtocolError> {
|
||||
// info!("change cipher spec of len={}", rx_buf.len());
|
||||
// TODO: Decode data
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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(_) => {
|
||||
|
||||
@@ -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<CipherSuite> = Sum<
|
||||
@@ -29,7 +29,7 @@ type LabelBuffer<CipherSuite> = Sum<
|
||||
Sum<LongestLabel, LabelOverhead>,
|
||||
>;
|
||||
|
||||
/// 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<KeySize = Self::KeyLen> + AeadInPlace<NonceSize = Self::IvLen>;
|
||||
@@ -62,35 +62,23 @@ impl TlsCipherSuite for Aes256GcmSha384 {
|
||||
type LabelBufferSize = LabelBuffer<Self>;
|
||||
}
|
||||
|
||||
/// 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<CipherSuite>
|
||||
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<CipherSuite> Verifier<CipherSuite> 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<SignatureScheme, 25>,
|
||||
pub(crate) named_groups: Vec<NamedGroup, 13>,
|
||||
@@ -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<Self::Signature>, 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<u8, N>`) — the certificate is only needed long enough to encode
|
||||
/// into the TLS message.
|
||||
fn client_cert(&mut self) -> Option<Certificate<impl AsRef<[u8]>>> {
|
||||
None::<Certificate<&[u8]>>
|
||||
}
|
||||
@@ -203,6 +184,7 @@ impl<S> signature::Signer<S> 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
|
||||
}
|
||||
|
||||
@@ -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<CipherSuite>(
|
||||
key_schedule: &mut ReadKeySchedule<CipherSuite>,
|
||||
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 = <CipherSuite::Cipher as KeyInit>::new(client_key);
|
||||
let len = buf.len() + <CipherSuite::Cipher as AeadCore>::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<CipherSuite>
|
||||
where
|
||||
CipherSuite: TlsCipherSuite,
|
||||
{
|
||||
// Saved pre-master transcript hash used for Finished after a certificate exchange
|
||||
traffic_hash: Option<CipherSuite::Hash>,
|
||||
secret: Option<EphemeralSecret>,
|
||||
certificate_request: Option<CertificateRequest>,
|
||||
@@ -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<u8, 146> = 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<CipherSuite>(
|
||||
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
|
||||
|
||||
@@ -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<Self, ParseError> {
|
||||
// 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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,12 @@ impl<const N: usize> 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)?;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Self, crate::ProtocolError> {
|
||||
// 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;
|
||||
|
||||
@@ -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>),
|
||||
|
||||
@@ -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<N: ArrayLength<u8>> {
|
||||
pub verify: GenericArray<u8, N>,
|
||||
@@ -25,7 +23,6 @@ impl<N: ArrayLength<u8>> Debug for PskBinder<N> {
|
||||
impl<N: ArrayLength<u8>> PskBinder<N> {
|
||||
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)?;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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<CipherSuite>,
|
||||
) -> 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::<CipherSuite>::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)?;
|
||||
|
||||
@@ -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<N: ArrayLength<u8>> {
|
||||
pub verify: GenericArray<u8, N>,
|
||||
pub hash: Option<GenericArray<u8, N>>,
|
||||
@@ -28,23 +30,12 @@ impl<N: ArrayLength<u8>> Debug for Finished<N> {
|
||||
|
||||
impl<N: ArrayLength<u8>> Finished<N> {
|
||||
pub fn parse(buf: &mut ParseBuffer, _len: u32) -> Result<Self, ProtocolError> {
|
||||
// info!("finished len: {}", len);
|
||||
let mut verify = GenericArray::default();
|
||||
buf.fill(&mut verify)?;
|
||||
//let hash = GenericArray::from_slice()
|
||||
//let hash: Result<Vec<u8, _>, ()> = 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(())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -17,9 +17,7 @@ pub struct ServerHello<'a> {
|
||||
|
||||
impl<'a> ServerHello<'a> {
|
||||
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<ServerHello<'a>, 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<SharedSecret> {
|
||||
let server_key_share = self.key_share()?;
|
||||
let server_public_key = PublicKey::from_sec1_bytes(server_key_share.opaque).ok()?;
|
||||
|
||||
@@ -13,6 +13,7 @@ pub type LabelBufferSize<CipherSuite> = <CipherSuite as TlsCipherSuite>::LabelBu
|
||||
|
||||
pub type IvArray<CipherSuite> = GenericArray<u8, <CipherSuite as TlsCipherSuite>::IvLen>;
|
||||
pub type KeyArray<CipherSuite> = GenericArray<u8, <CipherSuite as TlsCipherSuite>::KeyLen>;
|
||||
/// Hash-sized byte array, used as the HKDF secret at each key schedule stage.
|
||||
pub type HashArray<CipherSuite> = GenericArray<u8, HashOutputSize<CipherSuite>>;
|
||||
|
||||
type Hkdf<CipherSuite> = hkdf::Hkdf<
|
||||
@@ -43,17 +44,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// HKDF-Expand-Label as defined in RFC 8446 §7.1
|
||||
fn make_expanded_hkdf_label<N: ArrayLength<u8>>(
|
||||
&self,
|
||||
label: &[u8],
|
||||
context_type: ContextType<CipherSuite>,
|
||||
) -> Result<GenericArray<u8, N>, ProtocolError> {
|
||||
//info!("make label {:?} {}", label, len);
|
||||
let mut hkdf_label = heapless_typenum::Vec::<u8, LabelBufferSize<CipherSuite>>::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::<CipherSuite>::extract(Some(self.secret.as_ref()), ikm);
|
||||
self.hkdf.replace(hkdf);
|
||||
@@ -123,6 +125,7 @@ where
|
||||
.make_expanded_hkdf_label::<HashOutputSize<CipherSuite>>(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::<CipherSuite>::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<CipherSuite>) -> IvArray<CipherSuite> {
|
||||
//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::<CipherSuite::IvLen>(&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<N: ArrayLength<u8>>(input: &[u8]) -> GenericArray<u8, N> {
|
||||
// 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<HashOutputSize<CipherSuite>>,
|
||||
) -> Result<bool, ProtocolError> {
|
||||
//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::<CipherSuite::Hash>::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!()
|
||||
}
|
||||
}
|
||||
|
||||
44
src/lib.rs
44
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::<Aes128GcmSha256>(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>(_: T) {}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ impl<'b> ParseBuffer<'b> {
|
||||
}
|
||||
|
||||
pub fn read_u16(&mut self) -> Result<u16, ParseError> {
|
||||
//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)
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ pub type Encrypted = bool;
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ClientRecord<'config, 'a, CipherSuite>
|
||||
where
|
||||
// N: ArrayLength<u8>,
|
||||
CipherSuite: TlsCipherSuite,
|
||||
{
|
||||
Handshake(ClientHandshake<'config, 'a, CipherSuite>, Encrypted),
|
||||
@@ -80,7 +79,6 @@ impl ClientRecordHeader {
|
||||
|
||||
impl<'config, CipherSuite> ClientRecord<'config, '_, CipherSuite>
|
||||
where
|
||||
//N: ArrayLength<u8>,
|
||||
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<D: Digest>(buf: &[u8]) -> Result<Self, ProtocolError> {}
|
||||
}
|
||||
|
||||
@@ -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::<Aes128GcmSha256>::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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user