rustls-sni-resolver 0.0.1

SNI-keyed certificate map implementing rustls::ResolvesServerCert, designed for hot-reload via ArcSwap.
Documentation
  • Coverage
  • 50%
    5 out of 10 items documented0 out of 7 items with examples
  • Size
  • Source code size: 28.78 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 418.29 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 20s Average build duration of successful builds.
  • all releases: 20s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • canmi21/vane
    84 4 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • canmi21

Rustls SNI Resolver

A minimal ResolvesServerCert implementation backed by { by_sni: HashMap<String, Arc<E>>, default: Option<Arc<E>> }, with the whole struct designed to live behind an Arc<ArcSwap<_>> so a config reload is one atomic pointer swap.

E is generic over the [EntryKey] trait, so callers can attach their own per-cert state (expiry timestamps, OCSP staple handles, ACME order IDs, …) without forking the resolver.

rustls's built-in ResolvesServerCertUsingSni returns None on unmatched SNI with no built-in fallback hook — every operator-facing TLS service ends up writing this small "with a default" variant by hand.

Example

use std::sync::Arc;
use arc_swap::ArcSwap;
use rustls_sni_resolver::{CertStore, EntryKey, Resolver};

#[derive(Debug)]
struct MyEntry {
    key: Arc<rustls::sign::CertifiedKey>,
    not_after: std::time::SystemTime,
}

impl EntryKey for MyEntry {
    fn key(&self) -> Arc<rustls::sign::CertifiedKey> {
        Arc::clone(&self.key)
    }
}

# fn run(api_entry: Arc<MyEntry>, default_entry: Arc<MyEntry>) {
let mut store: CertStore<MyEntry> = CertStore::new();
store.by_sni.insert("api.example.com".into(), api_entry);
store.default = Some(default_entry);

let store = Arc::new(ArcSwap::from_pointee(store));
let resolver: Arc<dyn rustls::server::ResolvesServerCert> =
    Arc::new(Resolver::new(store.clone()));

// Later, on reload, swap atomically:
let mut fresh: CertStore<MyEntry> = CertStore::new();
// ... populate fresh ...
store.store(Arc::new(fresh));
# }

Lookup semantics

CertStore::lookup(Option<&str>) returns:

  • the entry under the matching SNI key, if one exists;
  • otherwise the default entry, if one is set;
  • otherwise None.

Because rustls already ASCII-lowercases the server_name per RFC 6066 § 3, populators should also store by_sni keys in lowercase. This crate does not lowercase on insert — callers own that invariant (typical populators read keys from configuration that has already been normalized at parse time).

License

Released under the MIT License © 2026 Canmi