Something went wrong. Try again.
a (hacky, wip) multi-tenant oidc-terminating reverse proxy, written in anger on top of pingora
Something went wrong. Try again.
3.3 kB · 85 lines
Rust
at wip/primary
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586//! # Cookie handling//!//! cookies are stored as postcard-encoded ed25519-signed tokens, [Protocol Buffer Tokens], which//! this is inspired by. They contain session ids, _not_ the actual access token, which are stored//! in an in-memory session store.//!//! the signing key is generated on server start this does mean server restarts invalidate active//! sessions. this is not a big deal for my current usecase, and we could probably do secret//! handover or have configurable secrets should the need arise.//!//! [Protocol Buffer Tokens]: https://fly.io/blog/api-tokens-a-tedious-survey/
use std::marker::PhantomData;
use base64::Engine as _;use base64::prelude::BASE64_STANDARD;use color_eyre::Result;use serde::de::DeserializeOwned;use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]pub struct Signed<MSG> { signature: ed25519_dalek::Signature, message: Vec<u8>, _kind: PhantomData<MSG>,}impl<MSG: DeserializeOwned + Versioned> Signed<MSG> { pub fn contents(raw: &str, key: &ed25519_dalek::SigningKey) -> Result<MSG, ()> { let raw = BASE64_STANDARD.decode(raw).map_err(drop)?; // timing threats: technically, someone could try to discover the appropriate shape of our // tokens by seeing if we early-return from the postcard::from_bytes message. // this doesn't seem like a huge threat, since we're not encrypting our cookies, so the // structure is already pretty obvious
let envelope: Self = postcard::from_bytes(&raw).map_err(drop)?; key.verify(&envelope.message, &envelope.signature) .map_err(drop)?; let (version_num, msg_raw): (u64, _) = postcard::take_from_bytes(&envelope.message).map_err(drop)?; // we're past the signature part, so we know this is ours. // worst we're getting here is an attempt to check if old tokens still work, // so we don't need to be as stressed about timing sensitivity if version_num != MSG::VERSION { return Err(()); } postcard::from_bytes(msg_raw).map_err(drop) }}
impl<MSG: Serialize + Versioned> Signed<MSG> { pub fn sign(msg: MSG, key: &ed25519_dalek::SigningKey) -> Result<String, ()> { use ed25519_dalek::ed25519::signature::Signer as _;
let raw = { let raw = Vec::new(); let raw = postcard::to_extend(&MSG::VERSION, raw).map_err(drop)?; postcard::to_extend(&msg, raw).map_err(drop)? }; let signature = key.sign(&raw); let envelope = Self { signature, message: raw, _kind: Default::default(), }; let raw = postcard::to_extend(&envelope, Vec::new()).map_err(drop)?; Ok(BASE64_STANDARD.encode(raw)) }}
pub trait Versioned { const VERSION: u64;}
#[derive(Serialize, Deserialize)]pub struct CookieMessage { // NB: per rfc:draft-ietf-oauth-v2-1#7.1.3.4, since we're signing our cookies and not encrypting // them, we can't store the access token directly. instead we'll store the session id, which // we'll use to look up the token in a session store. pub session_id: u64,}impl Versioned for CookieMessage { const VERSION: u64 = 1;}
pub type CookieContents = Signed<CookieMessage>;