use crate::models::{DelegationToken, Violation};
use base64ct::{Base64UrlUnpadded, Encoding};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use std::collections::HashMap;
use std::sync::RwLock;
pub const SIGNATURE_ALG_ED25519: &str = "Ed25519";
const STAGE: &str = "verify_issuer";
pub trait IssuerKeyResolver: Send + Sync {
fn resolve(&self, issuer: &str, key_id: &str) -> Result<Option<VerifyingKey>, Violation>;
}
#[derive(Default)]
pub struct PinnedIssuerKeys {
keys: RwLock<HashMap<(String, String), VerifyingKey>>,
}
impl PinnedIssuerKeys {
pub fn new() -> Self {
Self::default()
}
pub fn insert(
&self,
issuer: impl Into<String>,
key_id: impl Into<String>,
key: VerifyingKey,
) -> Result<(), Violation> {
self.keys
.write()
.map_err(|_| Violation::new(STAGE, "issuer key store lock poisoned"))?
.insert((issuer.into(), key_id.into()), key);
Ok(())
}
}
impl IssuerKeyResolver for PinnedIssuerKeys {
fn resolve(&self, issuer: &str, key_id: &str) -> Result<Option<VerifyingKey>, Violation> {
Ok(self
.keys
.read()
.map_err(|_| Violation::new(STAGE, "issuer key store lock poisoned"))?
.get(&(issuer.to_owned(), key_id.to_owned()))
.copied())
}
}
pub fn sign_token(
token: &DelegationToken,
issuer_signing_key: &SigningKey,
) -> Result<String, Violation> {
let payload = token_signing_payload(token)?;
Ok(Base64UrlUnpadded::encode_string(
&issuer_signing_key.sign(&payload).to_bytes(),
))
}
pub fn verify_token(
token: &DelegationToken,
resolver: &dyn IssuerKeyResolver,
) -> Result<(), Violation> {
let key = resolver
.resolve(&token.issuer, &token.key_id)?
.ok_or_else(|| Violation::new(STAGE, "token issuer/key_id is not trusted"))?;
let signature_bytes = Base64UrlUnpadded::decode_vec(&token.signature)
.map_err(|_| Violation::new(STAGE, "token signature must be base64url-no-pad"))?;
let signature_bytes: [u8; 64] = signature_bytes
.try_into()
.map_err(|_| Violation::new(STAGE, "token signature must decode to 64 bytes"))?;
let signature = Signature::from_bytes(&signature_bytes);
key.verify(&token_signing_payload(token)?, &signature)
.map_err(|_| Violation::new(STAGE, "token signature verification failed"))
}
fn token_signing_payload(token: &DelegationToken) -> Result<Vec<u8>, Violation> {
let mut unsigned = token.clone();
unsigned.signature.clear();
let value = serde_json::to_value(unsigned)
.map_err(|error| Violation::new(STAGE, format!("token serialization failed: {error}")))?;
Ok(canonical_json(&value))
}
fn canonical_json(value: &serde_json::Value) -> Vec<u8> {
let mut output = Vec::new();
write_canonical(&mut output, value);
output
}
fn write_canonical(output: &mut Vec<u8>, value: &serde_json::Value) {
match value {
serde_json::Value::Object(map) => {
output.push(b'{');
let mut entries: Vec<_> = map.iter().collect();
entries.sort_unstable_by_key(|(key, _)| *key);
for (index, (key, value)) in entries.into_iter().enumerate() {
if index > 0 {
output.push(b',');
}
output.extend_from_slice(
serde_json::to_string(key)
.expect("JSON object keys always serialize")
.as_bytes(),
);
output.push(b':');
write_canonical(output, value);
}
output.push(b'}');
}
serde_json::Value::Array(values) => {
output.push(b'[');
for (index, value) in values.iter().enumerate() {
if index > 0 {
output.push(b',');
}
write_canonical(output, value);
}
output.push(b']');
}
primitive => output.extend_from_slice(
serde_json::to_string(primitive)
.expect("JSON values always serialize")
.as_bytes(),
),
}
}