1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature="std"), no_std)]
3
4
5
6pub type SigningKey = [u8; 32];
16
17pub type Nonce = [u8; 12];
22
23const NONCE_LENGTH: usize = core::mem::size_of::<Nonce>();
24
25const SIGNATURE_LENGTH: usize = 16;
26
27
28
29#[cfg(feature="rand")]
34pub fn generate_signing_key() -> SigningKey {
35 let mut data = [0; 32];
36 rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut data);
37 data
38}
39
40
41
42#[cfg(feature="rand")]
47pub fn generate_nonce() -> Nonce {
48 let mut data = [0; 12];
49 rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut data);
50 data
51}
52
53
54
55pub fn parse_cookie_header_value(header: &[u8]) -> impl Iterator<Item = (&str, &[u8])> {
73 header
74 .split(|c| *c == b';')
75 .map(|x| x.trim_ascii())
76 .filter_map(|x| {
77 let mut key_value_iterator = x.split(|c| *c == b'=').into_iter();
78
79 let key: &[u8] = key_value_iterator.next()?;
80 let key: &[u8] = key.trim_ascii();
81 let key: &str = core::str::from_utf8(key).ok()?;
82
83 let value: &[u8] = key_value_iterator.next()?.trim_ascii();
84 let value: &[u8] = value.strip_prefix(&[b'"']).unwrap_or(value);
85 let value: &[u8] = value.strip_suffix(&[b'"']).unwrap_or(value);
86
87 Some((key, value))
88 })
89}
90
91
92
93
94#[cfg(all(feature="std", feature="rand"))]
130pub fn encode_cookie(key: SigningKey, name: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> String {
131 let nonce = generate_nonce();
132 let mut output = vec![0; encoded_buffer_size(value.as_ref().len()).expect("unreachable, len comes from a slice and no slice can be large enough to make this operation overflow")];
133 encode_cookie_advanced(key, nonce, name, value, &mut output).expect("unreachable, the buffer should always be correctly sized");
134 String::from_utf8(output).expect("unreachable, encode_cookie_advanced should always produce ascii data")
135}
136
137
138
139pub fn encode_cookie_advanced<'a>(
149 key: SigningKey,
150 nonce: Nonce,
151 name: impl AsRef<[u8]>,
152 value: impl AsRef<[u8]>,
153 output: &'a mut [u8],
154) -> Result<(), OutputBufferWrongSize> {
155 let value: &[u8] = value.as_ref();
156
157 let expected_size =
158 match encoded_buffer_size(value.len()) {
159 None => return Err(OutputBufferWrongSize { expected_size: None, was: value.len(), }),
160 Some(x) if output.len() < x => return Err(OutputBufferWrongSize { expected_size: Some(x), was: value.len(), }),
161 Some(x) if x < output.len() => return Err(OutputBufferWrongSize { expected_size: Some(x), was: value.len(), }),
162 Some(x) => x,
163 };
164
165 let (nonce_slot, rest_of_output) = output.split_at_mut(NONCE_LENGTH);
168 let (encrypted_slot, rest_of_output) = rest_of_output.split_at_mut(value.len());
169 let (signature_slot, _rest_of_output) = rest_of_output.split_at_mut(SIGNATURE_LENGTH);
170
171 nonce_slot.copy_from_slice(&nonce);
174 encrypted_slot.copy_from_slice(value);
175
176 use aes_gcm::{AeadInPlace, KeyInit};
181 let key_array = aes_gcm::aead::generic_array::GenericArray::from_slice(&key);
182 let nonce_array = aes_gcm::aead::generic_array::GenericArray::from_slice(nonce_slot);
183 let encryptor = aes_gcm::Aes256Gcm::new(key_array);
184 let signature = encryptor
185 .encrypt_in_place_detached(&nonce_array, name.as_ref(), encrypted_slot)
186 .expect("failed to encrypt");
187
188 signature_slot.copy_from_slice(&signature);
191
192 let total_length = NONCE_LENGTH + value.len() + SIGNATURE_LENGTH;
193
194 encode_bytes_as_ascii(&mut output[..expected_size], total_length).unwrap();
197
198 Ok(())
199}
200
201#[derive(Debug, Eq, PartialEq, Copy, Clone)]
203pub struct OutputBufferWrongSize {
204 pub expected_size: Option<usize>,
206 pub was: usize,
207}
208
209
210
211pub const fn encoded_buffer_size(value_length: usize) -> Option<usize> {
220 if (usize::MAX / 2) - NONCE_LENGTH - SIGNATURE_LENGTH < value_length {
221 None
222 } else {
223 Some((NONCE_LENGTH + value_length + SIGNATURE_LENGTH) * 2)
224 }
225}
226
227
228
229pub const fn decode_buffer_size(value_length: usize) -> Option<usize> {
241 if value_length < (NONCE_LENGTH + SIGNATURE_LENGTH) * 2 {
242 None
243 } else {
244 Some((value_length / 2) - NONCE_LENGTH - SIGNATURE_LENGTH)
245 }
246}
247
248
249
250#[cfg(feature="std")]
267pub fn decode_cookie(key: SigningKey, name: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result<Vec<u8>, DecodeError> {
268 let Some(output_buffer_length) = decode_buffer_size(value.as_ref().len()) else {
269 return Err(DecodeError);
270 };
271
272 let mut output = vec![0; output_buffer_length];
273
274 match decode_cookie_advanced(key, name, value, &mut output) {
275 Ok(_) => Ok(output),
276 Err(reason) => Err(reason),
277 }
278}
279
280
281
282pub fn decode_cookie_advanced(key: SigningKey, name: impl AsRef<[u8]>, value: impl AsRef<[u8]>, output: &mut [u8]) -> Result<(), DecodeError> {
293 let value = value.as_ref();
294
295 if value.len() == 0 { return Err(DecodeError); }
296
297 if output.len() != decode_buffer_size(value.len()).ok_or(DecodeError)? {
298 return Err(DecodeError);
299 }
300
301 let merged_values_length = value.len() / 2;
302
303 let mut nonce = [0u8; NONCE_LENGTH];
305 decode_ascii_as_bytes(value, &mut nonce, 0, NONCE_LENGTH);
306
307 let mut signature = [0u8; SIGNATURE_LENGTH];
308 decode_ascii_as_bytes(value, &mut signature, merged_values_length - SIGNATURE_LENGTH, merged_values_length);
309
310 decode_ascii_as_bytes(value, output, NONCE_LENGTH, merged_values_length - SIGNATURE_LENGTH);
311
312 let key_array = aes_gcm::aead::generic_array::GenericArray::from_slice(&key);
314 let nonce_array = aes_gcm::aead::generic_array::GenericArray::from_slice(&nonce);
315 let signature = aes_gcm::aead::generic_array::GenericArray::from_slice(&signature);
316
317 use aes_gcm::KeyInit;
319 use aes_gcm::AeadInPlace;
320 aes_gcm::Aes256Gcm::new(key_array)
321 .decrypt_in_place_detached(
322 nonce_array,
323 name.as_ref(),
324 output,
325 signature,
326 )
327 .or(Err(DecodeError))
328}
329
330#[derive(Copy, Clone, Debug, Eq, PartialEq)]
334pub struct DecodeError;
335
336
337
338fn encode_bytes_as_ascii<'a>(input: &'a mut [u8], length: usize) -> Option<&'a mut str> {
372 if input.len() < length * 2 {
373 return None;
374 }
375
376 let mut read_index = length;
377 let mut write_index = length * 2;
378
379 while 0 < read_index {
380 read_index -= 1;
381 write_index -= 2;
382 let byte = input[read_index];
383 let high = byte >> 4;
384 let low = byte & 0b1111;
385 input[write_index + 0] = high + b'a';
386 input[write_index + 1] = low + b'a';
387 }
388
389 let string =
390 core::str::from_utf8_mut(&mut input[..length * 2])
391 .expect("unreachable: code can only generate valid ascii");
392
393 Some(string)
394}
395
396
397
398fn decode_ascii_as_bytes<'a>(input: &[u8], output: &'a mut [u8], from: usize, to: usize) -> &'a mut [u8] {
408 if to < from {
409 return &mut output[..0];
410 }
411
412 let length = (to - from).min(output.len());
413
414 for (index, chunk) in input.chunks_exact(2).skip(from).take(length).enumerate() {
415 let [high, low] = chunk else { unreachable!() };
416
417 output[index] =
418 ((high.saturating_sub(b'a')) & 0b1111) << 4
419 | ((low.saturating_sub(b'a')) & 0b1111);
420 }
421
422 &mut output[..length]
423}
424
425
426
427#[cfg(test)]
428mod test {
429 use super::*;
430
431 pub fn init_random() -> oorandom::Rand64 {
432 let seed = rand::Rng::gen_range(&mut rand::thread_rng(), 100_000_000..999_999_999);
433 println!("Seed: {}", seed);
434 oorandom::Rand64::new(seed)
435 }
436
437 pub fn random_bytes(random: &mut oorandom::Rand64) -> Vec<u8> {
438 let length = random.rand_range(0..50);
439 let mut data = vec![0; length as usize];
440 for entry in data.iter_mut() {
441 *entry = random.rand_u64() as u8;
442 }
443
444 data
445 }
446
447 pub const fn const_unwrap(input: Option<usize>) -> usize {
448 match input {
449 None => panic!("Tried to unwrap a None value"),
450 Some(t) => t,
451 }
452 }
453
454 #[test]
455 fn test_ascii_encode() {
456 let mut random = test::init_random();
457
458 for _ in 0..100 {
459 let raw_data = test::random_bytes(&mut random);
460
461 if raw_data.len() == 0 { continue; }
462
463 let mut encoded_buffer = vec![0u8; raw_data.len() * 2];
464 encoded_buffer[..raw_data.len()].copy_from_slice(&raw_data);
465 encode_bytes_as_ascii(&mut encoded_buffer, raw_data.len()).unwrap();
466
467 for _ in 0..10 {
468 let from = random.rand_range(0..raw_data.len() as u64) as usize;
469 let to = random.rand_range(from as u64..raw_data.len() as u64) as usize;
470 let mut decoded_buffer = vec![0u8; to - from];
471 decode_ascii_as_bytes(&encoded_buffer, &mut decoded_buffer, from, to);
472
473 assert_eq!(&raw_data[from..to], &decoded_buffer);
474 }
475 }
476 }
477
478 #[test]
479 fn encode_decode_succeeds() {
480 let key = generate_signing_key();
481 let nonce = generate_nonce();
482 let name = "session";
483 let data = r#"{"id":5}"#;
484
485 let mut encoded = [0u8; const_unwrap(encoded_buffer_size(8))];
486 encode_cookie_advanced(key, nonce, name, data, &mut encoded).unwrap();
487
488 let mut decoded = [0u8; 8];
489 decode_cookie_advanced(key, name, encoded, &mut decoded).unwrap();
490
491 assert_eq!(decoded, data.as_bytes());
492 }
493
494 #[test]
495 fn returns_error_for_invalid_buffer_lengths() {
496 let key = generate_signing_key();
497
498 assert_eq!(Err(DecodeError), decode_cookie_advanced(key, "", "", &mut []));
499 assert_eq!(Err(DecodeError), decode_cookie_advanced(key, "", "a", &mut []));
500 assert_eq!(Err(DecodeError), decode_cookie_advanced(key, "", "asdklfjaskdf", &mut []));
501 assert_eq!(Err(DecodeError), decode_cookie_advanced(key, "", "asdklfjaskdf", &mut [0u8]));
502 assert_eq!(Err(DecodeError), decode_cookie_advanced(key, "", "asdklfjaskdf", &mut [0u8; 5]));
503 }
504
505 #[test]
506 fn different_keys_fails() {
507 let key_a = generate_signing_key();
508 let nonce = generate_nonce();
509 let name = "session";
510 let data = r#"{"id":5}"#;
511
512 let mut encoded = [0u8; const_unwrap(encoded_buffer_size(8))];
513 encode_cookie_advanced(key_a, nonce, name, data, &mut encoded).unwrap();
514
515 let key_b = generate_signing_key();
516 let mut decoded = [0u8; 8];
517 let decode_result = decode_cookie_advanced(key_b, name, encoded, &mut decoded);
518
519 assert_eq!(decode_result, Err(DecodeError));
520 }
521
522 #[test]
523 fn different_names_fails() {
524 let key = generate_signing_key();
525 let nonce = generate_nonce();
526 let name_a = "session";
527 let data = r#"{"id":5}"#;
528
529 let mut encoded = [0u8; const_unwrap(encoded_buffer_size(8))];
530 encode_cookie_advanced(key, nonce, name_a, data, &mut encoded).unwrap();
531
532 let name_b = "laskdjf";
533 let mut decoded = [0u8; 8];
534 let decode_result = decode_cookie_advanced(key, name_b, encoded, &mut decoded);
535
536 assert_eq!(decode_result, Err(DecodeError));
537 }
538
539 #[test]
540 fn identical_values_have_different_ciphers() {
541 let key = generate_signing_key();
542 let name = "session";
543 let data = "which wolf do you feed?";
544
545 let mut encoded_1 = [0u8; const_unwrap(encoded_buffer_size(23))];
546 encode_cookie_advanced(key, generate_nonce(), name, data, &mut encoded_1).unwrap();
547
548 let mut encoded_2 = [0u8; const_unwrap(encoded_buffer_size(23))];
549 encode_cookie_advanced(key, generate_nonce(), name, data, &mut encoded_2).unwrap();
550
551 assert_ne!(encoded_1, encoded_2);
552 }
553
554 #[test]
555 fn parses_spaceless_header() {
556 let header = b"session=213lkj1;another=3829";
557 let mut iterator = parse_cookie_header_value(header);
558
559 let (name, value) = iterator.next().unwrap();
560 assert_eq!(name, "session");
561 assert_eq!(value, b"213lkj1");
562
563 let (name, value) = iterator.next().unwrap();
564 assert_eq!(name, "another");
565 assert_eq!(value, b"3829");
566 }
567
568 #[test]
569 fn parses_spaced_header() {
570 let header = b"session = 123kj; sakjdf = klsjdf23";
571 let mut iterator = parse_cookie_header_value(header);
572
573 let (name, value) = iterator.next().unwrap();
574 assert_eq!(name, "session");
575 assert_eq!(value, b"123kj");
576
577 let (name, value) = iterator.next().unwrap();
578 assert_eq!(name, "sakjdf");
579 assert_eq!(value, b"klsjdf23");
580 }
581
582 #[test]
583 fn strips_value_quotes() {
584 let header = b"session=\"alkjs\"";
585 let mut iterator = parse_cookie_header_value(header);
586 let (name, value) = iterator.next().unwrap();
587 assert_eq!(name, "session");
588 assert_eq!(value, b"alkjs");
589 }
590
591 #[test]
592 fn includes_name_quotes() {
593 let header = b"\"session\"=asdf";
594 let mut iterator = parse_cookie_header_value(header);
595 let (name, value) = iterator.next().unwrap();
596 assert_eq!(name, "\"session\"");
597 assert_eq!(value, b"asdf");
598 }
599}