#![forbid(unsafe_code)]
pub mod canonical;
pub mod keys;
pub mod message;
pub mod replay;
pub mod tier1_hmac;
pub mod tier2_authz;
pub mod tier3_aead;
#[cfg(feature = "axum")]
pub mod axum_adapter;
#[cfg(feature = "serde")]
pub mod vectors;
use time::OffsetDateTime;
pub use crate::message::{SesameError, SesameHeaders};
use crate::keys::KeyProvider;
use crate::message::{hex_decode, PROTOCOL_VERSION};
use crate::replay::ReplayCache;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Tier {
Zero = 0,
One = 1,
Two = 2,
Three = 3,
}
impl Tier {
pub fn from_u8(n: u8) -> Tier {
match n {
0 => Tier::Zero,
1 => Tier::One,
2 => Tier::Two,
_ => Tier::Three,
}
}
pub fn level(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone)]
pub struct SesameConfig {
pub replay_window_secs: i64,
}
impl Default for SesameConfig {
fn default() -> Self {
SesameConfig {
replay_window_secs: 300,
}
}
}
pub struct VerifiedRequest {
pub plaintext: Vec<u8>,
pub key_id: String,
pub scope_channel: Option<String>,
pub achieved_tier: Tier,
}
pub struct SignedResponse {
pub headers: Vec<(&'static str, String)>,
pub body: Vec<u8>,
pub content_type: &'static str,
}
pub struct RequestContext<'a> {
pub method: &'a str,
pub path: &'a str,
pub target_channel: Option<&'a str>,
}
#[allow(clippy::too_many_arguments)]
pub fn verify_request(
cfg: &SesameConfig,
provider: &dyn KeyProvider,
replay: &dyn ReplayCache,
ctx: &RequestContext<'_>,
headers: &SesameHeaders,
raw_body: &[u8],
now: OffsetDateTime,
min_tier: Tier,
) -> Result<VerifiedRequest, SesameError> {
if headers.is_absent() {
if min_tier == Tier::Zero {
return Ok(VerifiedRequest {
plaintext: raw_body.to_vec(),
key_id: String::new(),
scope_channel: None,
achieved_tier: Tier::Zero,
});
}
return Err(SesameError::MissingHeaders);
}
let t1 = headers.require_tier1()?;
if t1.version != PROTOCOL_VERSION {
return Err(SesameError::InvalidVersion);
}
tier1_hmac::check_freshness(t1.timestamp, now, cfg.replay_window_secs)?;
if provider.is_revoked(t1.key_id) {
return Err(SesameError::KeyRevoked);
}
let signing_keys: Vec<Vec<u8>> = provider
.signing_keys(t1.key_id)
.into_iter()
.map(|k| k.0)
.collect();
if signing_keys.is_empty() {
return Err(SesameError::UnknownKey);
}
let body_hash = canonical::body_hash_hex(raw_body);
let scope_for_sig = headers.scope.as_deref();
let canonical = canonical::request_canonical(
ctx.method,
ctx.path,
t1.timestamp,
t1.nonce,
&body_hash,
scope_for_sig,
);
tier1_hmac::verify_any(&signing_keys, &canonical, t1.signature)?;
if !replay.check_and_remember(t1.key_id, t1.nonce, now.unix_timestamp()) {
return Err(SesameError::ReplayDetected);
}
let mut achieved = Tier::One;
let mut scope_channel = None;
if let Some(scope) = headers.scope.as_deref() {
let channel = tier2_authz::authorize(provider, t1.key_id, scope, ctx.target_channel)?;
scope_channel = Some(channel);
achieved = Tier::Two;
} else if min_tier >= Tier::Two {
return Err(SesameError::ScopeDenied);
}
let plaintext = if headers.encrypted {
let enc_key_id = headers
.enc_key_id
.as_deref()
.ok_or(SesameError::DecryptFailed)?;
let iv_hex = headers.iv.as_deref().ok_or(SesameError::DecryptFailed)?;
let iv_bytes = hex_decode(iv_hex).ok_or(SesameError::DecryptFailed)?;
if iv_bytes.len() != tier3_aead::IV_LEN {
return Err(SesameError::DecryptFailed);
}
let mut iv = [0u8; tier3_aead::IV_LEN];
iv.copy_from_slice(&iv_bytes);
let aead = provider
.aead_key(enc_key_id)
.ok_or(SesameError::DecryptFailed)?;
let aad = tier3_aead::aad_for_headers(
t1.version,
t1.key_id,
t1.timestamp,
t1.nonce,
scope_for_sig,
);
let pt = tier3_aead::open(&aead.0, &iv, &aad, raw_body)?;
achieved = Tier::Three;
pt
} else if min_tier >= Tier::Three {
return Err(SesameError::DecryptFailed);
} else {
raw_body.to_vec()
};
if achieved < min_tier {
return Err(SesameError::MissingHeaders);
}
Ok(VerifiedRequest {
plaintext,
key_id: t1.key_id.to_string(),
scope_channel,
achieved_tier: achieved,
})
}
pub struct ResponseParams<'a> {
pub signing_key_id: &'a str,
pub correlation: &'a str,
pub scope: Option<&'a str>,
pub tier: Tier,
pub enc_key_id: Option<&'a str>,
}
#[cfg(feature = "rng")]
pub fn sign_response(
cfg: &SesameConfig,
provider: &dyn KeyProvider,
params: &ResponseParams<'_>,
plaintext_xml: &[u8],
now: OffsetDateTime,
) -> Result<SignedResponse, SesameError> {
use crate::message::hex_encode;
use time::format_description::well_known::Rfc3339;
let _ = cfg; let signing_key = provider
.primary_signing_key(params.signing_key_id)
.ok_or(SesameError::UnknownKey)?;
let timestamp = now
.format(&Rfc3339)
.map_err(|_| SesameError::ExpiredTimestamp)?;
let nonce = hex_encode(&random_128());
let mut headers: Vec<(&'static str, String)> = vec![
(message::H_VERSION, PROTOCOL_VERSION.to_string()),
(message::H_KEY_ID, params.signing_key_id.to_string()),
(message::H_TIMESTAMP, timestamp.clone()),
(message::H_NONCE, nonce.clone()),
];
if let Some(scope) = params.scope {
headers.push((message::H_SCOPE, scope.to_string()));
}
let (body, content_type): (Vec<u8>, &'static str) = if params.tier >= Tier::Three {
let enc_key_id = params.enc_key_id.ok_or(SesameError::DecryptFailed)?;
let aead = provider
.aead_key(enc_key_id)
.ok_or(SesameError::DecryptFailed)?;
let iv = tier3_aead::random_iv();
let aad = tier3_aead::aad_for_headers(
PROTOCOL_VERSION,
params.signing_key_id,
×tamp,
&nonce,
params.scope,
);
let ct = tier3_aead::seal(&aead.0, &iv, &aad, plaintext_xml)?;
headers.push((message::H_ENCRYPTED, "true".to_string()));
headers.push((message::H_ENC_KEY_ID, enc_key_id.to_string()));
headers.push((message::H_IV, hex_encode(&iv)));
(ct, "application/octet-stream")
} else {
(plaintext_xml.to_vec(), "application/xml")
};
let body_hash = canonical::body_hash_hex(&body);
let canonical = canonical::response_canonical(
params.correlation,
×tamp,
&nonce,
&body_hash,
params.scope,
);
let signature = tier1_hmac::sign(&signing_key.0, &canonical);
headers.push((message::H_SIGNATURE, signature));
Ok(SignedResponse {
headers,
body,
content_type,
})
}
#[cfg(feature = "rng")]
fn random_128() -> [u8; 16] {
use rand::rngs::OsRng;
use rand::RngCore;
let mut b = [0u8; 16];
OsRng.fill_bytes(&mut b);
b
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keys::{AeadKey, ChannelScope, HmacKey, StaticKeyProvider};
use crate::message::hex_encode;
use crate::replay::InMemoryReplayCache;
use crate::tier3_aead::KEY_LEN;
use time::format_description::well_known::Rfc3339;
const XML: &[u8] = b"<?xml version=\"1.0\"?><SignalProcessingEvent/>";
fn now() -> OffsetDateTime {
OffsetDateTime::parse("2026-02-24T18:00:00Z", &Rfc3339).unwrap()
}
fn provider() -> StaticKeyProvider {
StaticKeyProvider::new()
.with_signing_key(
"sas-east-01",
HmacKey(b"client-secret".to_vec()),
ChannelScope::list(["SportsFeed-East"]),
)
.with_signing_key(
"pois-primary",
HmacKey(b"pois-secret".to_vec()),
ChannelScope::all(),
)
.with_aead_key("enc-sportsfeed-2026q1", AeadKey([0x42; KEY_LEN]))
}
fn make_request(tier: Tier, encrypt_with: Option<&str>) -> (SesameHeaders, Vec<u8>) {
let p = provider();
let key = p.primary_signing_key("sas-east-01").unwrap().0;
let timestamp = "2026-02-24T18:00:00Z";
let nonce = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6";
let scope = if tier >= Tier::Two {
Some("channel=SportsFeed-East")
} else {
None
};
let (body, enc_headers) = if tier >= Tier::Three {
let enc_key_id = encrypt_with.unwrap_or("enc-sportsfeed-2026q1");
let aead = p.aead_key(enc_key_id).unwrap();
let iv = [0u8; tier3_aead::IV_LEN];
let aad = tier3_aead::aad_for_headers(
PROTOCOL_VERSION,
"sas-east-01",
timestamp,
nonce,
scope,
);
let ct = tier3_aead::seal(&aead.0, &iv, &aad, XML).unwrap();
(ct, Some((enc_key_id.to_string(), hex_encode(&iv))))
} else {
(XML.to_vec(), None)
};
let body_hash = canonical::body_hash_hex(&body);
let canonical = canonical::request_canonical(
"POST",
"/esam?channel=SportsFeed-East",
timestamp,
nonce,
&body_hash,
scope,
);
let signature = tier1_hmac::sign(&key, &canonical);
let headers = SesameHeaders {
version: Some(PROTOCOL_VERSION.to_string()),
key_id: Some("sas-east-01".to_string()),
timestamp: Some(timestamp.to_string()),
nonce: Some(nonce.to_string()),
signature: Some(signature),
scope: scope.map(|s| s.to_string()),
encrypted: enc_headers.is_some(),
enc_key_id: enc_headers.as_ref().map(|(k, _)| k.clone()),
iv: enc_headers.as_ref().map(|(_, iv)| iv.clone()),
};
(headers, body)
}
fn ctx() -> RequestContext<'static> {
RequestContext {
method: "POST",
path: "/esam?channel=SportsFeed-East",
target_channel: Some("SportsFeed-East"),
}
}
#[test]
fn tier1_roundtrip() {
let (h, body) = make_request(Tier::One, None);
let cache = InMemoryReplayCache::new(300);
let v = verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::One,
)
.expect("tier1 must verify");
assert_eq!(v.plaintext, XML);
assert_eq!(v.achieved_tier, Tier::One);
}
#[test]
fn tier2_roundtrip() {
let (h, body) = make_request(Tier::Two, None);
let cache = InMemoryReplayCache::new(300);
let v = verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::Two,
)
.expect("tier2 must verify");
assert_eq!(v.scope_channel.as_deref(), Some("SportsFeed-East"));
assert_eq!(v.achieved_tier, Tier::Two);
}
#[test]
fn tier3_roundtrip_decrypts_to_original_xml() {
let (h, body) = make_request(Tier::Three, None);
let cache = InMemoryReplayCache::new(300);
let v = verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::Three,
)
.expect("tier3 must verify");
assert_eq!(v.plaintext, XML);
assert_eq!(v.achieved_tier, Tier::Three);
}
#[test]
fn tier0_passthrough_when_allowed() {
let cache = InMemoryReplayCache::new(300);
let h = SesameHeaders::default();
let v = verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
XML,
now(),
Tier::Zero,
)
.expect("tier0 passthrough");
assert_eq!(v.achieved_tier, Tier::Zero);
assert_eq!(v.plaintext, XML);
}
#[test]
fn tier0_rejected_when_tier1_required() {
let cache = InMemoryReplayCache::new(300);
let h = SesameHeaders::default();
assert_eq!(
verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
XML,
now(),
Tier::One
)
.err(),
Some(SesameError::MissingHeaders)
);
}
#[test]
fn tampered_body_rejected() {
let (h, mut body) = make_request(Tier::One, None);
body.extend_from_slice(b"<!-- injected -->");
let cache = InMemoryReplayCache::new(300);
assert_eq!(
verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::One
)
.err(),
Some(SesameError::SignatureMismatch)
);
}
#[test]
fn tampered_signed_header_rejected() {
let (mut h, body) = make_request(Tier::One, None);
h.nonce = Some("ffffffffffffffffffffffffffffffff".to_string()); let cache = InMemoryReplayCache::new(300);
assert_eq!(
verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::One
)
.err(),
Some(SesameError::SignatureMismatch)
);
}
#[test]
fn replayed_nonce_rejected() {
let (h, body) = make_request(Tier::One, None);
let cache = InMemoryReplayCache::new(300);
assert!(verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::One
)
.is_ok());
assert_eq!(
verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::One
)
.err(),
Some(SesameError::ReplayDetected)
);
}
#[test]
fn stale_timestamp_rejected() {
let (h, body) = make_request(Tier::One, None);
let cache = InMemoryReplayCache::new(300);
let later = OffsetDateTime::parse("2026-02-24T18:10:00Z", &Rfc3339).unwrap(); assert_eq!(
verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
later,
Tier::One
)
.err(),
Some(SesameError::ExpiredTimestamp)
);
}
#[test]
fn unknown_key_rejected() {
let (mut h, body) = make_request(Tier::One, None);
h.key_id = Some("ghost".to_string());
let cache = InMemoryReplayCache::new(300);
assert_eq!(
verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::One
)
.err(),
Some(SesameError::UnknownKey)
);
}
#[test]
fn unauthorized_channel_rejected() {
let p = provider();
let key = p.primary_signing_key("sas-east-01").unwrap().0;
let timestamp = "2026-02-24T18:00:00Z";
let nonce = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6";
let scope = "channel=PremiumFeed";
let body_hash = canonical::body_hash_hex(XML);
let canonical = canonical::request_canonical(
"POST",
"/esam?channel=PremiumFeed",
timestamp,
nonce,
&body_hash,
Some(scope),
);
let signature = tier1_hmac::sign(&key, &canonical);
let h = SesameHeaders {
version: Some(PROTOCOL_VERSION.to_string()),
key_id: Some("sas-east-01".to_string()),
timestamp: Some(timestamp.to_string()),
nonce: Some(nonce.to_string()),
signature: Some(signature),
scope: Some(scope.to_string()),
..Default::default()
};
let ctx = RequestContext {
method: "POST",
path: "/esam?channel=PremiumFeed",
target_channel: Some("PremiumFeed"),
};
let cache = InMemoryReplayCache::new(300);
assert_eq!(
verify_request(
&SesameConfig::default(),
&p,
&cache,
&ctx,
&h,
XML,
now(),
Tier::Two
)
.err(),
Some(SesameError::ScopeDenied)
);
}
#[test]
fn truncated_gcm_tag_rejected() {
let (h, mut body) = make_request(Tier::Three, None);
body.truncate(body.len() - 1); let cache = InMemoryReplayCache::new(300);
let err = verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::Three,
)
.err();
assert_eq!(err, Some(SesameError::SignatureMismatch)); }
#[test]
fn wrong_version_rejected() {
let (mut h, body) = make_request(Tier::One, None);
h.version = Some("2.0".to_string());
let cache = InMemoryReplayCache::new(300);
assert_eq!(
verify_request(
&SesameConfig::default(),
&provider(),
&cache,
&ctx(),
&h,
&body,
now(),
Tier::One
)
.err(),
Some(SesameError::InvalidVersion)
);
}
#[test]
fn response_sign_and_client_verify_roundtrip() {
let p = provider();
let params = ResponseParams {
signing_key_id: "pois-primary",
correlation: "sig-20260224-001",
scope: Some("channel=SportsFeed-East"),
tier: Tier::Two,
enc_key_id: None,
};
let resp = sign_response(&SesameConfig::default(), &p, ¶ms, XML, now()).unwrap();
let get = |name: &str| {
resp.headers
.iter()
.find(|(k, _)| *k == name)
.map(|(_, v)| v.clone())
};
let ts = get(message::H_TIMESTAMP).unwrap();
let nonce = get(message::H_NONCE).unwrap();
let sig = get(message::H_SIGNATURE).unwrap();
let body_hash = canonical::body_hash_hex(&resp.body);
let canonical = canonical::response_canonical(
"sig-20260224-001",
&ts,
&nonce,
&body_hash,
Some("channel=SportsFeed-East"),
);
let key = p.primary_signing_key("pois-primary").unwrap().0;
assert!(tier1_hmac::verify(&key, &canonical, &sig).is_ok());
}
#[test]
fn forged_response_detected() {
let p = provider();
let params = ResponseParams {
signing_key_id: "pois-primary",
correlation: "sig-1",
scope: None,
tier: Tier::One,
enc_key_id: None,
};
let resp = sign_response(&SesameConfig::default(), &p, ¶ms, XML, now()).unwrap();
let get = |name: &str| {
resp.headers
.iter()
.find(|(k, _)| *k == name)
.map(|(_, v)| v.clone())
};
let ts = get(message::H_TIMESTAMP).unwrap();
let nonce = get(message::H_NONCE).unwrap();
let sig = get(message::H_SIGNATURE).unwrap();
let forged = b"<SignalProcessingNotification action=\"blackout\"/>";
let body_hash = canonical::body_hash_hex(forged);
let canonical = canonical::response_canonical("sig-1", &ts, &nonce, &body_hash, None);
let key = p.primary_signing_key("pois-primary").unwrap().0;
assert!(tier1_hmac::verify(&key, &canonical, &sig).is_err());
}
#[test]
fn response_iv_differs_from_request_iv() {
let (req_headers, _req_body) = make_request(Tier::Three, None);
let req_iv = req_headers.iv.clone().unwrap();
let p = provider();
let params = ResponseParams {
signing_key_id: "pois-primary",
correlation: "sig-001",
scope: Some("channel=SportsFeed-East"),
tier: Tier::Three,
enc_key_id: Some("enc-sportsfeed-2026q1"), };
let resp = sign_response(&SesameConfig::default(), &p, ¶ms, XML, now()).unwrap();
let resp_iv = resp
.headers
.iter()
.find(|(k, _)| *k == message::H_IV)
.map(|(_, v)| v.clone())
.unwrap();
assert_ne!(
req_iv, resp_iv,
"response reused the request IV under the same EncKeyId"
);
}
#[test]
fn tier3_response_uses_fresh_iv() {
let p = provider();
let params = ResponseParams {
signing_key_id: "pois-primary",
correlation: "sig-1",
scope: Some("channel=SportsFeed-East"),
tier: Tier::Three,
enc_key_id: Some("enc-sportsfeed-2026q1"),
};
let r1 = sign_response(&SesameConfig::default(), &p, ¶ms, XML, now()).unwrap();
let r2 = sign_response(&SesameConfig::default(), &p, ¶ms, XML, now()).unwrap();
let iv = |r: &SignedResponse| {
r.headers
.iter()
.find(|(k, _)| *k == message::H_IV)
.map(|(_, v)| v.clone())
.unwrap()
};
assert_ne!(iv(&r1), iv(&r2), "each response MUST use a fresh GCM IV");
assert_eq!(r1.content_type, "application/octet-stream");
}
}