use crate::constants::{MIN_SALT_ENTROPY_BYTES, TIGHTBEAM_SESSION_KDF_INFO};
use crate::crypto::aead::KeyInit;
use crate::crypto::kdf::KdfFunction;
use crate::crypto::profiles::{CryptoProvider, SecurityProfileDesc};
use crate::crypto::x509::attr::{Attribute, Attributes};
use crate::oids::HANDSHAKE_ABORT_ALERT;
use crate::transport::handshake::attributes::{extract_alert_x509, find_x509};
use crate::transport::handshake::error::HandshakeError;
use crate::transport::handshake::negotiation::{select_profile, SecurityOffer};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
pub trait HandshakeNegotiation {
fn supported_profiles(&self) -> &[SecurityProfileDesc];
fn negotiate_profile(&self, offer: Option<&SecurityOffer>) -> Result<SecurityProfileDesc, HandshakeError> {
let supported = self.supported_profiles();
if supported.is_empty() {
return Err(HandshakeError::NoSupportedProfiles);
}
match offer {
Some(offer) => Ok(select_profile(offer, supported)?),
None => Ok(supported[0]), }
}
}
pub trait HandshakeFinalization<P>
where
P: CryptoProvider,
{
fn selected_profile(&self) -> Option<SecurityProfileDesc>;
fn derive_session_aead(&self, input_key: &[u8], salt: &[u8]) -> Result<P::AeadCipher, HandshakeError>
where
P::AeadCipher: KeyInit,
{
let profile = self.selected_profile().ok_or(HandshakeError::InvalidState)?;
let key_size = profile.aead_key_size.ok_or(HandshakeError::InvalidState)? as usize;
if salt.len() < MIN_SALT_ENTROPY_BYTES {
return Err(HandshakeError::InsufficientSaltEntropy {
actual: salt.len(),
minimum: MIN_SALT_ENTROPY_BYTES,
});
}
let final_key_bytes = P::Kdf::derive_dynamic_key(input_key, TIGHTBEAM_SESSION_KDF_INFO, Some(salt), key_size)?;
Ok(P::AeadCipher::new_from_slice(&final_key_bytes[..])?)
}
}
pub trait HandshakeAlertHandler {
fn check_for_alert(&self, attrs: Option<&Attributes>) -> Result<(), HandshakeError> {
if let Some(attrs) = attrs {
let attr_refs: Vec<&Attribute> = attrs.iter().collect();
if let Ok(alert_attr) = find_x509(&attr_refs, &HANDSHAKE_ABORT_ALERT) {
let alert = extract_alert_x509(alert_attr)?;
return Err(HandshakeError::AbortReceived(alert));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::profiles::DefaultCryptoProvider;
use crate::der::asn1::ObjectIdentifier;
use crate::oids::{AES_128_GCM, AES_256_GCM, CURVE_SECP256K1, HASH_SHA256, SIGNER_ECDSA_WITH_SHA256};
struct MockServer {
profiles: Vec<SecurityProfileDesc>,
}
impl HandshakeNegotiation for MockServer {
fn supported_profiles(&self) -> &[SecurityProfileDesc] {
&self.profiles
}
}
struct MockClient {
profile: Option<SecurityProfileDesc>,
}
impl<P> HandshakeFinalization<P> for MockClient
where
P: CryptoProvider,
{
fn selected_profile(&self) -> Option<SecurityProfileDesc> {
self.profile
}
}
fn create_test_profile(aead_oid: ObjectIdentifier, key_size: u16) -> SecurityProfileDesc {
SecurityProfileDesc {
digest: HASH_SHA256,
aead: Some(aead_oid),
aead_key_size: Some(key_size),
signature: Some(SIGNER_ECDSA_WITH_SHA256),
kdf: Some(HASH_SHA256),
curve: Some(CURVE_SECP256K1),
key_wrap: None,
kem: None,
}
}
#[test]
fn test_negotiate_profile_with_offer() -> Result<(), Box<dyn std::error::Error>> {
let p_a = create_test_profile(AES_128_GCM, 16);
let p_b = create_test_profile(AES_256_GCM, 32);
let server = MockServer { profiles: vec![p_a, p_b] };
let offer = SecurityOffer::new(vec![p_a, p_b]);
let selected = server.negotiate_profile(Some(&offer))?;
assert_eq!(selected.aead_key_size, Some(16)); Ok(())
}
#[test]
fn test_negotiate_profile_dealers_choice() -> Result<(), Box<dyn std::error::Error>> {
let p_a = create_test_profile(AES_128_GCM, 16);
let p_b = create_test_profile(AES_256_GCM, 32);
let server = MockServer { profiles: vec![p_a, p_b] };
let selected = server.negotiate_profile(None)?;
assert_eq!(selected.aead_key_size, Some(16)); Ok(())
}
#[test]
fn test_negotiate_profile_no_supported() {
let server = MockServer { profiles: vec![] };
let result = server.negotiate_profile(None);
assert!(matches!(result, Err(HandshakeError::NoSupportedProfiles)));
}
#[test]
fn test_derive_session_aead_success() {
let profile = create_test_profile(AES_256_GCM, 32);
let client = MockClient { profile: Some(profile) };
let input_key = [0x42u8; 32];
let salt = [0x99u8; 32];
let result = <MockClient as HandshakeFinalization<DefaultCryptoProvider>>::derive_session_aead(
&client, &input_key, &salt,
);
assert!(result.is_ok());
}
#[test]
fn test_derive_session_aead_insufficient_salt() {
let profile = create_test_profile(AES_256_GCM, 32);
let client = MockClient { profile: Some(profile) };
let input_key = [0x42u8; 32];
let salt = [0x99u8; 8];
let result = <MockClient as HandshakeFinalization<DefaultCryptoProvider>>::derive_session_aead(
&client, &input_key, &salt,
);
assert!(matches!(
result,
Err(HandshakeError::InsufficientSaltEntropy { actual: 8, minimum: 16 })
));
}
#[test]
fn test_derive_session_aead_no_profile() {
let client = MockClient { profile: None };
let input_key = [0x42u8; 32];
let salt = [0x99u8; 32];
let result = <MockClient as HandshakeFinalization<DefaultCryptoProvider>>::derive_session_aead(
&client, &input_key, &salt,
);
assert!(matches!(result, Err(HandshakeError::InvalidState)));
}
}