use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use reqwest::Url;
use serde_json::{Map, Value};
use sha2::{Digest, Sha256};
use super::key::Key;
use super::{lock, now};
use crate::error::Result;
type Origin = (String, Option<String>, Option<u16>);
#[derive(Default)]
pub(crate) struct Proofs {
nonces: Mutex<HashMap<Origin, String>>,
keys: Mutex<HashMap<String, Arc<Key>>>,
}
fn origin(url: &str) -> Origin {
match Url::parse(url) {
Ok(uri) => (
uri.scheme().to_string(),
uri.host_str().map(str::to_string),
uri.port_or_known_default(),
),
Err(_) => (String::new(), None, None),
}
}
impl Proofs {
pub(crate) fn sign(
&self,
pem: &str,
url: &str,
token: Option<&str>,
verb: &str,
) -> Result<String> {
let cached = lock(&self.keys).get(pem).cloned();
let key = match cached {
Some(key) => key,
None => {
let key = Arc::new(Key::new(pem)?);
lock(&self.keys).insert(pem.to_string(), key.clone());
key
}
};
let htu = url.split(['?', '#']).next().unwrap_or(url);
let mut claims = Map::new();
claims.insert("jti".into(), uuid::Uuid::new_v4().to_string().into());
claims.insert("htm".into(), verb.into());
claims.insert("htu".into(), htu.into());
claims.insert("iat".into(), now().into());
if let Some(token) = token {
let ath = URL_SAFE_NO_PAD.encode(Sha256::digest(token.as_bytes()));
claims.insert("ath".into(), ath.into());
}
if let Some(nonce) = lock(&self.nonces).get(&origin(url)) {
claims.insert("nonce".into(), nonce.clone().into());
}
let mut header = Map::new();
header.insert("typ".into(), "dpop+jwt".into());
header.insert("jwk".into(), key.jwk().unwrap_or(Value::Null));
key.jwt(&Value::Object(claims), key.algorithm(&[]), header)
}
pub(crate) fn remember(&self, url: &str, nonce: Option<&str>) {
if let Some(nonce) = nonce {
lock(&self.nonces).insert(origin(url), nonce.to_string());
}
}
}