// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// TLS 1.2's cipher suites, as a table: in 1.2 a suite names the key exchange, the certificate and
// the record protection all at once, so another suite is another row and nothing else.
///|
/// How the two ends agree on a premaster secret.
pub(all) enum Exchange {
Ecdhe
Dhe
/// The client encrypts it to the server's RSA key. No forward secrecy.
Rsa
} derive(Eq, Debug)
///|
pub extend Exchange with Eq::{equal, not_equal}
///|
pub extend Exchange with Debug::{to_repr}
///|
/// The kind of certificate a suite is for. EdDSA certificates go with `Ecdsa`, as RFC 8422 §5.1 has.
pub(all) enum Auth {
Rsa
Ecdsa
} derive(Eq, Debug)
///|
pub extend Auth with Eq::{equal, not_equal}
///|
pub extend Auth with Debug::{to_repr}
///|
/// The MAC of a CBC suite.
pub(all) enum Mac {
Sha1
Sha256
Sha384
} derive(Eq, Debug)
///|
pub extend Mac with Eq::{equal, not_equal}
///|
pub extend Mac with Debug::{to_repr}
///|
/// The hash a MAC is keyed over.
pub fn Mac::hasher(self : Mac) -> () -> &@spec.Hash {
match self {
Sha1 =>
() => {
let h : &@spec.Hash = @sha1.Hasher::new()
h
}
Sha256 => @keys.Digest::Sha256.hasher()
Sha384 => @keys.Digest::Sha384.hasher()
}
}
///|
/// The MAC's length, which is also its key's.
pub fn Mac::size(self : Mac) -> Int32 {
match self {
Sha1 => 20
Sha256 => 32
Sha384 => 48
}
}
///|
/// How a suite protects its records.
pub(all) enum Bulk {
Aead(Suite)
/// AES in CCM mode, with a tag of 16 octets or 8 (RFC 6655).
Ccm(key~ : Int32, tag~ : Int32)
/// AES in CBC mode with HMAC (RFC 5246 §6.2.3.2).
Cbc(key~ : Int32, mac~ : Mac)
} derive(Eq, Debug)
///|
pub extend Bulk with Eq::{equal, not_equal}
///|
pub extend Bulk with Debug::{to_repr}
///|
/// A row of the table.
pub(all) struct Suite12 {
code : Int32
exchange : Exchange
auth : Auth
bulk : Bulk
/// The hash of the PRF and the transcript.
digest : @keys.Digest
} derive(Eq, Debug)
///|
pub extend Suite12 with Eq::{equal, not_equal}
///|
pub extend Suite12 with Debug::{to_repr}
///|
/// The ECDHE suites with an AEAD, which is what is on by default: forward secrecy, and no MAC to
/// time.
pub let suites12 : Array[Suite12] = [
row(0xcca9, Ecdhe, Ecdsa, Aead(ChaCha20Poly1305), Sha256),
row(0xcca8, Ecdhe, Rsa, Aead(ChaCha20Poly1305), Sha256),
row(0xc02b, Ecdhe, Ecdsa, Aead(Aes128Gcm), Sha256),
row(0xc02f, Ecdhe, Rsa, Aead(Aes128Gcm), Sha256),
row(0xc02c, Ecdhe, Ecdsa, Aead(Aes256Gcm), Sha384),
row(0xc030, Ecdhe, Rsa, Aead(Aes256Gcm), Sha384),
]
///|
/// Every suite this library speaks, strongest first. Asking for these is asking to talk to whatever
/// is out there; `suites12` is the default for good reason.
pub let all12 : Array[Suite12] = [
..suites12,
row(0xc0ac, Ecdhe, Ecdsa, Ccm(key=16, tag=16), Sha256),
row(0xc0ad, Ecdhe, Ecdsa, Ccm(key=32, tag=16), Sha256),
row(0xc0ae, Ecdhe, Ecdsa, Ccm(key=16, tag=8), Sha256),
row(0xc0af, Ecdhe, Ecdsa, Ccm(key=32, tag=8), Sha256),
row(0xccaa, Dhe, Rsa, Aead(ChaCha20Poly1305), Sha256),
row(0x009e, Dhe, Rsa, Aead(Aes128Gcm), Sha256),
row(0x009f, Dhe, Rsa, Aead(Aes256Gcm), Sha384),
row(0xc09e, Dhe, Rsa, Ccm(key=16, tag=16), Sha256),
row(0xc09f, Dhe, Rsa, Ccm(key=32, tag=16), Sha256),
row(0xc0a2, Dhe, Rsa, Ccm(key=16, tag=8), Sha256),
row(0xc0a3, Dhe, Rsa, Ccm(key=32, tag=8), Sha256),
row(0xc023, Ecdhe, Ecdsa, Cbc(key=16, mac=Sha256), Sha256),
row(0xc027, Ecdhe, Rsa, Cbc(key=16, mac=Sha256), Sha256),
row(0xc024, Ecdhe, Ecdsa, Cbc(key=32, mac=Sha384), Sha384),
row(0xc028, Ecdhe, Rsa, Cbc(key=32, mac=Sha384), Sha384),
row(0xc009, Ecdhe, Ecdsa, Cbc(key=16, mac=Sha1), Sha256),
row(0xc013, Ecdhe, Rsa, Cbc(key=16, mac=Sha1), Sha256),
row(0xc00a, Ecdhe, Ecdsa, Cbc(key=32, mac=Sha1), Sha256),
row(0xc014, Ecdhe, Rsa, Cbc(key=32, mac=Sha1), Sha256),
row(0x0067, Dhe, Rsa, Cbc(key=16, mac=Sha256), Sha256),
row(0x006b, Dhe, Rsa, Cbc(key=32, mac=Sha256), Sha256),
row(0x0033, Dhe, Rsa, Cbc(key=16, mac=Sha1), Sha256),
row(0x0039, Dhe, Rsa, Cbc(key=32, mac=Sha1), Sha256),
row(0x009c, Rsa, Rsa, Aead(Aes128Gcm), Sha256),
row(0x009d, Rsa, Rsa, Aead(Aes256Gcm), Sha384),
row(0xc09c, Rsa, Rsa, Ccm(key=16, tag=16), Sha256),
row(0xc09d, Rsa, Rsa, Ccm(key=32, tag=16), Sha256),
row(0xc0a0, Rsa, Rsa, Ccm(key=16, tag=8), Sha256),
row(0xc0a1, Rsa, Rsa, Ccm(key=32, tag=8), Sha256),
row(0x003c, Rsa, Rsa, Cbc(key=16, mac=Sha256), Sha256),
row(0x003d, Rsa, Rsa, Cbc(key=32, mac=Sha256), Sha256),
row(0x002f, Rsa, Rsa, Cbc(key=16, mac=Sha1), Sha256),
row(0x0035, Rsa, Rsa, Cbc(key=32, mac=Sha1), Sha256),
]
///|
fn row(
code : Int32,
exchange : Exchange,
auth : Auth,
bulk : Bulk,
digest : @keys.Digest,
) -> Suite12 {
{ code, exchange, auth, bulk, digest, }
}
///|
/// The suite's name in the IANA registry, which is what `openssl ciphers -stdname` prints.
pub fn Suite12::name(self : Suite12) -> String {
let exchange = match (self.exchange, self.auth) {
(Ecdhe, Ecdsa) => "ECDHE_ECDSA"
(Ecdhe, Rsa) => "ECDHE_RSA"
(Dhe, _) => "DHE_RSA"
(Rsa, _) => "RSA"
}
let bulk = match self.bulk {
Aead(Aes128Gcm) => "AES_128_GCM_SHA256"
Aead(Aes256Gcm) => "AES_256_GCM_SHA384"
Aead(ChaCha20Poly1305) => "CHACHA20_POLY1305_SHA256"
Ccm(key~, tag~) => {
let short = if tag == 8 { "_8" } else { "" }
"AES_\{key * 8}_CCM\{short}"
}
Cbc(key~, mac~) => {
let hash = match mac {
Sha1 => "SHA"
Sha256 => "SHA256"
Sha384 => "SHA384"
}
"AES_\{key * 8}_CBC_\{hash}"
}
}
"TLS_\{exchange}_WITH_\{bulk}"
}
///|
/// The suite a code names, `None` for one this library does not speak.
pub fn Suite12::of(code : Int32) -> Suite12? {
all12.iter().find_first(s => s.code == code)
}
///|
/// The key length.
pub fn Suite12::key_size(self : Suite12) -> Int32 {
match self.bulk {
Aead(suite) => suite.key_size()
Ccm(key~, ..) | Cbc(key~, ..) => key
}
}
///|
/// The MAC key length: nothing for an AEAD.
pub fn Suite12::mac_size(self : Suite12) -> Int32 {
match self.bulk {
Cbc(mac~, ..) => mac.size()
_ => 0
}
}
///|
/// The fixed part of the nonce the key block gives each direction: GCM's and CCM's four-octet salt,
/// ChaCha20-Poly1305's twelve, and nothing for CBC, whose IV goes out with each record.
pub fn Suite12::iv_size(self : Suite12) -> Int32 {
match self.bulk {
Aead(ChaCha20Poly1305) => 12
Aead(_) | Ccm(..) => 4
Cbc(..) => 0
}
}
///|
/// Whether records under this suite are CBC, which is what encrypt-then-MAC applies to.
pub fn Suite12::is_cbc(self : Suite12) -> Bool {
self.bulk is Cbc(..)
}