1use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use base64::engine::general_purpose::URL_SAFE_NO_PAD;
8use base64::Engine;
9use serde_json::{json, Value};
10
11use crate::error::Error;
12use crate::jcs::canonical_json;
13use crate::keys::{ed25519_sign, ed25519_verify};
14
15pub type ResolveKey = Arc<
16 dyn (Fn(String) -> Pin<Box<dyn Future<Output = Result<[u8; 32], Error>> + Send>>) + Send + Sync,
17>;
18
19fn b64url_encode(data: &[u8]) -> String {
20 URL_SAFE_NO_PAD.encode(data)
21}
22
23fn b64url_decode(s: &str) -> Result<Vec<u8>, Error> {
24 URL_SAFE_NO_PAD
25 .decode(s)
26 .map_err(|e| Error::Jws(format!("base64url: {e}")))
27}
28
29pub async fn sign_compact(
30 payload: &Value,
31 private_key: &[u8; 32],
32 kid: &str,
33) -> Result<String, Error> {
34 let header = json!({ "alg": "EdDSA", "typ": "JWS", "kid": kid });
35 let header_bytes = canonical_json(&header)?;
36 let payload_bytes = canonical_json(payload)?;
37 let header_b64 = b64url_encode(&header_bytes);
38 let payload_b64 = b64url_encode(&payload_bytes);
39 let signing_input = format!("{header_b64}.{payload_b64}");
40 let sig = ed25519_sign(private_key, signing_input.as_bytes());
41 Ok(format!(
42 "{header_b64}.{payload_b64}.{}",
43 b64url_encode(&sig)
44 ))
45}
46
47pub async fn verify_compact(
48 token: &str,
49 resolve_key: &ResolveKey,
50) -> Result<(Value, String), Error> {
51 let parts: Vec<&str> = token.split('.').collect();
52 if parts.len() != 3 {
53 return Err(Error::Jws("compact JWS must have 3 parts".into()));
54 }
55 let header_b64 = parts[0];
56 let payload_b64 = parts[1];
57 let sig_b64 = parts[2];
58 let header_bytes = b64url_decode(header_b64)?;
59 let header: Value = serde_json::from_slice(&header_bytes)?;
60 let alg = header.get("alg").and_then(|v| v.as_str()).unwrap_or("");
61 if alg != "EdDSA" {
62 return Err(Error::Jws(format!("unsupported JWS alg: {alg}")));
63 }
64 let kid = header
65 .get("kid")
66 .and_then(|v| v.as_str())
67 .ok_or_else(|| Error::Jws("JWS header missing kid".into()))?
68 .to_string();
69 let public_key = (resolve_key)(kid.clone()).await?;
70 let signing_input = format!("{header_b64}.{payload_b64}");
71 let sig = b64url_decode(sig_b64)?;
72 if !ed25519_verify(&public_key, signing_input.as_bytes(), &sig) {
73 return Err(Error::Jws("JWS signature verification failed".into()));
74 }
75 let payload_bytes = b64url_decode(payload_b64)?;
76 let payload: Value = serde_json::from_slice(&payload_bytes)?;
77 Ok((payload, kid))
78}