use super::{CipherSuite, Error, NamedGroup, Result};
use crate::crypto::gcm::AesGcm;
use crate::crypto::rng;
use crate::keys::PrivateKey;
use crate::x509::{Certificate, RootStore};
#[cfg(feature = "client")]
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};
pub trait TimeSource: Send + Sync {
fn unix_millis(&self) -> u64;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SystemClock;
impl TimeSource for SystemClock {
fn unix_millis(&self) -> u64 {
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as u64).unwrap_or(0)
}
}
#[derive(Clone, Debug)]
pub struct CertifiedKey {
pub chain: Vec<Vec<u8>>,
pub key: PrivateKey,
names: Vec<String>,
}
impl CertifiedKey {
pub fn new(chain: Vec<Vec<u8>>, key: PrivateKey) -> Result<CertifiedKey> {
let leaf = chain.first().ok_or_else(|| Error::Config("empty certificate chain".into()))?;
let cert = Certificate::from_der(leaf).map_err(|e| Error::Config(format!("bad certificate: {e}")))?;
match &cert.public_key {
Some(pk) if pk.same_as(&key.public_key()) => {
let names = if cert.has_san { cert.dns_names } else { cert.subject.common_name().map(|s| vec![s.to_ascii_lowercase()]).unwrap_or_default() };
Ok(CertifiedKey { chain, key, names })
}
Some(_) => Err(Error::Config("the private key does not match the certificate".into())),
None => Err(Error::Config("unsupported certificate key type".into())),
}
}
pub fn from_pem(cert_pem: &[u8], key_pem: &[u8]) -> Result<CertifiedKey> {
let chain: Vec<Vec<u8>> = crate::pem::parse_pem(cert_pem)
.map_err(Error::Config)?
.into_iter()
.filter(|b| b.label == "CERTIFICATE")
.map(|b| b.der)
.collect();
let key = PrivateKey::from_pem(key_pem).map_err(Error::Config)?;
CertifiedKey::new(chain, key)
}
pub fn from_der(chain: Vec<Vec<u8>>, key_der: &[u8]) -> Result<CertifiedKey> {
let key = PrivateKey::from_der(key_der).map_err(Error::Config)?;
CertifiedKey::new(chain, key)
}
pub fn names(&self) -> &[String] {
&self.names
}
}
#[derive(Clone, Debug, Default)]
pub enum ClientAuth {
#[default]
None,
Optional(RootStore),
Required(RootStore),
}
pub struct Ticketer {
keys: Mutex<TicketKeys>,
rotate_ms: u64,
}
struct TicketKey {
id: [u8; 8],
aead: AesGcm,
created_ms: u64,
}
struct TicketKeys {
current: Option<TicketKey>,
previous: Option<TicketKey>,
}
impl std::fmt::Debug for Ticketer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Ticketer(rotate every {} s)", self.rotate_ms / 1000)
}
}
impl Ticketer {
pub fn new(rotate_secs: u32) -> Ticketer {
Ticketer { keys: Mutex::new(TicketKeys { current: None, previous: None }), rotate_ms: rotate_secs.max(1) as u64 * 1000 }
}
fn fresh(now: u64) -> TicketKey {
let mut k = rng::array::<32>();
let key = TicketKey { id: rng::array(), aead: AesGcm::new(&k), created_ms: now };
crate::util::zeroize(&mut k);
key
}
fn rotate(&self, keys: &mut TicketKeys, now: u64) {
let stale = match &keys.current {
None => true,
Some(k) => now.saturating_sub(k.created_ms) >= self.rotate_ms,
};
if stale {
keys.previous = keys.current.take();
keys.current = Some(Self::fresh(now));
if let Some(p) = &keys.previous {
if now.saturating_sub(p.created_ms) >= 2 * self.rotate_ms {
keys.previous = None;
}
}
}
}
pub(crate) fn seal(&self, now: u64, plain: &[u8]) -> Vec<u8> {
let mut keys = self.keys.lock().unwrap_or_else(|e| e.into_inner());
self.rotate(&mut keys, now);
let k = keys.current.as_ref().expect("current key");
let nonce = rng::array::<12>();
let mut out = Vec::with_capacity(36 + plain.len());
out.extend_from_slice(&k.id);
out.extend_from_slice(&nonce);
let start = out.len();
out.extend_from_slice(plain);
let tag = k.aead.seal(&nonce, &k.id, &mut out[start..]);
out.extend_from_slice(&tag);
out
}
pub(crate) fn open(&self, now: u64, ticket: &[u8]) -> Option<Vec<u8>> {
if ticket.len() < 36 {
return None;
}
let mut keys = self.keys.lock().unwrap_or_else(|e| e.into_inner());
self.rotate(&mut keys, now);
let (id, rest) = ticket.split_at(8);
let (nonce, rest) = rest.split_at(12);
let (ct, tag) = rest.split_at(rest.len() - 16);
for k in [&keys.current, &keys.previous].into_iter().flatten() {
if k.id == id {
let mut pt = ct.to_vec();
return k.aead.open(nonce.try_into().unwrap(), &k.id, &mut pt, tag).then_some(pt);
}
}
None
}
pub fn rotate_now(&self, now_ms: u64) {
let mut keys = self.keys.lock().unwrap_or_else(|e| e.into_inner());
keys.previous = keys.current.take();
keys.current = Some(Self::fresh(now_ms));
}
}
#[derive(Clone)]
pub struct ServerConfig {
pub(crate) default_cert: Option<Arc<CertifiedKey>>,
pub(crate) sni_certs: Vec<(String, Arc<CertifiedKey>)>,
pub alpn: Vec<Vec<u8>>,
pub client_auth: ClientAuth,
pub suites: Vec<CipherSuite>,
pub groups: Vec<NamedGroup>,
pub ticketer: Option<Arc<Ticketer>>,
pub ticket_lifetime_secs: u32,
pub ticket_count: u8,
pub strict_sni: bool,
pub max_handshake_message: usize,
pub time: Arc<dyn TimeSource>,
#[cfg(feature = "tls12")]
pub min_version: super::ProtocolVersion,
#[cfg(feature = "tls12")]
pub max_version: super::ProtocolVersion,
#[cfg(feature = "tls12")]
pub suites12: Vec<super::tls12::Tls12Suite>,
#[cfg(feature = "tls12")]
pub session_ids: Option<Arc<super::tls12::SessionIdCache>>,
#[cfg(feature = "revocation")]
pub ocsp_staple: Option<crate::revocation::StapleSource>,
#[cfg(feature = "revocation")]
pub client_revocation: Option<crate::revocation::RevocationPolicy>,
}
impl std::fmt::Debug for ServerConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ServerConfig").field("alpn", &self.alpn).field("suites", &self.suites).field("groups", &self.groups).finish()
}
}
impl ServerConfig {
pub fn require_tls13(&mut self) {
#[cfg(feature = "tls12")]
{
self.min_version = super::ProtocolVersion::Tls13;
}
}
pub fn new(cert: CertifiedKey) -> ServerConfig {
ServerConfig {
default_cert: Some(Arc::new(cert)),
sni_certs: Vec::new(),
alpn: Vec::new(),
client_auth: ClientAuth::None,
suites: CipherSuite::default_order(),
groups: NamedGroup::ALL.to_vec(),
ticketer: Some(Arc::new(Ticketer::new(6 * 3600))),
ticket_lifetime_secs: 6 * 3600,
ticket_count: 1,
strict_sni: false,
max_handshake_message: 64 * 1024,
time: Arc::new(SystemClock),
#[cfg(feature = "tls12")]
min_version: super::ProtocolVersion::Tls12,
#[cfg(feature = "tls12")]
max_version: super::ProtocolVersion::Tls13,
#[cfg(feature = "tls12")]
suites12: super::tls12::Tls12Suite::default_order(),
#[cfg(feature = "tls12")]
session_ids: Some(Arc::new(super::tls12::SessionIdCache::new(1024))),
#[cfg(feature = "revocation")]
ocsp_staple: None,
#[cfg(feature = "revocation")]
client_revocation: None,
}
}
pub fn from_pem(cert_pem: &[u8], key_pem: &[u8]) -> Result<ServerConfig> {
Ok(ServerConfig::new(CertifiedKey::from_pem(cert_pem, key_pem)?))
}
pub fn add_sni_cert(&mut self, cert: CertifiedKey) {
let cert = Arc::new(cert);
for n in cert.names() {
self.sni_certs.push((n.clone(), cert.clone()));
}
}
pub fn with_alpn(mut self, protocols: &[&[u8]]) -> ServerConfig {
self.alpn = protocols.iter().map(|p| p.to_vec()).collect();
self
}
pub(crate) fn resolve(&self, sni: Option<&str>) -> Option<Arc<CertifiedKey>> {
if let Some(name) = sni {
if let Some((_, c)) = self.sni_certs.iter().find(|(p, _)| crate::x509::dns_name_matches(p, name)) {
return Some(c.clone());
}
if self.strict_sni && !self.sni_certs.is_empty() {
return None;
}
}
self.default_cert.clone()
}
}
#[cfg(feature = "client")]
#[derive(Clone, Debug)]
pub(crate) struct StoredSession {
pub suite: CipherSuite,
pub psk: Vec<u8>,
pub ticket: Vec<u8>,
pub age_add: u32,
pub received_ms: u64,
pub lifetime_secs: u32,
pub alpn: Option<Vec<u8>>,
}
#[cfg(feature = "client")]
pub struct SessionCache {
map: Mutex<HashMap<String, VecDeque<StoredSession>>>,
#[cfg(feature = "tls12")]
map12: Mutex<HashMap<String, super::tls12::ClientSession12>>,
max_servers: usize,
per_server: usize,
}
#[cfg(feature = "client")]
impl std::fmt::Debug for SessionCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SessionCache({} servers)", self.map.lock().map(|m| m.len()).unwrap_or(0))
}
}
#[cfg(feature = "client")]
impl SessionCache {
pub fn new(max_servers: usize) -> SessionCache {
SessionCache {
map: Mutex::new(HashMap::new()),
#[cfg(feature = "tls12")]
map12: Mutex::new(HashMap::new()),
max_servers: max_servers.max(1),
per_server: 4,
}
}
#[cfg(feature = "tls12")]
pub(crate) fn put12(&self, server: &str, s: super::tls12::ClientSession12) {
let mut m = self.map12.lock().unwrap_or_else(|e| e.into_inner());
if !m.contains_key(server) && m.len() >= self.max_servers {
if let Some(k) = m.keys().next().cloned() {
m.remove(&k);
}
}
m.insert(server.to_string(), s);
}
#[cfg(feature = "tls12")]
pub(crate) fn get12(&self, server: &str, now: u64) -> Option<super::tls12::ClientSession12> {
let mut m = self.map12.lock().unwrap_or_else(|e| e.into_inner());
match m.get(server) {
Some(s) if now < s.received_ms + s.lifetime_secs as u64 * 1000 => Some(s.clone()),
Some(_) => {
m.remove(server);
None
}
None => None,
}
}
pub(crate) fn put(&self, server: &str, s: StoredSession) {
let mut m = self.map.lock().unwrap_or_else(|e| e.into_inner());
if !m.contains_key(server) && m.len() >= self.max_servers {
if let Some(k) = m.keys().next().cloned() {
m.remove(&k);
}
}
let q = m.entry(server.to_string()).or_default();
if q.len() >= self.per_server {
q.pop_front();
}
q.push_back(s);
}
pub(crate) fn take(&self, server: &str, now: u64) -> Option<StoredSession> {
let mut m = self.map.lock().unwrap_or_else(|e| e.into_inner());
let q = m.get_mut(server)?;
while let Some(s) = q.pop_back() {
if now < s.received_ms + s.lifetime_secs as u64 * 1000 {
return Some(s);
}
}
None
}
pub fn len_for(&self, server: &str) -> usize {
self.map.lock().map(|m| m.get(server).map_or(0, |q| q.len())).unwrap_or(0)
}
}
#[cfg(feature = "client")]
pub struct ClientConfig {
pub roots: RootStore,
pub alpn: Vec<Vec<u8>>,
pub suites: Vec<CipherSuite>,
pub groups: Vec<NamedGroup>,
pub client_cert: Option<CertifiedKey>,
pub sessions: Option<Arc<SessionCache>>,
pub enable_sni: bool,
pub max_handshake_message: usize,
pub time: Arc<dyn TimeSource>,
#[cfg(feature = "tls12")]
pub min_version: super::ProtocolVersion,
#[cfg(feature = "tls12")]
pub max_version: super::ProtocolVersion,
#[cfg(feature = "tls12")]
pub suites12: Vec<super::tls12::Tls12Suite>,
#[cfg(feature = "revocation")]
pub revocation: Option<crate::revocation::RevocationPolicy>,
}
#[cfg(feature = "client")]
impl std::fmt::Debug for ClientConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClientConfig").field("roots", &self.roots).field("alpn", &self.alpn).finish()
}
}
#[cfg(feature = "client")]
impl ClientConfig {
pub fn new(roots: RootStore) -> ClientConfig {
ClientConfig {
roots,
alpn: Vec::new(),
suites: CipherSuite::ALL.to_vec(),
groups: NamedGroup::ALL.to_vec(),
client_cert: None,
sessions: Some(Arc::new(SessionCache::new(256))),
enable_sni: true,
max_handshake_message: 64 * 1024,
time: Arc::new(SystemClock),
#[cfg(feature = "tls12")]
min_version: super::ProtocolVersion::Tls12,
#[cfg(feature = "tls12")]
max_version: super::ProtocolVersion::Tls13,
#[cfg(feature = "tls12")]
suites12: super::tls12::Tls12Suite::default_order(),
#[cfg(feature = "revocation")]
revocation: None,
}
}
pub fn with_alpn(mut self, protocols: &[&[u8]]) -> ClientConfig {
self.alpn = protocols.iter().map(|p| p.to_vec()).collect();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn certificate_parsed_once_and_shared_by_clones() {
let ss = crate::certgen::self_signed_with_sans(&["a.example", "*.b.example"], 30);
let ck = CertifiedKey::new(vec![ss.cert_der.clone()], ss.key.clone()).unwrap();
assert_eq!(ck.names(), ["a.example".to_string(), "*.b.example".to_string()]);
let mut cfg = ServerConfig::new(ck.clone());
cfg.add_sni_cert(ck);
assert_eq!(cfg.sni_certs.len(), 2);
assert!(cfg.resolve(Some("x.b.example")).is_some());
let h3 = cfg.clone().with_alpn(&[b"h3"]);
assert!(Arc::ptr_eq(cfg.default_cert.as_ref().unwrap(), h3.default_cert.as_ref().unwrap()));
assert!(Arc::ptr_eq(cfg.ticketer.as_ref().unwrap(), h3.ticketer.as_ref().unwrap()));
assert!(cfg.alpn.is_empty() && h3.alpn == [b"h3".to_vec()]);
}
}