1use base64::engine::general_purpose::STANDARD;
6use base64::Engine;
7use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use sha2::{Digest, Sha256};
11
12use crate::canonicalize;
13use crate::expiration::{is_within_window, Window};
14
15#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
16pub struct RelayAuthority {
17 pub relay_authority_version: String,
18 pub relay: String,
19 pub trust_domain: String,
20 pub kinds: Vec<String>,
21 #[serde(skip_serializing_if = "Option::is_none", default)]
22 pub max_hop_count: Option<u32>,
23 #[serde(skip_serializing_if = "Option::is_none", default)]
24 pub rate_limit_per_minute: Option<u32>,
25 pub valid_from: String,
26 #[serde(skip_serializing_if = "Option::is_none", default)]
27 pub valid_until: Option<String>,
28 pub issuer: String,
29 #[serde(skip_serializing_if = "Option::is_none", default)]
30 pub constraints: Option<Vec<Value>>,
31 pub signature: SignatureEnvelope,
32}
33
34#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
35pub struct SignatureEnvelope {
36 pub algorithm: String,
37 pub signer: String,
38 pub signature: String,
39}
40
41#[derive(Clone, Debug, Default)]
42pub struct RelayFrame {
43 pub ciphertext: Vec<u8>,
44 pub destination: String,
45 pub priority: Option<String>,
46 pub hop_count: u32,
47 pub expires_at: Option<String>,
48 pub source: Option<String>,
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
52pub struct RelayForwardedEvent {
53 #[serde(rename = "type")]
54 pub kind: String,
55 pub relay: String,
56 pub destination: String,
57 #[serde(skip_serializing_if = "Option::is_none", default)]
58 pub source: Option<String>,
59 pub hop_count_in: u32,
60 pub hop_count_out: u32,
61 pub size_bytes: usize,
62 pub forwarded_at: String,
63 #[serde(skip_serializing_if = "Option::is_none", default)]
64 pub authority_id: Option<String>,
65 #[serde(skip_serializing_if = "Option::is_none", default)]
66 pub priority: Option<String>,
67}
68
69#[derive(Debug, thiserror::Error)]
70pub enum RelayPolicyError {
71 #[error("relay authority invalid: {0}")]
72 Authority(String),
73 #[error("authority window: {0}")]
74 Window(String),
75 #[error("frame expired: {0}")]
76 Expired(String),
77 #[error("hop count: {0}")]
78 HopCap(String),
79 #[error("rate limit: {0}")]
80 Rate(String),
81}
82
83pub struct RelayHandler {
84 authority: RelayAuthority,
85 issuer_pub: [u8; 32],
86 validated: std::cell::Cell<bool>,
87 rate_bucket_minute: std::cell::Cell<i64>,
88 rate_bucket_count: std::cell::Cell<u32>,
89}
90
91impl RelayHandler {
92 pub fn new(authority: RelayAuthority, issuer_public_key: [u8; 32]) -> Self {
93 RelayHandler {
94 authority,
95 issuer_pub: issuer_public_key,
96 validated: std::cell::Cell::new(false),
97 rate_bucket_minute: std::cell::Cell::new(-1),
98 rate_bucket_count: std::cell::Cell::new(0),
99 }
100 }
101
102 pub fn authority(&self) -> &RelayAuthority {
103 &self.authority
104 }
105
106 pub fn forward(
107 &self,
108 frame: &RelayFrame,
109 now: &str,
110 ) -> Result<(RelayFrame, RelayForwardedEvent), RelayPolicyError> {
111 if !self.validated.get() {
112 let v = verify_relay_authority(&self.authority, &self.issuer_pub);
113 if !v.ok {
114 return Err(RelayPolicyError::Authority(
115 v.reason.unwrap_or_else(|| "unknown".into()),
116 ));
117 }
118 self.validated.set(true);
119 }
120 let window = Window {
121 valid_from: Some(self.authority.valid_from.as_str()),
122 valid_until: self.authority.valid_until.as_deref(),
123 ..Window::default()
124 };
125 if !is_within_window(&window, now) {
126 return Err(RelayPolicyError::Window(
127 "outside valid_from/valid_until".into(),
128 ));
129 }
130 if let Some(exp) = &frame.expires_at {
131 if exp.as_str() < now {
132 return Err(RelayPolicyError::Expired(format!(
133 "frame expired at {}",
134 exp
135 )));
136 }
137 }
138 if let Some(max) = self.authority.max_hop_count {
139 if frame.hop_count >= max {
140 return Err(RelayPolicyError::HopCap(format!(
141 "hop count {} >= max {}",
142 frame.hop_count, max
143 )));
144 }
145 }
146 if let Some(limit) = self.authority.rate_limit_per_minute {
147 let minute = parse_minute(now);
148 if minute != self.rate_bucket_minute.get() {
149 self.rate_bucket_minute.set(minute);
150 self.rate_bucket_count.set(0);
151 }
152 self.rate_bucket_count.set(self.rate_bucket_count.get() + 1);
153 if self.rate_bucket_count.get() > limit {
154 return Err(RelayPolicyError::Rate(format!(
155 "rate limit {}/min exceeded",
156 limit
157 )));
158 }
159 }
160 let mut outgoing = frame.clone();
161 outgoing.hop_count = frame.hop_count + 1;
162 let event = RelayForwardedEvent {
163 kind: "relay.forwarded".into(),
164 relay: self.authority.relay.clone(),
165 destination: frame.destination.clone(),
166 source: frame.source.clone(),
167 hop_count_in: frame.hop_count,
168 hop_count_out: outgoing.hop_count,
169 size_bytes: frame.ciphertext.len(),
170 forwarded_at: now.to_string(),
171 authority_id: Some(self.authority.relay.clone()),
172 priority: frame.priority.clone(),
173 };
174 Ok((outgoing, event))
175 }
176}
177
178fn parse_minute(now: &str) -> i64 {
179 let len = now.len().min(19);
183 if !now.is_char_boundary(len) || len < 19 {
184 return 0;
185 }
186 let trimmed = &now[..19];
187 let unix = parse_iso8601(trimmed).unwrap_or(0);
188 unix / 60
189}
190
191fn parse_iso8601(s: &str) -> Option<i64> {
192 if s.len() < 19 {
193 return None;
194 }
195 let year: i64 = s.get(0..4)?.parse().ok()?;
196 let month: u32 = s.get(5..7)?.parse().ok()?;
197 let day: u32 = s.get(8..10)?.parse().ok()?;
198 let hour: u32 = s.get(11..13)?.parse().ok()?;
199 let minute: u32 = s.get(14..16)?.parse().ok()?;
200 let second: u32 = s.get(17..19)?.parse().ok()?;
201 let y = if month <= 2 { year - 1 } else { year };
202 let era = if y >= 0 { y } else { y - 399 } / 400;
203 let yoe = (y - era * 400) as u64;
204 let m = if month > 2 { month - 3 } else { month + 9 };
205 let doy = (153 * m as u64 + 2) / 5 + day as u64 - 1;
206 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
207 let days = era * 146_097 + doe as i64 - 719_468;
208 Some(days * 86_400 + (hour as i64) * 3600 + (minute as i64) * 60 + second as i64)
209}
210
211pub fn relay_authority_signing_bytes(a: &RelayAuthority) -> [u8; 32] {
212 let mut value = serde_json::to_value(a).unwrap_or(Value::Null);
213 if let Value::Object(map) = &mut value {
214 map.remove("signature");
215 }
216 let canonical = canonicalize(&value).unwrap_or_default();
217 Sha256::digest(canonical.as_bytes()).into()
218}
219
220pub fn sign_relay_authority(
221 mut authority: RelayAuthority,
222 private_key: &[u8; 32],
223) -> RelayAuthority {
224 authority.signature = SignatureEnvelope {
225 algorithm: "ed25519".into(),
226 signer: authority.issuer.clone(),
227 signature: String::new(),
228 };
229 let digest = relay_authority_signing_bytes(&authority);
230 let signing = SigningKey::from_bytes(private_key);
231 let sig: Signature = signing.sign(&digest);
232 authority.signature.signature = STANDARD.encode(sig.to_bytes());
233 authority
234}
235
236#[derive(Debug)]
237pub struct VerifyRelayAuthorityResult {
238 pub ok: bool,
239 pub reason: Option<String>,
240}
241
242pub fn verify_relay_authority(
243 authority: &RelayAuthority,
244 issuer_public_key: &[u8; 32],
245) -> VerifyRelayAuthorityResult {
246 let rejected = |r: &str| VerifyRelayAuthorityResult {
247 ok: false,
248 reason: Some(r.to_string()),
249 };
250 if authority.relay_authority_version != "1" {
251 return rejected(&format!(
252 "unsupported version {}",
253 authority.relay_authority_version
254 ));
255 }
256 if authority.signature.algorithm != "ed25519" {
257 return rejected(&format!(
258 "unsupported signature algorithm {}",
259 authority.signature.algorithm
260 ));
261 }
262 if authority.signature.signer != authority.issuer {
263 return rejected("signature signer does not match authority issuer");
264 }
265 let digest = relay_authority_signing_bytes(authority);
266 let sig_bytes = match STANDARD.decode(&authority.signature.signature) {
267 Ok(b) => b,
268 Err(e) => return rejected(&format!("signature base64 decode: {}", e)),
269 };
270 let sig = match Signature::from_slice(&sig_bytes) {
271 Ok(s) => s,
272 Err(e) => return rejected(&format!("signature parse: {}", e)),
273 };
274 let vk = match VerifyingKey::from_bytes(issuer_public_key) {
275 Ok(v) => v,
276 Err(e) => return rejected(&format!("verifying key: {}", e)),
277 };
278 if vk.verify(&digest, &sig).is_err() {
279 return rejected("relay authority signature did not verify");
280 }
281 VerifyRelayAuthorityResult {
282 ok: true,
283 reason: None,
284 }
285}