1use std::time::{SystemTime, UNIX_EPOCH};
2
3use base64::engine::general_purpose::URL_SAFE_NO_PAD;
4use base64::Engine as _;
5use ed25519_dalek::{Signature, Verifier, VerifyingKey};
6use sha2::{Digest, Sha256};
7
8use crate::TrellisClientError;
9
10pub(crate) fn sha256(bytes: &[u8]) -> [u8; 32] {
11 let mut hasher = Sha256::new();
12 hasher.update(bytes);
13 let digest = hasher.finalize();
14 digest.into()
15}
16
17pub(crate) fn base64url_encode(bytes: &[u8]) -> String {
18 URL_SAFE_NO_PAD.encode(bytes)
19}
20
21pub(crate) fn base64url_decode(value: &str) -> Result<Vec<u8>, base64::DecodeError> {
22 URL_SAFE_NO_PAD.decode(value)
23}
24
25pub(crate) fn now_iat_seconds() -> u64 {
26 SystemTime::now()
27 .duration_since(UNIX_EPOCH)
28 .unwrap_or_default()
29 .as_secs()
30}
31
32pub(crate) fn build_proof_input(session_key: &str, subject: &str, payload_hash: &[u8]) -> Vec<u8> {
33 let session_key = session_key.as_bytes();
34 let subject = subject.as_bytes();
35
36 let mut out =
37 Vec::with_capacity(4 + session_key.len() + 4 + subject.len() + 4 + payload_hash.len());
38 out.extend_from_slice(&(session_key.len() as u32).to_be_bytes());
39 out.extend_from_slice(session_key);
40 out.extend_from_slice(&(subject.len() as u32).to_be_bytes());
41 out.extend_from_slice(subject);
42 out.extend_from_slice(&(payload_hash.len() as u32).to_be_bytes());
43 out.extend_from_slice(payload_hash);
44 out
45}
46
47pub fn verify_proof(
49 public_session_key: &str,
50 subject: &str,
51 payload: &[u8],
52 proof_base64url: &str,
53) -> Result<bool, TrellisClientError> {
54 let public_key_bytes = base64url_decode(public_session_key)?;
55 if public_key_bytes.len() != 32 {
56 return Ok(false);
57 }
58 let mut public_key = [0u8; 32];
59 public_key.copy_from_slice(&public_key_bytes);
60
61 let signature_bytes = base64url_decode(proof_base64url)?;
62 if signature_bytes.len() != 64 {
63 return Ok(false);
64 }
65 let mut signature = [0u8; 64];
66 signature.copy_from_slice(&signature_bytes);
67
68 let payload_hash = sha256(payload);
69 let input = build_proof_input(public_session_key, subject, &payload_hash);
70 let digest = sha256(&input);
71
72 let public_key = VerifyingKey::from_bytes(&public_key)
73 .map_err(|error| TrellisClientError::RpcError(error.to_string()))?;
74 let signature = Signature::from_bytes(&signature);
75 Ok(public_key.verify(&digest, &signature).is_ok())
76}