weft_licence_wire/
request.rs1use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
17use sha2::{Digest, Sha256};
18
19use crate::{hex_decode_exact, hex_encode};
20
21pub const REQUEST_DOMAIN: &str = "weft-licence-v1/request";
23pub const MAX_TS_MS: u64 = 4_102_444_800_000;
25pub const CLOCK_FLOOR_SECS: u64 = 1_780_000_000;
28pub const REQUEST_WINDOW_MS: u64 = 120_000;
30pub const MAX_NONCES: usize = 4096;
32
33pub fn signing_string(
35 method: &str,
36 target: &str,
37 node: &str,
38 audience: &str,
39 ts_ms: u64,
40 nonce: &str,
41 body: &[u8],
42) -> String {
43 format!(
44 "{REQUEST_DOMAIN}\n{method}\n{target}\n{node}\n{audience}\n{ts_ms}\n{nonce}\n{}",
45 hex_encode(&Sha256::digest(body))
46 )
47}
48
49pub fn valid_nonce(n: &str) -> bool {
51 (16..=64).contains(&n.len()) && n.bytes().all(|b| b.is_ascii_alphanumeric())
52}
53
54pub fn valid_ts(ts_ms: u64) -> bool {
56 ts_ms <= MAX_TS_MS
57}
58
59pub fn within_window(ts_ms: u64, now_ms: u64, window_ms: u64) -> bool {
61 ts_ms.abs_diff(now_ms) <= window_ms
62}
63
64#[allow(clippy::too_many_arguments)]
66pub fn sign(
67 key: &SigningKey,
68 node: &str,
69 audience: &str,
70 method: &str,
71 target: &str,
72 body: &[u8],
73 ts_ms: u64,
74 nonce: &str,
75) -> String {
76 let s = signing_string(method, target, node, audience, ts_ms, nonce, body);
77 hex_encode(&key.sign(s.as_bytes()).to_bytes())
78}
79
80#[allow(clippy::too_many_arguments)]
83pub fn verify_signature(
84 steward_pubkey: &[u8; 32],
85 node: &str,
86 audience: &str,
87 method: &str,
88 target: &str,
89 body: &[u8],
90 ts_ms: u64,
91 nonce: &str,
92 sig_hex: &str,
93) -> bool {
94 let (Some(sig), Ok(vk)) =
95 (hex_decode_exact::<64>(sig_hex), VerifyingKey::from_bytes(steward_pubkey))
96 else {
97 return false;
98 };
99 let s = signing_string(method, target, node, audience, ts_ms, nonce, body);
100 vk.verify_strict(s.as_bytes(), &Signature::from_bytes(&sig)).is_ok()
101}