1use crate::crypto::{aead_decrypt, aead_encrypt, sha256_digest};
2use crate::error::MoqSecureError;
3use crate::key_store::KeyStore;
4use ed25519_dalek::{Signer, Verifier};
5
6pub const MAGIC: [u8; 4] = *b"MOQS";
7pub const VERSION: u8 = 1;
8
9pub const SIG_SLOT_LEN: usize = 64;
10pub const AEAD_TAG_LEN: usize = 16;
11pub const PAD_LEN_FIELD_LEN: usize = 4;
12
13pub const FIXED_HEADER_LEN: usize = 4 + 1 + 1 + 8 + 1 + 1 + 1;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct WireHeader {
19 pub magic: [u8; 4],
20 pub version: u8,
21 pub key_id: u8,
22 pub ctr: u64,
23 pub n_signed: u8,
24 pub sig_flag: u8,
25 pub encrypted: u8,
26}
27
28impl WireHeader {
29 pub fn encode(&self) -> Vec<u8> {
30 let mut v = Vec::with_capacity(FIXED_HEADER_LEN);
31
32 v.extend_from_slice(&self.magic);
33 v.push(self.version);
34 v.push(self.key_id);
35 v.extend_from_slice(&self.ctr.to_be_bytes());
36 v.push(self.n_signed);
37 v.push(self.sig_flag);
38 v.push(self.encrypted);
39
40 v
41 }
42
43 pub fn aad(&self) -> Vec<u8> {
44 self.encode()
45 }
46
47 pub fn validate(&self) -> Result<(), MoqSecureError> {
48 if self.magic != MAGIC {
49 return Err(MoqSecureError::InvalidMagic);
50 }
51
52 if self.version != VERSION {
53 return Err(MoqSecureError::UnsupportedVersion(self.version));
54 }
55
56 if self.sig_flag != 0 && self.sig_flag != 1 {
57 return Err(MoqSecureError::InvalidSigFlag(self.sig_flag));
58 }
59
60 if self.encrypted != 0 && self.encrypted != 1 {
61 return Err(MoqSecureError::InvalidEncryptedFlag(self.encrypted));
62 }
63
64 if self.n_signed == 0 && self.sig_flag != 0 {
65 return Err(MoqSecureError::SigningMismatch);
66 }
67
68 Ok(())
69 }
70}
71
72#[derive(Debug, Clone)]
73pub struct Frame {
74 pub header: WireHeader,
75
76 pub payload: Vec<u8>,
80
81 pub tag: [u8; AEAD_TAG_LEN],
82 pub signature: Option<[u8; SIG_SLOT_LEN]>,
83}
84
85impl Frame {
86 pub fn parse(frame: &[u8]) -> Result<Self, MoqSecureError> {
87 if frame.len() < FIXED_HEADER_LEN {
88 return Err(MoqSecureError::TruncatedFrame);
89 }
90
91 let (h_bytes, rest0) = frame.split_at(FIXED_HEADER_LEN);
92 let mut idx = 0;
93
94 let mut magic = [0u8; 4];
95 magic.copy_from_slice(&h_bytes[idx..idx + 4]);
96 idx += 4;
97
98 let version = h_bytes[idx];
99 idx += 1;
100
101 let key_id = h_bytes[idx];
102 idx += 1;
103
104 let mut ctr_bytes = [0u8; 8];
105 ctr_bytes.copy_from_slice(&h_bytes[idx..idx + 8]);
106 idx += 8;
107
108 let ctr = u64::from_be_bytes(ctr_bytes);
109
110 let n_signed = h_bytes[idx];
111 idx += 1;
112
113 let sig_flag = h_bytes[idx];
114 idx += 1;
115
116 let encrypted = h_bytes[idx];
117
118 let header = WireHeader {
119 magic,
120 version,
121 key_id,
122 ctr,
123 n_signed,
124 sig_flag,
125 encrypted,
126 };
127
128 header.validate()?;
129
130 let sig_len = if header.sig_flag == 1 {
131 SIG_SLOT_LEN
132 } else {
133 0
134 };
135
136 if rest0.len() < sig_len {
137 return Err(MoqSecureError::TruncatedFrame);
138 }
139
140 let (payload_and_tag, signature_bytes) = if sig_len == 0 {
141 (rest0, None)
142 } else {
143 let split_at = rest0.len() - SIG_SLOT_LEN;
144 let (payload, signature) = rest0.split_at(split_at);
145 (payload, Some(signature))
146 };
147
148 let signature = if let Some(sig_bytes) = signature_bytes {
149 if sig_bytes.len() != SIG_SLOT_LEN {
150 return Err(MoqSecureError::TruncatedFrame);
151 }
152
153 let mut sig = [0u8; SIG_SLOT_LEN];
154 sig.copy_from_slice(sig_bytes);
155
156 if sig == [0u8; SIG_SLOT_LEN] {
157 return Err(MoqSecureError::InvalidSignature);
158 }
159
160 Some(sig)
161 } else {
162 None
163 };
164
165 if header.encrypted == 1 {
166 if payload_and_tag.len() < AEAD_TAG_LEN {
167 return Err(MoqSecureError::CiphertextTooShort);
168 }
169
170 let split_at = payload_and_tag.len() - AEAD_TAG_LEN;
171 let ciphertext = &payload_and_tag[..split_at];
172 let tag_bytes = &payload_and_tag[split_at..];
173
174 let tag: [u8; AEAD_TAG_LEN] = tag_bytes
175 .try_into()
176 .map_err(|_| MoqSecureError::CiphertextTooShort)?;
177
178 Ok(Self {
179 header,
180 payload: ciphertext.to_vec(),
181 tag,
182 signature,
183 })
184 } else {
185 Ok(Self {
186 header,
187 payload: payload_and_tag.to_vec(),
188 tag: [0u8; AEAD_TAG_LEN],
189 signature,
190 })
191 }
192 }
193
194 pub fn serialize(&self) -> Vec<u8> {
195 let mut out = Vec::new();
196
197 out.extend_from_slice(&self.header.encode());
198
199 if self.header.encrypted == 1 {
200 out.extend_from_slice(&self.payload);
201 out.extend_from_slice(&self.tag);
202 } else {
203 out.extend_from_slice(&self.payload);
204 }
205
206 if self.header.sig_flag == 1 {
207 if let Some(signature) = self.signature {
208 out.extend_from_slice(&signature);
209 } else {
210 out.extend_from_slice(&[0u8; SIG_SLOT_LEN]);
211 }
212 }
213
214 out
215 }
216
217 pub fn aad_bytes(&self) -> Vec<u8> {
218 self.header.aad()
219 }
220
221 pub fn digest_for_signature(&self) -> [u8; 32] {
222 let header_bytes = self.header.encode();
223
224 let mut data = Vec::with_capacity(
225 header_bytes.len()
226 + self.payload.len()
227 + if self.header.encrypted == 1 {
228 AEAD_TAG_LEN
229 } else {
230 0
231 },
232 );
233
234 data.extend_from_slice(&header_bytes);
235 data.extend_from_slice(&self.payload);
236
237 if self.header.encrypted == 1 {
238 data.extend_from_slice(&self.tag);
239 }
240
241 sha256_digest(&data)
242 }
243
244 pub fn decode_plaintext_with_key_store(
245 &self,
246 key_store: &dyn KeyStore,
247 broadcaster_public_key: &ed25519_dalek::VerifyingKey,
248 lease_remaining: &mut u8,
249 ) -> Result<Vec<u8>, MoqSecureError> {
250 let signing_enabled = self.header.n_signed > 0;
251 let signed = self.header.sig_flag == 1;
252
253 let next_lease_remaining = if !signing_enabled {
260 if self.header.sig_flag != 0 || self.signature.is_some() {
261 return Err(MoqSecureError::SigningMismatch);
262 }
263
264 None
266 } else if signed {
267 let signature_bytes = self
268 .signature
269 .ok_or(MoqSecureError::InvalidSignature)?;
270
271 let signature =
272 ed25519_dalek::Signature::from_bytes(&signature_bytes);
273
274 let digest = self.digest_for_signature();
275
276 broadcaster_public_key
277 .verify(&digest, &signature)
278 .map_err(|_| MoqSecureError::InvalidSignature)?;
279
280 Some(self.header.n_signed.saturating_sub(1))
286 } else {
287 if *lease_remaining == 0 {
292 return Err(MoqSecureError::InvalidSignature);
293 }
294
295 Some(lease_remaining.saturating_sub(1))
296 };
297
298 let padded_plaintext = if self.header.encrypted == 1 {
299 let key = key_store
300 .aead_key(self.header.key_id)
301 .ok_or(MoqSecureError::InvalidKeyId(self.header.key_id))?;
302
303 aead_decrypt(
304 key,
305 self.header.key_id,
306 self.header.ctr,
307 &self.aad_bytes(),
308 &self.payload,
309 &self.tag,
310 )?
311 } else {
312 self.payload.clone()
313 };
314
315 if padded_plaintext.len() < PAD_LEN_FIELD_LEN {
316 return Err(MoqSecureError::InvalidPadLength);
317 }
318
319 let mut pad_len_bytes = [0u8; PAD_LEN_FIELD_LEN];
320 pad_len_bytes.copy_from_slice(&padded_plaintext[..PAD_LEN_FIELD_LEN]);
321
322 let pad_len = u32::from_be_bytes(pad_len_bytes) as usize;
323
324 let content_start = PAD_LEN_FIELD_LEN
325 .checked_add(pad_len)
326 .ok_or(MoqSecureError::InvalidPadLength)?;
327
328 if content_start > padded_plaintext.len() {
329 return Err(MoqSecureError::InvalidPadLength);
330 }
331
332 let plaintext = padded_plaintext[content_start..].to_vec();
333
334 if let Some(next) = next_lease_remaining {
342 *lease_remaining = next;
343 }
344
345 Ok(plaintext)
346 }
347}
348
349pub fn encrypt_frame(
350 key_store: &dyn KeyStore,
351 broadcaster_private_key: &ed25519_dalek::SigningKey,
352 key_id: u8,
353 ctr: u64,
354 n_signed: u8,
355 maybe_sign: bool,
356 encrypted: u8,
357 pad_len: u32,
358 plaintext: &[u8],
359) -> Result<Frame, MoqSecureError> {
360 if encrypted != 0 && encrypted != 1 {
361 return Err(MoqSecureError::InvalidEncryptedFlag(encrypted));
362 }
363
364 let sig_flag = if n_signed != 0 && maybe_sign {
365 1
366 } else {
367 0
368 };
369
370 let header = WireHeader {
371 magic: MAGIC,
372 version: VERSION,
373 key_id,
374 ctr,
375 n_signed,
376 sig_flag,
377 encrypted,
378 };
379
380 header.validate()?;
381
382 let pad_len_usize = pad_len as usize;
383
384 let mut padded_plaintext = Vec::with_capacity(
385 PAD_LEN_FIELD_LEN + pad_len_usize + plaintext.len(),
386 );
387
388 padded_plaintext.extend_from_slice(&pad_len.to_be_bytes());
389 padded_plaintext.resize(PAD_LEN_FIELD_LEN + pad_len_usize, 0);
390 padded_plaintext.extend_from_slice(plaintext);
391
392 let frame_without_signature = if encrypted == 1 {
393 let key = key_store
394 .aead_key(key_id)
395 .ok_or(MoqSecureError::InvalidKeyId(key_id))?;
396
397 let (ciphertext, tag) = aead_encrypt(
398 key,
399 key_id,
400 ctr,
401 &header.aad(),
402 &padded_plaintext,
403 );
404
405 Frame {
406 header,
407 payload: ciphertext,
408 tag,
409 signature: None,
410 }
411 } else {
412 Frame {
413 header,
414 payload: padded_plaintext,
415 tag: [0u8; AEAD_TAG_LEN],
416 signature: None,
417 }
418 };
419
420 if sig_flag == 1 {
421 let digest = frame_without_signature.digest_for_signature();
422 let signature = broadcaster_private_key.sign(&digest);
423
424 Ok(Frame {
425 signature: Some(signature.to_bytes()),
426 ..frame_without_signature
427 })
428 } else {
429 Ok(frame_without_signature)
430 }
431}
432
433pub fn decrypt_frame(
434 key_store: &dyn KeyStore,
435 broadcaster_public_key: &ed25519_dalek::VerifyingKey,
436 lease_remaining: &mut u8,
437 frame_bytes: &[u8],
438) -> Result<Vec<u8>, MoqSecureError> {
439 let frame = Frame::parse(frame_bytes)?;
440
441 frame.decode_plaintext_with_key_store(
442 key_store,
443 broadcaster_public_key,
444 lease_remaining,
445 )
446}