#[cfg(not(feature = "std"))]
extern crate alloc;
use crate::crypto::profiles::SecurityProfileDesc;
use crate::der::Sequence;
use crate::Beamable;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
#[cfg(feature = "derive")]
use crate::Errorizable;
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "derive", derive(Beamable, Sequence))]
pub struct SecurityOffer {
pub profiles: Vec<SecurityProfileDesc>,
}
impl SecurityOffer {
pub fn new(profiles: Vec<SecurityProfileDesc>) -> Self {
Self { profiles }
}
pub fn single(profile: SecurityProfileDesc) -> Self {
Self { profiles: Vec::from([profile]) }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "derive", derive(Beamable, Sequence))]
pub struct SecurityAccept {
pub profile: SecurityProfileDesc,
}
impl SecurityAccept {
pub fn new(profile: SecurityProfileDesc) -> Self {
Self { profile }
}
}
#[cfg_attr(feature = "derive", derive(Errorizable))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NegotiationError {
#[cfg_attr(feature = "derive", error("No mutually supported security profile"))]
NoMutualProfile,
#[cfg_attr(feature = "derive", error("Security offer is empty"))]
EmptyOffer,
#[cfg_attr(feature = "derive", error("DER encoding error: {0}"))]
DerError(crate::der::Error),
}
crate::impl_error_display!(NegotiationError {
NoMutualProfile => "No mutually supported security profile",
EmptyOffer => "Security offer is empty",
DerError(e) => "DER encoding error: {e}",
});
impl From<crate::der::Error> for NegotiationError {
fn from(e: crate::der::Error) -> Self {
Self::DerError(e)
}
}
pub fn select_profile(
offer: &SecurityOffer,
supported: &[SecurityProfileDesc],
) -> Result<SecurityProfileDesc, NegotiationError> {
if offer.profiles.is_empty() {
return Err(NegotiationError::EmptyOffer);
}
for candidate in &offer.profiles {
if supported.contains(candidate) {
return Ok(*candidate);
}
}
Err(NegotiationError::NoMutualProfile)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oids::{
AES_128_WRAP, AES_192_WRAP, AES_256_GCM, AES_256_WRAP, HASH_SHA3_256, SIGNER_ECDSA_WITH_SHA3_512,
};
fn mock_profile(id: u8) -> SecurityProfileDesc {
SecurityProfileDesc {
digest: HASH_SHA3_256,
aead: Some(AES_256_GCM),
aead_key_size: Some(32),
signature: Some(SIGNER_ECDSA_WITH_SHA3_512),
kdf: Some(HASH_SHA3_256),
curve: Some(crate::oids::CURVE_SECP256K1),
key_wrap: match id {
1 => Some(AES_128_WRAP),
2 => Some(AES_256_WRAP),
3 => Some(AES_192_WRAP),
_ => None,
},
kem: None,
}
}
#[test]
fn test_offer_single() {
let profile = mock_profile(1);
let offer = SecurityOffer::single(profile);
assert_eq!(offer.profiles.len(), 1);
assert_eq!(offer.profiles[0], profile);
}
#[test]
fn test_select_first_mutual() {
let p1 = mock_profile(1);
let p2 = mock_profile(2);
let p3 = mock_profile(3);
let offer = SecurityOffer::new(Vec::from([p1, p2, p3]));
let supported = [p2, p3];
let selected = select_profile(&offer, &supported).unwrap();
assert_eq!(selected, p2); }
#[test]
fn test_no_mutual_profile() {
let p1 = mock_profile(1);
let p2 = mock_profile(2);
let p3 = mock_profile(3);
let offer = SecurityOffer::new(Vec::from([p1, p2]));
let supported = [p3];
let result = select_profile(&offer, &supported);
assert!(matches!(result, Err(NegotiationError::NoMutualProfile)));
}
#[test]
fn test_empty_offer() {
let offer = SecurityOffer::new(Vec::new());
let supported = [mock_profile(1)];
let result = select_profile(&offer, &supported);
assert!(matches!(result, Err(NegotiationError::EmptyOffer)));
}
#[cfg(feature = "aead")]
#[test]
fn test_select_profile_multiple_aead_ciphers() {
use crate::oids::{
AES_128_GCM, AES_128_WRAP, AES_256_GCM, AES_256_WRAP, CURVE_SECP256K1, HASH_SHA256,
SIGNER_ECDSA_WITH_SHA256,
};
let aes128_gcm = SecurityProfileDesc {
digest: HASH_SHA256,
aead: Some(AES_128_GCM),
aead_key_size: Some(16),
signature: Some(SIGNER_ECDSA_WITH_SHA256),
kdf: Some(HASH_SHA256),
curve: Some(CURVE_SECP256K1),
key_wrap: Some(AES_128_WRAP),
kem: None,
};
let aes256_gcm = SecurityProfileDesc {
digest: HASH_SHA256,
aead: Some(AES_256_GCM),
aead_key_size: Some(32),
signature: Some(SIGNER_ECDSA_WITH_SHA256),
kdf: Some(HASH_SHA256),
curve: Some(CURVE_SECP256K1),
key_wrap: Some(AES_256_WRAP),
kem: None,
};
let client_offer = SecurityOffer::new(Vec::from([aes128_gcm, aes256_gcm]));
let server_supported = [aes256_gcm, aes128_gcm];
let selected = select_profile(&client_offer, &server_supported).unwrap();
assert_eq!(selected.aead, Some(AES_128_GCM));
assert_eq!(selected.aead_key_size, Some(16));
let client_offer_256 = SecurityOffer::new(Vec::from([aes256_gcm, aes128_gcm]));
let selected_256 = select_profile(&client_offer_256, &server_supported).unwrap();
assert_eq!(selected_256.aead, Some(AES_256_GCM));
assert_eq!(selected_256.aead_key_size, Some(32));
let server_256_only = [aes256_gcm];
let selected_fallback = select_profile(&client_offer, &server_256_only).unwrap();
assert_eq!(selected_fallback.aead, Some(AES_256_GCM));
assert_eq!(selected_fallback.aead_key_size, Some(32));
}
}