diff --git a/Cargo.lock b/Cargo.lock index a86077a..ff9f175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1172,6 +1172,7 @@ dependencies = [ "parking_lot", "prometheus", "reqwest", + "ring", "rustls", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 5c0b785..2223b05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ lru = "0.6" parking_lot = "0.11" prometheus = { version = "0.12", features = [ "process" ] } reqwest = { version = "0.11", default_features = false, features = [ "json", "stream", "rustls-tls" ] } +ring = "*" rustls = "0.19" serde = "1" serde_json = "1" diff --git a/src/routes.rs b/src/routes.rs index 2c4f1f3..65a92fc 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -17,6 +17,7 @@ use log::{debug, error, info, trace, warn}; use once_cell::sync::Lazy; use prometheus::{Encoder, TextEncoder}; use reqwest::{Client, StatusCode}; +use ring::signature::ECDSA_P256_SHA256_ASN1; use serde::Deserialize; use sodiumoxide::crypto::box_::{open_precomputed, Nonce, PrecomputedKey, NONCEBYTES}; use thiserror::Error; @@ -170,6 +171,8 @@ enum TokenValidationError { TokenExpired, #[error("Invalid chapter hash.")] InvalidChapterHash, + #[error("Invalid v32 format")] + InvalidV32Format, } impl Responder for TokenValidationError { @@ -217,6 +220,35 @@ fn validate_token( Ok(()) } +fn validate_token_v32(pub_key: &[u8], token: String) -> Result<(), TokenValidationError> { + #[derive(Deserialize)] + struct Token<'a> { + expires: DateTime, + client_id: &'a str, + } + + let (token_base64, sig_base64) = token + .split_once('~') + .ok_or_else(|| TokenValidationError::InvalidV32Format)?; + let token = base64::decode_config(token_base64, BASE64_CONFIG)?; + let sig = base64::decode_config(sig_base64, BASE64_CONFIG)?; + + ring::signature::UnparsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, pub_key) + .verify(&token, &sig) + .map_err(|_| TokenValidationError::DecryptionFailure)?; + + // At this point, token has a valid signature, now to check token fields + + let token: Token = + serde_json::from_slice(&token).map_err(|_| TokenValidationError::InvalidToken)?; + + if token.expires < Utc::now() { + return Err(TokenValidationError::TokenExpired); + } + + Ok(()) +} + #[inline] fn push_headers(builder: &mut HttpResponseBuilder) -> &mut HttpResponseBuilder { builder