rustls/rustls/src/suites.rs

464 lines
17 KiB
Rust

use crate::msgs::enums::{CipherSuite, HashAlgorithm, SignatureAlgorithm, SignatureScheme};
use crate::msgs::enums::{NamedGroup, ProtocolVersion};
use crate::msgs::handshake::KeyExchangeAlgorithm;
use crate::msgs::handshake::DecomposedSignatureScheme;
use crate::msgs::handshake::{ClientECDHParams, ServerECDHParams};
use crate::msgs::codec::{Reader, Codec};
use ring;
/// Bulk symmetric encryption scheme used by a cipher suite.
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq)]
pub enum BulkAlgorithm {
/// AES with 128-bit keys in Galois counter mode.
AES_128_GCM,
/// AES with 256-bit keys in Galois counter mode.
AES_256_GCM,
/// Chacha20 for confidentiality with poly1305 for authenticity.
CHACHA20_POLY1305,
}
/// The result of a key exchange. This has our public key,
/// and the agreed premaster secret.
pub struct KeyExchangeResult {
pub pubkey: ring::agreement::PublicKey,
pub premaster_secret: Vec<u8>,
}
/// An in-progress key exchange. This has the algorithm,
/// our private key, and our public key.
pub struct KeyExchange {
pub group: NamedGroup,
alg: &'static ring::agreement::Algorithm,
privkey: ring::agreement::EphemeralPrivateKey,
pub pubkey: ring::agreement::PublicKey,
}
impl KeyExchange {
pub fn named_group_to_ecdh_alg(group: NamedGroup)
-> Option<&'static ring::agreement::Algorithm> {
match group {
NamedGroup::X25519 => Some(&ring::agreement::X25519),
NamedGroup::secp256r1 => Some(&ring::agreement::ECDH_P256),
NamedGroup::secp384r1 => Some(&ring::agreement::ECDH_P384),
_ => None,
}
}
pub fn supported_groups() -> &'static [NamedGroup] {
// in preference order
&[
NamedGroup::X25519,
NamedGroup::secp384r1,
NamedGroup::secp256r1
]
}
pub fn client_ecdhe(kx_params: &[u8]) -> Option<KeyExchangeResult> {
let mut rd = Reader::init(kx_params);
let ecdh_params = ServerECDHParams::read(&mut rd)?;
KeyExchange::start_ecdhe(ecdh_params.curve_params.named_group)?
.complete(&ecdh_params.public.0)
}
pub fn start_ecdhe(named_group: NamedGroup) -> Option<KeyExchange> {
let alg = KeyExchange::named_group_to_ecdh_alg(named_group)?;
let rng = ring::rand::SystemRandom::new();
let ours = ring::agreement::EphemeralPrivateKey::generate(alg, &rng).unwrap();
let pubkey = ours.compute_public_key().unwrap();
Some(KeyExchange {
group: named_group,
alg,
privkey: ours,
pubkey,
})
}
pub fn check_client_params(&self, kx_params: &[u8]) -> bool {
self.decode_client_params(kx_params).is_some()
}
fn decode_client_params(&self, kx_params: &[u8]) -> Option<ClientECDHParams> {
let mut rd = Reader::init(kx_params);
let ecdh_params = ClientECDHParams::read(&mut rd).unwrap();
if rd.any_left() {
None
} else {
Some(ecdh_params)
}
}
pub fn server_complete(self, kx_params: &[u8]) -> Option<KeyExchangeResult> {
self.decode_client_params(kx_params)
.and_then(|ecdh| self.complete(&ecdh.public.0))
}
pub fn complete(self, peer: &[u8]) -> Option<KeyExchangeResult> {
let peer_key = ring::agreement::UnparsedPublicKey::new(self.alg, peer);
let secret = ring::agreement::agree_ephemeral(self.privkey,
&peer_key,
(),
|v| {
let mut r = Vec::new();
r.extend_from_slice(v);
Ok(r)
});
if secret.is_err() {
return None;
}
Some(KeyExchangeResult {
pubkey: self.pubkey,
premaster_secret: secret.unwrap(),
})
}
}
/// A cipher suite supported by rustls.
///
/// All possible instances of this class are provided by the library in
/// the `ALL_CIPHERSUITES` array.
#[derive(Debug)]
pub struct SupportedCipherSuite {
/// The TLS enumeration naming this cipher suite.
pub suite: CipherSuite,
/// How to exchange/agree keys.
pub kx: KeyExchangeAlgorithm,
/// How to do bulk encryption.
pub bulk: BulkAlgorithm,
/// How to do hashing.
pub hash: HashAlgorithm,
/// How to sign messages.
pub sign: SignatureAlgorithm,
/// Encryption key length, for the bulk algorithm.
pub enc_key_len: usize,
/// How long the fixed part of the 'IV' is.
///
/// This isn't usually an IV, but we continue the
/// terminology misuse to match the standard.
pub fixed_iv_len: usize,
/// This is a non-standard extension which extends the
/// key block to provide an initial explicit nonce offset,
/// in a deterministic and safe way. GCM needs this,
/// chacha20poly1305 works this way by design.
pub explicit_nonce_len: usize,
pub(crate) hkdf_algorithm: ring::hkdf::Algorithm,
}
impl PartialEq for SupportedCipherSuite {
fn eq(&self, other: &SupportedCipherSuite) -> bool {
self.suite == other.suite
}
}
impl SupportedCipherSuite {
/// Which hash function to use with this suite.
pub fn get_hash(&self) -> &'static ring::digest::Algorithm {
self.hkdf_algorithm.hmac_algorithm().digest_algorithm()
}
/// We have parameters and a verified public key in `kx_params`.
/// Generate an ephemeral key, generate the shared secret, and
/// return it and the public half in a `KeyExchangeResult`.
pub fn do_client_kx(&self, kx_params: &[u8]) -> Option<KeyExchangeResult> {
match self.kx {
KeyExchangeAlgorithm::ECDHE => KeyExchange::client_ecdhe(kx_params),
_ => None,
}
}
/// Start the KX process with the given group. This generates
/// the server's share, but we don't yet have the client's share.
pub fn start_server_kx(&self, named_group: NamedGroup) -> Option<KeyExchange> {
match self.kx {
KeyExchangeAlgorithm::ECDHE => KeyExchange::start_ecdhe(named_group),
_ => None,
}
}
/// Resolve the set of supported `SignatureScheme`s from the
/// offered `SupportedSignatureSchemes`. If we return an empty
/// set, the handshake terminates.
pub fn resolve_sig_schemes(&self,
offered: &[SignatureScheme])
-> Vec<SignatureScheme> {
let mut our_preference = vec![
// Prefer the designated hash algorithm of this suite, for
// security level consistency.
SignatureScheme::make(self.sign, self.hash),
// Then prefer the right sign algorithm, with the best hashes
// first.
SignatureScheme::make(self.sign, HashAlgorithm::SHA512),
SignatureScheme::make(self.sign, HashAlgorithm::SHA384),
SignatureScheme::make(self.sign, HashAlgorithm::SHA256)
];
// For RSA, support PSS too
if self.sign == SignatureAlgorithm::RSA {
our_preference.push(SignatureScheme::RSA_PSS_SHA512);
our_preference.push(SignatureScheme::RSA_PSS_SHA384);
our_preference.push(SignatureScheme::RSA_PSS_SHA256);
}
our_preference.retain(|pref| offered.contains(pref));
our_preference
}
/// Which AEAD algorithm to use for this suite.
pub fn get_aead_alg(&self) -> &'static ring::aead::Algorithm {
match self.bulk {
BulkAlgorithm::AES_128_GCM => &ring::aead::AES_128_GCM,
BulkAlgorithm::AES_256_GCM => &ring::aead::AES_256_GCM,
BulkAlgorithm::CHACHA20_POLY1305 => &ring::aead::CHACHA20_POLY1305,
}
}
/// Length of key block that needs to be output by the key
/// derivation phase for this suite.
pub fn key_block_len(&self) -> usize {
(self.enc_key_len + self.fixed_iv_len) * 2 + self.explicit_nonce_len
}
/// Return true if this suite is usable for TLS `version`.
pub fn usable_for_version(&self, version: ProtocolVersion) -> bool {
match version {
ProtocolVersion::TLSv1_3 => self.sign == SignatureAlgorithm::Anonymous,
ProtocolVersion::TLSv1_2 => self.sign != SignatureAlgorithm::Anonymous,
_ => false,
}
}
/// Can a session using suite self resume using suite new_suite?
pub fn can_resume_to(&self, new_suite: &SupportedCipherSuite) -> bool {
if self.usable_for_version(ProtocolVersion::TLSv1_3) &&
new_suite.usable_for_version(ProtocolVersion::TLSv1_3) {
// TLS1.3 actually specifies requirements here: suites are compatible
// for resumption if they have the same KDF hash
self.hash == new_suite.hash
} else if self.usable_for_version(ProtocolVersion::TLSv1_2) &&
new_suite.usable_for_version(ProtocolVersion::TLSv1_2) {
// Previous versions don't specify any constraint, so we don't
// resume between suites to avoid bad interactions.
self.suite == new_suite.suite
} else {
// Suites for different versions definitely can't resume!
false
}
}
}
pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite =
SupportedCipherSuite {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
kx: KeyExchangeAlgorithm::ECDHE,
sign: SignatureAlgorithm::ECDSA,
bulk: BulkAlgorithm::CHACHA20_POLY1305,
hash: HashAlgorithm::SHA256,
enc_key_len: 32,
fixed_iv_len: 12,
explicit_nonce_len: 0,
hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite =
SupportedCipherSuite {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
kx: KeyExchangeAlgorithm::ECDHE,
sign: SignatureAlgorithm::RSA,
bulk: BulkAlgorithm::CHACHA20_POLY1305,
hash: HashAlgorithm::SHA256,
enc_key_len: 32,
fixed_iv_len: 12,
explicit_nonce_len: 0,
hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
kx: KeyExchangeAlgorithm::ECDHE,
sign: SignatureAlgorithm::RSA,
bulk: BulkAlgorithm::AES_128_GCM,
hash: HashAlgorithm::SHA256,
enc_key_len: 16,
fixed_iv_len: 4,
explicit_nonce_len: 8,
hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = SupportedCipherSuite {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
kx: KeyExchangeAlgorithm::ECDHE,
sign: SignatureAlgorithm::RSA,
bulk: BulkAlgorithm::AES_256_GCM,
hash: HashAlgorithm::SHA384,
enc_key_len: 32,
fixed_iv_len: 4,
explicit_nonce_len: 8,
hkdf_algorithm: ring::hkdf::HKDF_SHA384,
};
pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
kx: KeyExchangeAlgorithm::ECDHE,
sign: SignatureAlgorithm::ECDSA,
bulk: BulkAlgorithm::AES_128_GCM,
hash: HashAlgorithm::SHA256,
enc_key_len: 16,
fixed_iv_len: 4,
explicit_nonce_len: 8,
hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = SupportedCipherSuite {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
kx: KeyExchangeAlgorithm::ECDHE,
sign: SignatureAlgorithm::ECDSA,
bulk: BulkAlgorithm::AES_256_GCM,
hash: HashAlgorithm::SHA384,
enc_key_len: 32,
fixed_iv_len: 4,
explicit_nonce_len: 8,
hkdf_algorithm: ring::hkdf::HKDF_SHA384,
};
pub static TLS13_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = SupportedCipherSuite {
suite: CipherSuite::TLS13_CHACHA20_POLY1305_SHA256,
kx: KeyExchangeAlgorithm::BulkOnly,
sign: SignatureAlgorithm::Anonymous,
bulk: BulkAlgorithm::CHACHA20_POLY1305,
hash: HashAlgorithm::SHA256,
enc_key_len: 32,
fixed_iv_len: 12,
explicit_nonce_len: 0,
hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static TLS13_AES_256_GCM_SHA384: SupportedCipherSuite = SupportedCipherSuite {
suite: CipherSuite::TLS13_AES_256_GCM_SHA384,
kx: KeyExchangeAlgorithm::BulkOnly,
sign: SignatureAlgorithm::Anonymous,
bulk: BulkAlgorithm::AES_256_GCM,
hash: HashAlgorithm::SHA384,
enc_key_len: 32,
fixed_iv_len: 12,
explicit_nonce_len: 0,
hkdf_algorithm: ring::hkdf::HKDF_SHA384,
};
pub static TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite {
suite: CipherSuite::TLS13_AES_128_GCM_SHA256,
kx: KeyExchangeAlgorithm::BulkOnly,
sign: SignatureAlgorithm::Anonymous,
bulk: BulkAlgorithm::AES_128_GCM,
hash: HashAlgorithm::SHA256,
enc_key_len: 16,
fixed_iv_len: 12,
explicit_nonce_len: 0,
hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
/// A list of all the cipher suites supported by rustls.
pub static ALL_CIPHERSUITES: [&'static SupportedCipherSuite; 9] =
[// TLS1.3 suites
&TLS13_CHACHA20_POLY1305_SHA256,
&TLS13_AES_256_GCM_SHA384,
&TLS13_AES_128_GCM_SHA256,
// TLS1.2 suites
&TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
&TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
&TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
&TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
&TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
&TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256];
// These both O(N^2)!
pub fn choose_ciphersuite_preferring_client(client_suites: &[CipherSuite],
server_suites: &[&'static SupportedCipherSuite])
-> Option<&'static SupportedCipherSuite> {
for client_suite in client_suites {
if let Some(selected) = server_suites.iter().find(|x| *client_suite == x.suite) {
return Some(*selected);
}
}
None
}
pub fn choose_ciphersuite_preferring_server(client_suites: &[CipherSuite],
server_suites: &[&'static SupportedCipherSuite])
-> Option<&'static SupportedCipherSuite> {
if let Some(selected) = server_suites.iter().find(|x| client_suites.contains(&x.suite)) {
return Some(*selected);
}
None
}
/// Return a list of the ciphersuites in `all` with the suites
/// incompatible with `SignatureAlgorithm` `sigalg` removed.
pub fn reduce_given_sigalg(all: &[&'static SupportedCipherSuite],
sigalg: SignatureAlgorithm)
-> Vec<&'static SupportedCipherSuite> {
all.iter()
.filter(|&&suite| suite.sign == SignatureAlgorithm::Anonymous || suite.sign == sigalg)
.cloned()
.collect()
}
/// Return a list of the ciphersuites in `all` with the suites
/// incompatible with the chosen `version` removed.
pub fn reduce_given_version(all: &[&'static SupportedCipherSuite],
version: ProtocolVersion)
-> Vec<&'static SupportedCipherSuite> {
all.iter()
.filter(|&&suite| suite.usable_for_version(version))
.cloned()
.collect()
}
#[cfg(test)]
mod test {
use crate::msgs::enums::CipherSuite;
#[test]
fn test_client_pref() {
let client = vec![CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384];
let server = vec![&super::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
&super::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256];
let chosen = super::choose_ciphersuite_preferring_client(&client, &server);
assert!(chosen.is_some());
assert_eq!(chosen.unwrap(),
&super::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256);
}
#[test]
fn test_server_pref() {
let client = vec![CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384];
let server = vec![&super::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
&super::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256];
let chosen = super::choose_ciphersuite_preferring_server(&client, &server);
assert!(chosen.is_some());
assert_eq!(chosen.unwrap(),
&super::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384);
}
}