use super::ProtocolVersion;
use crate::error::{last_error, Error, Result};
use crate::ffi::SslCtx;
use std::ffi::CString;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CipherSuite {
iana_name: &'static str,
openssl_name: &'static str,
iana_id: u16,
version: ProtocolVersion,
}
impl CipherSuite {
#[must_use]
pub const fn name(&self) -> &'static str {
self.iana_name
}
#[must_use]
pub const fn id(&self) -> u16 {
self.iana_id
}
#[must_use]
pub const fn version(&self) -> ProtocolVersion {
self.version
}
pub(crate) const fn openssl_name(&self) -> &'static str {
self.openssl_name
}
}
pub static TLS13_AES_128_GCM_SHA256: &CipherSuite = &CipherSuite {
iana_name: "TLS_AES_128_GCM_SHA256",
openssl_name: "TLS_AES_128_GCM_SHA256",
iana_id: 0x1301,
version: ProtocolVersion::Tls13,
};
pub static TLS13_AES_256_GCM_SHA384: &CipherSuite = &CipherSuite {
iana_name: "TLS_AES_256_GCM_SHA384",
openssl_name: "TLS_AES_256_GCM_SHA384",
iana_id: 0x1302,
version: ProtocolVersion::Tls13,
};
pub static TLS13_CHACHA20_POLY1305_SHA256: &CipherSuite = &CipherSuite {
iana_name: "TLS_CHACHA20_POLY1305_SHA256",
openssl_name: "TLS_CHACHA20_POLY1305_SHA256",
iana_id: 0x1303,
version: ProtocolVersion::Tls13,
};
pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: &CipherSuite = &CipherSuite {
iana_name: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
openssl_name: "ECDHE-ECDSA-AES128-GCM-SHA256",
iana_id: 0xC02B,
version: ProtocolVersion::Tls12,
};
pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: &CipherSuite = &CipherSuite {
iana_name: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
openssl_name: "ECDHE-ECDSA-AES256-GCM-SHA384",
iana_id: 0xC02C,
version: ProtocolVersion::Tls12,
};
pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: &CipherSuite = &CipherSuite {
iana_name: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
openssl_name: "ECDHE-ECDSA-CHACHA20-POLY1305",
iana_id: 0xCCA9,
version: ProtocolVersion::Tls12,
};
pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: &CipherSuite = &CipherSuite {
iana_name: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
openssl_name: "ECDHE-RSA-AES128-GCM-SHA256",
iana_id: 0xC02F,
version: ProtocolVersion::Tls12,
};
pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: &CipherSuite = &CipherSuite {
iana_name: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
openssl_name: "ECDHE-RSA-AES256-GCM-SHA384",
iana_id: 0xC030,
version: ProtocolVersion::Tls12,
};
pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: &CipherSuite = &CipherSuite {
iana_name: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
openssl_name: "ECDHE-RSA-CHACHA20-POLY1305",
iana_id: 0xCCA8,
version: ProtocolVersion::Tls12,
};
pub static ALL_CIPHER_SUITES: &[&CipherSuite] = &[
TLS13_AES_128_GCM_SHA256,
TLS13_AES_256_GCM_SHA384,
TLS13_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
];
pub static DEFAULT_CIPHER_SUITES: &[&CipherSuite] = ALL_CIPHER_SUITES;
pub(crate) fn split_for_aws_lc(suites: &[&CipherSuite]) -> (Option<String>, Option<String>) {
let mut tls12 = String::new();
let mut tls13 = String::new();
for s in suites {
let target = match s.version {
ProtocolVersion::Tls12 => &mut tls12,
ProtocolVersion::Tls13 => &mut tls13,
};
if !target.is_empty() {
target.push(':');
}
target.push_str(s.openssl_name());
}
let tls12 = if tls12.is_empty() { None } else { Some(tls12) };
let tls13 = if tls13.is_empty() { None } else { Some(tls13) };
(tls12, tls13)
}
pub(crate) fn apply_to_ctx(ctx: &SslCtx, suites: &[&CipherSuite]) -> Result<()> {
let (tls12, tls13) = split_for_aws_lc(suites);
if let Some(list) = tls12 {
let c = CString::new(list).expect("cipher-suite names contain no NUL bytes");
let ok = unsafe { aws_lc_sys::SSL_CTX_set_cipher_list(ctx.as_ptr(), c.as_ptr()) };
if ok != 1 {
return Err(Error::Init(format!(
"SSL_CTX_set_cipher_list: {}",
last_error()
)));
}
}
if let Some(list) = tls13 {
let c = CString::new(list).expect("cipher-suite names contain no NUL bytes");
let ok = unsafe { aws_lc_sys::SSL_CTX_set_ciphersuites(ctx.as_ptr(), c.as_ptr()) };
if ok != 1 {
return Err(Error::Init(format!(
"SSL_CTX_set_ciphersuites: {}",
last_error()
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metadata_round_trip() {
assert_eq!(TLS13_AES_128_GCM_SHA256.name(), "TLS_AES_128_GCM_SHA256");
assert_eq!(TLS13_AES_128_GCM_SHA256.id(), 0x1301);
assert_eq!(TLS13_AES_128_GCM_SHA256.version(), ProtocolVersion::Tls13);
assert_eq!(
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.openssl_name(),
"ECDHE-ECDSA-AES128-GCM-SHA256"
);
assert_eq!(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.id(), 0xC02B);
assert_eq!(
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.version(),
ProtocolVersion::Tls12
);
}
#[test]
fn split_groups_by_version() {
let suites = [
TLS13_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS13_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
];
let (tls12, tls13) = split_for_aws_lc(&suites);
assert_eq!(
tls12.as_deref(),
Some("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384")
);
assert_eq!(
tls13.as_deref(),
Some("TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256")
);
}
#[test]
fn split_empty_input_yields_none() {
let (tls12, tls13) = split_for_aws_lc(&[]);
assert!(tls12.is_none());
assert!(tls13.is_none());
}
#[test]
fn split_tls13_only() {
let (tls12, tls13) = split_for_aws_lc(&[TLS13_AES_256_GCM_SHA384]);
assert!(tls12.is_none());
assert_eq!(tls13.as_deref(), Some("TLS_AES_256_GCM_SHA384"));
}
#[test]
fn default_matches_all_for_now() {
assert!(std::ptr::eq(DEFAULT_CIPHER_SUITES, ALL_CIPHER_SUITES));
}
#[test]
fn all_constant_lists_every_suite() {
assert_eq!(ALL_CIPHER_SUITES.len(), 9);
for s in ALL_CIPHER_SUITES {
assert!(matches!(
s.version(),
ProtocolVersion::Tls12 | ProtocolVersion::Tls13
));
assert!(!s.name().is_empty());
}
}
}