Initial commit
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
36
src/handshake/binder.rs
Normal file
36
src/handshake/binder.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::ProtocolError;
|
||||
use crate::buffer::CryptoBuffer;
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
|
||||
pub struct PskBinder<N: ArrayLength<u8>> {
|
||||
pub verify: GenericArray<u8, N>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<N: ArrayLength<u8>> defmt::Format for PskBinder<N> {
|
||||
fn format(&self, f: defmt::Formatter<'_>) {
|
||||
defmt::write!(f, "verify length:{}", &self.verify.len());
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: ArrayLength<u8>> Debug for PskBinder<N> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("PskBinder").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: ArrayLength<u8>> PskBinder<N> {
|
||||
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
|
||||
let len = self.verify.len() as u8;
|
||||
buf.push(len).map_err(|_| ProtocolError::EncodeError)?;
|
||||
buf.extend_from_slice(&self.verify[..self.verify.len()])
|
||||
.map_err(|_| ProtocolError::EncodeError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn len() -> usize {
|
||||
N::to_usize()
|
||||
}
|
||||
}
|
||||
176
src/handshake/certificate.rs
Normal file
176
src/handshake/certificate.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use crate::ProtocolError;
|
||||
use crate::buffer::CryptoBuffer;
|
||||
use crate::extensions::messages::CertificateExtension;
|
||||
use crate::parse_buffer::ParseBuffer;
|
||||
use heapless::Vec;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CertificateRef<'a> {
|
||||
raw_entries: &'a [u8],
|
||||
request_context: &'a [u8],
|
||||
|
||||
pub entries: Vec<CertificateEntryRef<'a>, 16>,
|
||||
}
|
||||
|
||||
impl<'a> CertificateRef<'a> {
|
||||
#[must_use]
|
||||
pub fn with_context(request_context: &'a [u8]) -> Self {
|
||||
Self {
|
||||
raw_entries: &[],
|
||||
request_context,
|
||||
entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, entry: CertificateEntryRef<'a>) -> Result<(), ProtocolError> {
|
||||
self.entries.push(entry).map_err(|_| {
|
||||
error!("CertificateRef: InsufficientSpace");
|
||||
ProtocolError::InsufficientSpace
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ProtocolError> {
|
||||
let request_context_len = buf
|
||||
.read_u8()
|
||||
.map_err(|_| ProtocolError::InvalidCertificate)?;
|
||||
let request_context = buf
|
||||
.slice(request_context_len as usize)
|
||||
.map_err(|_| ProtocolError::InvalidCertificate)?;
|
||||
let entries_len = buf
|
||||
.read_u24()
|
||||
.map_err(|_| ProtocolError::InvalidCertificate)?;
|
||||
let mut raw_entries = buf
|
||||
.slice(entries_len as usize)
|
||||
.map_err(|_| ProtocolError::InvalidCertificate)?;
|
||||
|
||||
let entries = CertificateEntryRef::parse_vector(&mut raw_entries)?;
|
||||
|
||||
Ok(Self {
|
||||
raw_entries: raw_entries.as_slice(),
|
||||
request_context: request_context.as_slice(),
|
||||
entries,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
|
||||
buf.with_u8_length(|buf| buf.extend_from_slice(self.request_context))?;
|
||||
buf.with_u24_length(|buf| {
|
||||
for entry in &self.entries {
|
||||
entry.encode(buf)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum CertificateEntryRef<'a> {
|
||||
X509(&'a [u8]),
|
||||
RawPublicKey(&'a [u8]),
|
||||
}
|
||||
|
||||
impl<'a> CertificateEntryRef<'a> {
|
||||
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ProtocolError> {
|
||||
let entry_len = buf
|
||||
.read_u24()
|
||||
.map_err(|_| ProtocolError::InvalidCertificateEntry)?;
|
||||
let cert = buf
|
||||
.slice(entry_len as usize)
|
||||
.map_err(|_| ProtocolError::InvalidCertificateEntry)?;
|
||||
|
||||
let entry = CertificateEntryRef::X509(cert.as_slice());
|
||||
|
||||
CertificateExtension::parse_vector::<2>(buf)?;
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
pub fn parse_vector<const N: usize>(
|
||||
buf: &mut ParseBuffer<'a>,
|
||||
) -> Result<Vec<Self, N>, ProtocolError> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
while !buf.is_empty() {
|
||||
result
|
||||
.push(Self::parse(buf)?)
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
|
||||
match *self {
|
||||
CertificateEntryRef::RawPublicKey(_key) => {
|
||||
todo!("ASN1_subjectPublicKeyInfo encoding?");
|
||||
}
|
||||
CertificateEntryRef::X509(cert) => {
|
||||
buf.with_u24_length(|buf| buf.extend_from_slice(cert))?;
|
||||
}
|
||||
}
|
||||
|
||||
buf.push_u16(0)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, D: AsRef<[u8]>> From<&'a crate::config::Certificate<D>> for CertificateEntryRef<'a> {
|
||||
fn from(cert: &'a crate::config::Certificate<D>) -> Self {
|
||||
match cert {
|
||||
crate::config::Certificate::X509(data) => CertificateEntryRef::X509(data.as_ref()),
|
||||
crate::config::Certificate::RawPublicKey(data) => {
|
||||
CertificateEntryRef::RawPublicKey(data.as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Certificate<const N: usize> {
|
||||
request_context: Vec<u8, 256>,
|
||||
entries_data: Vec<u8, N>,
|
||||
}
|
||||
|
||||
impl<const N: usize> Certificate<N> {
|
||||
pub fn request_context(&self) -> &[u8] {
|
||||
&self.request_context[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> TryFrom<CertificateRef<'a>> for Certificate<N> {
|
||||
type Error = ProtocolError;
|
||||
fn try_from(cert: CertificateRef<'a>) -> Result<Self, Self::Error> {
|
||||
let mut request_context = Vec::new();
|
||||
request_context
|
||||
.extend_from_slice(cert.request_context)
|
||||
.map_err(|_| ProtocolError::OutOfMemory)?;
|
||||
let mut entries_data = Vec::new();
|
||||
entries_data
|
||||
.extend_from_slice(cert.raw_entries)
|
||||
.map_err(|_| ProtocolError::OutOfMemory)?;
|
||||
|
||||
Ok(Self {
|
||||
request_context,
|
||||
entries_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> TryFrom<&'a Certificate<N>> for CertificateRef<'a> {
|
||||
type Error = ProtocolError;
|
||||
fn try_from(cert: &'a Certificate<N>) -> Result<Self, Self::Error> {
|
||||
let request_context = cert.request_context();
|
||||
let entries =
|
||||
CertificateEntryRef::parse_vector(&mut ParseBuffer::from(&cert.entries_data[..]))?;
|
||||
Ok(Self {
|
||||
raw_entries: &cert.entries_data[..],
|
||||
request_context,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
49
src/handshake/certificate_request.rs
Normal file
49
src/handshake/certificate_request.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use crate::extensions::messages::CertificateRequestExtension;
|
||||
use crate::parse_buffer::ParseBuffer;
|
||||
use crate::{ProtocolError, unused};
|
||||
use heapless::Vec;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CertificateRequestRef<'a> {
|
||||
pub(crate) request_context: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> CertificateRequestRef<'a> {
|
||||
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<CertificateRequestRef<'a>, ProtocolError> {
|
||||
let request_context_len = buf
|
||||
.read_u8()
|
||||
.map_err(|_| ProtocolError::InvalidCertificateRequest)?;
|
||||
let request_context = buf
|
||||
.slice(request_context_len as usize)
|
||||
.map_err(|_| ProtocolError::InvalidCertificateRequest)?;
|
||||
|
||||
let extensions = CertificateRequestExtension::parse_vector::<6>(buf)?;
|
||||
|
||||
unused(extensions);
|
||||
Ok(Self {
|
||||
request_context: request_context.as_slice(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CertificateRequest {
|
||||
pub(crate) request_context: Vec<u8, 256>,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<CertificateRequestRef<'a>> for CertificateRequest {
|
||||
type Error = ProtocolError;
|
||||
fn try_from(cert: CertificateRequestRef<'a>) -> Result<Self, Self::Error> {
|
||||
let mut request_context = Vec::new();
|
||||
request_context
|
||||
.extend_from_slice(cert.request_context)
|
||||
.map_err(|_| {
|
||||
error!("CertificateRequest: InsufficientSpace");
|
||||
ProtocolError::InsufficientSpace
|
||||
})?;
|
||||
|
||||
Ok(Self { request_context })
|
||||
}
|
||||
}
|
||||
51
src/handshake/certificate_verify.rs
Normal file
51
src/handshake/certificate_verify.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use crate::ProtocolError;
|
||||
use crate::extensions::extension_data::signature_algorithms::SignatureScheme;
|
||||
use crate::parse_buffer::ParseBuffer;
|
||||
|
||||
use super::CryptoBuffer;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct HandshakeVerifyRef<'a> {
|
||||
pub signature_scheme: SignatureScheme,
|
||||
pub signature: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> HandshakeVerifyRef<'a> {
|
||||
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<HandshakeVerifyRef<'a>, ProtocolError> {
|
||||
let signature_scheme =
|
||||
SignatureScheme::parse(buf).map_err(|_| ProtocolError::InvalidSignatureScheme)?;
|
||||
|
||||
let len = buf
|
||||
.read_u16()
|
||||
.map_err(|_| ProtocolError::InvalidSignature)?;
|
||||
let signature = buf
|
||||
.slice(len as usize)
|
||||
.map_err(|_| ProtocolError::InvalidSignature)?;
|
||||
|
||||
Ok(Self {
|
||||
signature_scheme,
|
||||
signature: signature.as_slice(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rsa")]
|
||||
const SIGNATURE_SIZE: usize = 512;
|
||||
#[cfg(not(feature = "rsa"))]
|
||||
const SIGNATURE_SIZE: usize = 104;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct HandshakeVerify {
|
||||
pub(crate) signature_scheme: SignatureScheme,
|
||||
pub(crate) signature: heapless::Vec<u8, SIGNATURE_SIZE>,
|
||||
}
|
||||
|
||||
impl HandshakeVerify {
|
||||
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
|
||||
buf.push_u16(self.signature_scheme.as_u16())?;
|
||||
buf.with_u16_length(|buf| buf.extend_from_slice(self.signature.as_slice()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
165
src/handshake/client_hello.rs
Normal file
165
src/handshake/client_hello.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use heapless::Vec;
|
||||
use p256::EncodedPoint;
|
||||
use p256::ecdh::EphemeralSecret;
|
||||
use p256::elliptic_curve::rand_core::RngCore;
|
||||
use typenum::Unsigned;
|
||||
|
||||
use crate::ProtocolError;
|
||||
use crate::config::{ConnectConfig, TlsCipherSuite};
|
||||
use crate::extensions::extension_data::alpn::AlpnProtocolNameList;
|
||||
use crate::extensions::extension_data::key_share::{KeyShareClientHello, KeyShareEntry};
|
||||
use crate::extensions::extension_data::pre_shared_key::PreSharedKeyClientHello;
|
||||
use crate::extensions::extension_data::psk_key_exchange_modes::{
|
||||
PskKeyExchangeMode, PskKeyExchangeModes,
|
||||
};
|
||||
use crate::extensions::extension_data::server_name::ServerNameList;
|
||||
use crate::extensions::extension_data::signature_algorithms::SignatureAlgorithms;
|
||||
use crate::extensions::extension_data::supported_groups::{NamedGroup, SupportedGroups};
|
||||
use crate::extensions::extension_data::supported_versions::{SupportedVersionsClientHello, TLS13};
|
||||
use crate::extensions::messages::ClientHelloExtension;
|
||||
use crate::handshake::{LEGACY_VERSION, Random};
|
||||
use crate::key_schedule::{HashOutputSize, WriteKeySchedule};
|
||||
use crate::{CryptoBackend, buffer::CryptoBuffer};
|
||||
|
||||
pub struct ClientHello<'config, CipherSuite>
|
||||
where
|
||||
CipherSuite: TlsCipherSuite,
|
||||
{
|
||||
pub(crate) config: &'config ConnectConfig<'config>,
|
||||
random: Random,
|
||||
cipher_suite: PhantomData<CipherSuite>,
|
||||
pub(crate) secret: EphemeralSecret,
|
||||
}
|
||||
|
||||
impl<'config, CipherSuite> ClientHello<'config, CipherSuite>
|
||||
where
|
||||
CipherSuite: TlsCipherSuite,
|
||||
{
|
||||
pub fn new<CP>(config: &'config ConnectConfig<'config>, mut provider: CP) -> Self
|
||||
where
|
||||
CP: CryptoBackend,
|
||||
{
|
||||
let mut random = [0; 32];
|
||||
provider.rng().fill_bytes(&mut random);
|
||||
|
||||
Self {
|
||||
config,
|
||||
random,
|
||||
cipher_suite: PhantomData,
|
||||
secret: EphemeralSecret::random(&mut provider.rng()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
|
||||
let public_key = EncodedPoint::from(&self.secret.public_key());
|
||||
let public_key = public_key.as_ref();
|
||||
|
||||
buf.push_u16(LEGACY_VERSION)
|
||||
.map_err(|_| ProtocolError::EncodeError)?;
|
||||
buf.extend_from_slice(&self.random)
|
||||
.map_err(|_| ProtocolError::EncodeError)?;
|
||||
|
||||
// Empty legacy session ID — TLS 1.3 doesn't use it, but the field must be present
|
||||
buf.push(0).map_err(|_| ProtocolError::EncodeError)?;
|
||||
|
||||
// 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)?;
|
||||
|
||||
// Legacy compression methods: one entry, 0x00 = no compression
|
||||
buf.push(1).map_err(|_| ProtocolError::EncodeError)?;
|
||||
buf.push(0).map_err(|_| ProtocolError::EncodeError)?;
|
||||
|
||||
buf.with_u16_length(|buf| {
|
||||
ClientHelloExtension::SupportedVersions(SupportedVersionsClientHello {
|
||||
versions: Vec::from_slice(&[TLS13]).unwrap(),
|
||||
})
|
||||
.encode(buf)?;
|
||||
|
||||
ClientHelloExtension::SignatureAlgorithms(SignatureAlgorithms {
|
||||
supported_signature_algorithms: self.config.signature_schemes.clone(),
|
||||
})
|
||||
.encode(buf)?;
|
||||
|
||||
if let Some(max_fragment_length) = self.config.max_fragment_length {
|
||||
ClientHelloExtension::MaxFragmentLength(max_fragment_length).encode(buf)?;
|
||||
}
|
||||
|
||||
ClientHelloExtension::SupportedGroups(SupportedGroups {
|
||||
supported_groups: self.config.named_groups.clone(),
|
||||
})
|
||||
.encode(buf)?;
|
||||
|
||||
ClientHelloExtension::PskKeyExchangeModes(PskKeyExchangeModes {
|
||||
modes: Vec::from_slice(&[PskKeyExchangeMode::PskDheKe]).unwrap(),
|
||||
})
|
||||
.encode(buf)?;
|
||||
|
||||
ClientHelloExtension::KeyShare(KeyShareClientHello {
|
||||
client_shares: Vec::from_slice(&[KeyShareEntry {
|
||||
group: NamedGroup::Secp256r1,
|
||||
opaque: public_key,
|
||||
}])
|
||||
.unwrap(),
|
||||
})
|
||||
.encode(buf)?;
|
||||
|
||||
if let Some(server_name) = self.config.server_name {
|
||||
ClientHelloExtension::ServerName(ServerNameList::single(server_name))
|
||||
.encode(buf)?;
|
||||
}
|
||||
|
||||
if let Some(alpn_protocols) = self.config.alpn_protocols {
|
||||
ClientHelloExtension::ApplicationLayerProtocolNegotiation(AlpnProtocolNameList {
|
||||
protocols: alpn_protocols,
|
||||
})
|
||||
.encode(buf)?;
|
||||
}
|
||||
|
||||
if let Some((_, identities)) = &self.config.psk {
|
||||
ClientHelloExtension::PreSharedKey(PreSharedKeyClientHello {
|
||||
identities: identities.clone(),
|
||||
hash_size: <CipherSuite::Hash as OutputSizeUser>::output_size(),
|
||||
})
|
||||
.encode(buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finalize(
|
||||
&self,
|
||||
enc_buf: &mut [u8],
|
||||
transcript: &mut CipherSuite::Hash,
|
||||
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
|
||||
) -> Result<(), ProtocolError> {
|
||||
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;
|
||||
|
||||
transcript.update(&enc_buf[0..binders_pos - 2]);
|
||||
|
||||
let mut buf = CryptoBuffer::wrap(&mut enc_buf[binders_pos..]);
|
||||
for _id in identities {
|
||||
let binder = write_key_schedule.create_psk_binder(transcript)?;
|
||||
binder.encode(&mut buf)?;
|
||||
}
|
||||
|
||||
transcript.update(&enc_buf[binders_pos - 2..]);
|
||||
} else {
|
||||
transcript.update(enc_buf);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
19
src/handshake/encrypted_extensions.rs
Normal file
19
src/handshake/encrypted_extensions.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::extensions::messages::EncryptedExtensionsExtension;
|
||||
|
||||
use crate::ProtocolError;
|
||||
use crate::parse_buffer::ParseBuffer;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct EncryptedExtensions<'a> {
|
||||
_todo: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> EncryptedExtensions<'a> {
|
||||
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<EncryptedExtensions<'a>, ProtocolError> {
|
||||
EncryptedExtensionsExtension::parse_vector::<16>(buf)?;
|
||||
Ok(EncryptedExtensions { _todo: PhantomData })
|
||||
}
|
||||
}
|
||||
43
src/handshake/finished.rs
Normal file
43
src/handshake/finished.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::ProtocolError;
|
||||
use crate::buffer::CryptoBuffer;
|
||||
use crate::parse_buffer::ParseBuffer;
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
|
||||
/// 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>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<N: ArrayLength<u8>> defmt::Format for Finished<N> {
|
||||
fn format(&self, f: defmt::Formatter<'_>) {
|
||||
defmt::write!(f, "verify length:{}", &self.verify.len());
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: ArrayLength<u8>> Debug for Finished<N> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Finished")
|
||||
.field("verify", &self.hash)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: ArrayLength<u8>> Finished<N> {
|
||||
pub fn parse(buf: &mut ParseBuffer, _len: u32) -> Result<Self, ProtocolError> {
|
||||
let mut verify = GenericArray::default();
|
||||
buf.fill(&mut verify)?;
|
||||
Ok(Self { verify, hash: None })
|
||||
}
|
||||
|
||||
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
|
||||
buf.extend_from_slice(&self.verify[..self.verify.len()])
|
||||
.map_err(|_| ProtocolError::EncodeError)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
242
src/handshake/mod.rs
Normal file
242
src/handshake/mod.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use crate::ProtocolError;
|
||||
use crate::config::TlsCipherSuite;
|
||||
use crate::handshake::certificate::CertificateRef;
|
||||
use crate::handshake::certificate_request::CertificateRequestRef;
|
||||
use crate::handshake::certificate_verify::{HandshakeVerify, HandshakeVerifyRef};
|
||||
use crate::handshake::client_hello::ClientHello;
|
||||
use crate::handshake::encrypted_extensions::EncryptedExtensions;
|
||||
use crate::handshake::finished::Finished;
|
||||
use crate::handshake::new_session_ticket::NewSessionTicket;
|
||||
use crate::handshake::server_hello::ServerHello;
|
||||
use crate::key_schedule::HashOutputSize;
|
||||
use crate::parse_buffer::{ParseBuffer, ParseError};
|
||||
use crate::{buffer::CryptoBuffer, key_schedule::WriteKeySchedule};
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use sha2::Digest;
|
||||
|
||||
pub mod binder;
|
||||
pub mod certificate;
|
||||
pub mod certificate_request;
|
||||
pub mod certificate_verify;
|
||||
pub mod client_hello;
|
||||
pub mod encrypted_extensions;
|
||||
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];
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum HandshakeType {
|
||||
ClientHello = 1,
|
||||
ServerHello = 2,
|
||||
NewSessionTicket = 4,
|
||||
EndOfEarlyData = 5,
|
||||
EncryptedExtensions = 8,
|
||||
Certificate = 11,
|
||||
CertificateRequest = 13,
|
||||
HandshakeVerify = 15,
|
||||
Finished = 20,
|
||||
KeyUpdate = 24,
|
||||
MessageHash = 254,
|
||||
}
|
||||
|
||||
impl HandshakeType {
|
||||
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
|
||||
match buf.read_u8()? {
|
||||
1 => Ok(HandshakeType::ClientHello),
|
||||
2 => Ok(HandshakeType::ServerHello),
|
||||
4 => Ok(HandshakeType::NewSessionTicket),
|
||||
5 => Ok(HandshakeType::EndOfEarlyData),
|
||||
8 => Ok(HandshakeType::EncryptedExtensions),
|
||||
11 => Ok(HandshakeType::Certificate),
|
||||
13 => Ok(HandshakeType::CertificateRequest),
|
||||
15 => Ok(HandshakeType::HandshakeVerify),
|
||||
20 => Ok(HandshakeType::Finished),
|
||||
24 => Ok(HandshakeType::KeyUpdate),
|
||||
254 => Ok(HandshakeType::MessageHash),
|
||||
_ => Err(ParseError::InvalidData),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ClientHandshake<'config, 'a, CipherSuite>
|
||||
where
|
||||
CipherSuite: TlsCipherSuite,
|
||||
{
|
||||
ClientCert(CertificateRef<'a>),
|
||||
ClientCertVerify(HandshakeVerify),
|
||||
ClientHello(ClientHello<'config, CipherSuite>),
|
||||
Finished(Finished<HashOutputSize<CipherSuite>>),
|
||||
}
|
||||
|
||||
impl<CipherSuite> ClientHandshake<'_, '_, CipherSuite>
|
||||
where
|
||||
CipherSuite: TlsCipherSuite,
|
||||
{
|
||||
fn handshake_type(&self) -> HandshakeType {
|
||||
match self {
|
||||
ClientHandshake::ClientHello(_) => HandshakeType::ClientHello,
|
||||
ClientHandshake::Finished(_) => HandshakeType::Finished,
|
||||
ClientHandshake::ClientCert(_) => HandshakeType::Certificate,
|
||||
ClientHandshake::ClientCertVerify(_) => HandshakeType::HandshakeVerify,
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_inner(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
|
||||
match self {
|
||||
ClientHandshake::ClientHello(inner) => inner.encode(buf),
|
||||
ClientHandshake::Finished(inner) => inner.encode(buf),
|
||||
ClientHandshake::ClientCert(inner) => inner.encode(buf),
|
||||
ClientHandshake::ClientCertVerify(inner) => inner.encode(buf),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
|
||||
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))
|
||||
}
|
||||
|
||||
pub fn finalize(
|
||||
&self,
|
||||
buf: &mut CryptoBuffer,
|
||||
transcript: &mut CipherSuite::Hash,
|
||||
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
|
||||
) -> Result<(), ProtocolError> {
|
||||
let enc_buf = buf.as_mut_slice();
|
||||
if let ClientHandshake::ClientHello(hello) = self {
|
||||
hello.finalize(enc_buf, transcript, write_key_schedule)
|
||||
} else {
|
||||
transcript.update(enc_buf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize_encrypted(buf: &mut CryptoBuffer, transcript: &mut CipherSuite::Hash) {
|
||||
let enc_buf = buf.as_slice();
|
||||
let end = enc_buf.len();
|
||||
transcript.update(&enc_buf[0..end]);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ServerHandshake<'a, CipherSuite: TlsCipherSuite> {
|
||||
ServerHello(ServerHello<'a>),
|
||||
EncryptedExtensions(EncryptedExtensions<'a>),
|
||||
NewSessionTicket(NewSessionTicket<'a>),
|
||||
Certificate(CertificateRef<'a>),
|
||||
CertificateRequest(CertificateRequestRef<'a>),
|
||||
HandshakeVerify(HandshakeVerifyRef<'a>),
|
||||
Finished(Finished<HashOutputSize<CipherSuite>>),
|
||||
}
|
||||
|
||||
impl<CipherSuite: TlsCipherSuite> ServerHandshake<'_, CipherSuite> {
|
||||
#[allow(dead_code)]
|
||||
pub fn handshake_type(&self) -> HandshakeType {
|
||||
match self {
|
||||
ServerHandshake::ServerHello(_) => HandshakeType::ServerHello,
|
||||
ServerHandshake::EncryptedExtensions(_) => HandshakeType::EncryptedExtensions,
|
||||
ServerHandshake::NewSessionTicket(_) => HandshakeType::NewSessionTicket,
|
||||
ServerHandshake::Certificate(_) => HandshakeType::Certificate,
|
||||
ServerHandshake::CertificateRequest(_) => HandshakeType::CertificateRequest,
|
||||
ServerHandshake::HandshakeVerify(_) => HandshakeType::HandshakeVerify,
|
||||
ServerHandshake::Finished(_) => HandshakeType::Finished,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<CipherSuite: TlsCipherSuite> Debug for ServerHandshake<'_, CipherSuite> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
ServerHandshake::ServerHello(inner) => Debug::fmt(inner, f),
|
||||
ServerHandshake::EncryptedExtensions(inner) => Debug::fmt(inner, f),
|
||||
ServerHandshake::Certificate(inner) => Debug::fmt(inner, f),
|
||||
ServerHandshake::CertificateRequest(inner) => Debug::fmt(inner, f),
|
||||
ServerHandshake::HandshakeVerify(inner) => Debug::fmt(inner, f),
|
||||
ServerHandshake::Finished(inner) => Debug::fmt(inner, f),
|
||||
ServerHandshake::NewSessionTicket(inner) => Debug::fmt(inner, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a, CipherSuite: TlsCipherSuite> defmt::Format for ServerHandshake<'a, CipherSuite> {
|
||||
fn format(&self, f: defmt::Formatter<'_>) {
|
||||
match self {
|
||||
ServerHandshake::ServerHello(inner) => defmt::write!(f, "{}", inner),
|
||||
ServerHandshake::EncryptedExtensions(inner) => defmt::write!(f, "{}", inner),
|
||||
ServerHandshake::Certificate(inner) => defmt::write!(f, "{}", inner),
|
||||
ServerHandshake::CertificateRequest(inner) => defmt::write!(f, "{}", inner),
|
||||
ServerHandshake::HandshakeVerify(inner) => defmt::write!(f, "{}", inner),
|
||||
ServerHandshake::Finished(inner) => defmt::write!(f, "{}", inner),
|
||||
ServerHandshake::NewSessionTicket(inner) => defmt::write!(f, "{}", inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, CipherSuite: TlsCipherSuite> ServerHandshake<'a, CipherSuite> {
|
||||
pub fn read(
|
||||
buf: &mut ParseBuffer<'a>,
|
||||
digest: &mut CipherSuite::Hash,
|
||||
) -> Result<Self, ProtocolError> {
|
||||
let handshake_start = buf.offset();
|
||||
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());
|
||||
}
|
||||
|
||||
digest.update(&buf.as_slice()[handshake_start..handshake_end]);
|
||||
|
||||
Ok(handshake)
|
||||
}
|
||||
|
||||
fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ProtocolError> {
|
||||
let handshake_type =
|
||||
HandshakeType::parse(buf).map_err(|_| ProtocolError::InvalidHandshake)?;
|
||||
|
||||
trace!("handshake = {:?}", handshake_type);
|
||||
|
||||
let content_len = buf
|
||||
.read_u24()
|
||||
.map_err(|_| ProtocolError::InvalidHandshake)?;
|
||||
|
||||
let handshake = match handshake_type {
|
||||
HandshakeType::ServerHello => ServerHandshake::ServerHello(ServerHello::parse(buf)?),
|
||||
HandshakeType::NewSessionTicket => {
|
||||
ServerHandshake::NewSessionTicket(NewSessionTicket::parse(buf)?)
|
||||
}
|
||||
HandshakeType::EncryptedExtensions => {
|
||||
ServerHandshake::EncryptedExtensions(EncryptedExtensions::parse(buf)?)
|
||||
}
|
||||
HandshakeType::Certificate => ServerHandshake::Certificate(CertificateRef::parse(buf)?),
|
||||
|
||||
HandshakeType::CertificateRequest => {
|
||||
ServerHandshake::CertificateRequest(CertificateRequestRef::parse(buf)?)
|
||||
}
|
||||
|
||||
HandshakeType::HandshakeVerify => {
|
||||
ServerHandshake::HandshakeVerify(HandshakeVerifyRef::parse(buf)?)
|
||||
}
|
||||
HandshakeType::Finished => {
|
||||
ServerHandshake::Finished(Finished::parse(buf, content_len)?)
|
||||
}
|
||||
t => {
|
||||
warn!("Unimplemented handshake type: {:?}", t);
|
||||
return Err(ProtocolError::Unimplemented);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(handshake)
|
||||
}
|
||||
}
|
||||
33
src/handshake/new_session_ticket.rs
Normal file
33
src/handshake/new_session_ticket.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::extensions::messages::NewSessionTicketExtension;
|
||||
use crate::parse_buffer::ParseBuffer;
|
||||
use crate::{ProtocolError, unused};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct NewSessionTicket<'a> {
|
||||
_todo: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> NewSessionTicket<'a> {
|
||||
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<NewSessionTicket<'a>, ProtocolError> {
|
||||
let lifetime = buf.read_u32()?;
|
||||
let age_add = buf.read_u32()?;
|
||||
|
||||
let nonce_length = buf.read_u8()?;
|
||||
let nonce = buf
|
||||
.slice(nonce_length as usize)
|
||||
.map_err(|_| ProtocolError::InvalidNonceLength)?;
|
||||
|
||||
let ticket_length = buf.read_u16()?;
|
||||
let ticket = buf
|
||||
.slice(ticket_length as usize)
|
||||
.map_err(|_| ProtocolError::InvalidTicketLength)?;
|
||||
|
||||
let extensions = NewSessionTicketExtension::parse_vector::<1>(buf)?;
|
||||
|
||||
unused((lifetime, age_add, nonce, ticket, extensions));
|
||||
Ok(Self { _todo: PhantomData })
|
||||
}
|
||||
}
|
||||
80
src/handshake/server_hello.rs
Normal file
80
src/handshake/server_hello.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use heapless::Vec;
|
||||
|
||||
use crate::cipher::CryptoEngine;
|
||||
use crate::cipher_suites::CipherSuite;
|
||||
use crate::extensions::extension_data::key_share::KeyShareEntry;
|
||||
use crate::extensions::messages::ServerHelloExtension;
|
||||
use crate::parse_buffer::ParseBuffer;
|
||||
use crate::{ProtocolError, unused};
|
||||
use p256::PublicKey;
|
||||
use p256::ecdh::{EphemeralSecret, SharedSecret};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct ServerHello<'a> {
|
||||
extensions: Vec<ServerHelloExtension<'a>, 4>,
|
||||
}
|
||||
|
||||
impl<'a> ServerHello<'a> {
|
||||
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<ServerHello<'a>, ProtocolError> {
|
||||
// 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];
|
||||
buf.fill(&mut random)?;
|
||||
|
||||
let session_id_length = buf
|
||||
.read_u8()
|
||||
.map_err(|_| ProtocolError::InvalidSessionIdLength)?;
|
||||
|
||||
// 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)?;
|
||||
|
||||
let cipher_suite =
|
||||
CipherSuite::parse(buf).map_err(|_| ProtocolError::InvalidCipherSuite)?;
|
||||
|
||||
// compression_method: always 0x00 in TLS 1.3
|
||||
buf.read_u8()?;
|
||||
|
||||
let extensions = ServerHelloExtension::parse_vector(buf)?;
|
||||
|
||||
debug!("server cipher_suite {:?}", cipher_suite);
|
||||
debug!("server extensions {:?}", extensions);
|
||||
|
||||
unused(session_id);
|
||||
Ok(Self { extensions })
|
||||
}
|
||||
|
||||
pub fn key_share(&self) -> Option<&KeyShareEntry<'_>> {
|
||||
self.extensions.iter().find_map(|e| {
|
||||
if let ServerHelloExtension::KeyShare(entry) = e {
|
||||
Some(&entry.0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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()?;
|
||||
Some(secret.diffie_hellman(&server_public_key))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn initialize_crypto_engine(&self, secret: &EphemeralSecret) -> Option<CryptoEngine> {
|
||||
let server_key_share = self.key_share()?;
|
||||
|
||||
let group = server_key_share.group;
|
||||
|
||||
let server_public_key = PublicKey::from_sec1_bytes(server_key_share.opaque).ok()?;
|
||||
let shared = secret.diffie_hellman(&server_public_key);
|
||||
|
||||
Some(CryptoEngine::new(group, shared))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user