use heapless::Vec;
use crate::buffer::CryptoBuffer;
use crate::cipher_suites::CipherSuite;
use crate::crypto_engine::CryptoEngine;
use crate::extensions::extension_data::key_share::KeyShareEntry;
use crate::extensions::messages::ServerHelloExtension;
use crate::handshake::{LEGACY_VERSION, Random};
use crate::parse_buffer::ParseBuffer;
use crate::{TlsError, 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>, TlsError> {
let _version = buf.read_u16().map_err(|_| TlsError::InvalidHandshake)?;
let mut random = [0; 32];
buf.fill(&mut random)?;
let session_id_length = buf
.read_u8()
.map_err(|_| TlsError::InvalidSessionIdLength)?;
let session_id = buf
.slice(session_id_length as usize)
.map_err(|_| TlsError::InvalidSessionIdLength)?;
let cipher_suite = CipherSuite::parse(buf).map_err(|_| TlsError::InvalidCipherSuite)?;
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
}
})
}
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))
}
}
#[derive(Debug)]
pub struct ServerHelloEmit<'a> {
pub random: Random,
pub legacy_session_id_echo: &'a [u8],
pub cipher_suite: u16,
pub extensions: Vec<ServerHelloExtension<'a>, 4>,
}
impl<'a> ServerHelloEmit<'a> {
pub fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), TlsError> {
if self.legacy_session_id_echo.len() > 32 {
return Err(TlsError::EncodeError);
}
buf.push_u16(LEGACY_VERSION)
.map_err(|_| TlsError::EncodeError)?;
buf.extend_from_slice(&self.random)
.map_err(|_| TlsError::EncodeError)?;
buf.push(self.legacy_session_id_echo.len() as u8)
.map_err(|_| TlsError::EncodeError)?;
buf.extend_from_slice(self.legacy_session_id_echo)
.map_err(|_| TlsError::EncodeError)?;
buf.push_u16(self.cipher_suite)
.map_err(|_| TlsError::EncodeError)?;
buf.push(0).map_err(|_| TlsError::EncodeError)?;
buf.with_u16_length(|buf| {
for ext in &self.extensions {
ext.encode(buf)?;
}
Ok(())
})?;
Ok(())
}
}
#[cfg(test)]
mod emit_tests {
use super::*;
use crate::extensions::extension_data::pre_shared_key::PreSharedKeyServerHello;
use crate::extensions::extension_data::supported_versions::{
SupportedVersionsServerHello, TLS13,
};
#[test]
fn server_hello_emit_round_trip() {
let random: Random = [0x42; 32];
let session_id = [0x01u8, 0x02, 0x03, 0x04];
let mut extensions: Vec<ServerHelloExtension<'_>, 4> = Vec::new();
extensions
.push(ServerHelloExtension::SupportedVersions(
SupportedVersionsServerHello {
selected_version: TLS13,
},
))
.unwrap();
extensions
.push(ServerHelloExtension::PreSharedKey(PreSharedKeyServerHello {
selected_identity: 0,
}))
.unwrap();
let emit = ServerHelloEmit {
random,
legacy_session_id_echo: &session_id,
cipher_suite: 0x1301, extensions,
};
let mut backing = [0u8; 256];
let mut crypto = CryptoBuffer::wrap(&mut backing);
emit.encode(&mut crypto).expect("encode");
let written_len = crypto.len();
let written = &backing[..written_len];
let expected_min = 2 + 32 + 1 + 4 + 2 + 1 + 2 + 6 + 6;
assert_eq!(written_len, expected_min);
assert_eq!(&written[0..2], &[0x03, 0x03]); assert_eq!(&written[2..34], &random); assert_eq!(written[34], 4); assert_eq!(&written[35..39], &session_id); assert_eq!(&written[39..41], &[0x13, 0x01]); assert_eq!(written[41], 0);
let mut parse = ParseBuffer::new(written);
let parsed = ServerHello::parse(&mut parse).expect("ServerHello::parse round-trip");
let psk_id = parsed.extensions.iter().find_map(|ext| {
if let ServerHelloExtension::PreSharedKey(p) = ext {
Some(p.selected_identity)
} else {
None
}
});
assert_eq!(psk_id, Some(0));
let sv = parsed.extensions.iter().find_map(|ext| {
if let ServerHelloExtension::SupportedVersions(v) = ext {
Some(v.selected_version)
} else {
None
}
});
assert_eq!(sv, Some(TLS13));
}
#[test]
fn server_hello_emit_rejects_oversized_session_id() {
let oversized = [0u8; 33];
let emit = ServerHelloEmit {
random: [0; 32],
legacy_session_id_echo: &oversized,
cipher_suite: 0x1301,
extensions: Vec::new(),
};
let mut backing = [0u8; 256];
let mut crypto = CryptoBuffer::wrap(&mut backing);
let result = emit.encode(&mut crypto);
assert!(matches!(result, Err(TlsError::EncodeError)));
}
}