#cookies #security #encryption #signing #secure #signed #parser

no-std simple-cookie

Functions for creating and parsing signed & encrypted cookies

6 releases (stable)

2.0.2 Nov 19, 2024
2.0.1 Nov 16, 2024
1.0.0 Mar 16, 2024
0.1.1 Jan 26, 2023

#451 in Cryptography

Download history 38/week @ 2024-08-18 28/week @ 2024-08-25 31/week @ 2024-09-01 122/week @ 2024-09-08 409/week @ 2024-09-15 851/week @ 2024-09-22 387/week @ 2024-09-29 172/week @ 2024-10-06 257/week @ 2024-10-13 361/week @ 2024-10-20 368/week @ 2024-10-27 446/week @ 2024-11-03 488/week @ 2024-11-10 532/week @ 2024-11-17 160/week @ 2024-11-24 422/week @ 2024-12-01

1,606 downloads per month

MIT license

28KB
299 lines

Functions for creating and parsing signed & encrypted cookies.

The cookie crate is the de facto secure cookie library in Rust. It is Way Too Complicated (TM) for what I need. (And, in my opinion, for what most people need.) This is the 80% solution for 20% of the effort.

This library has only four goals:

  • A simple, easily auditable implementation of signing, encrypting, decrypting & verifying cookies.
  • Clear comments pointing out security issues and describing how to avoid them.
  • no_std & no_alloc support. (Because why allocate if you don't need to?)
  • Never panic

The goals of this library are not:

  • Automatically detecting when a new Set-Cookie header is required.
  • Tracking changes to cookies.
  • Validating cookie name compliance with RFC6265. (Just don't use any weird cookie names.)
  • Any kind of cookie "jar" functionality.
  • Literally anything else.

Basic use

With the rand and std features enabled (they are enabled by default), you just need three function calls:

use simple_cookie::{generate_signing_key, encode_cookie, decode_cookie};

let signing_key = generate_signing_key();
let encoded = encode_cookie(signing_key, "account_id", 56u32.to_le_bytes());
let decoded = decode_cookie(signing_key, "account_id", encoded);

assert_eq!(decoded, Ok(vec![56, 0, 0, 0]));

You probably want an actual Set-Cookie header. You can build one pretty easily:

use simple_cookie::{generate_signing_key, encode_cookie};

let signing_key = generate_signing_key();
let encoded = encode_cookie(signing_key, "account_id", 56u32.to_le_bytes());

// You might find the docs for the Set-Cookie header on MDN helpful: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
let header = format!("Set-Cookie: session={}; Max-Age=604800; Secure; HttpOnly; SameSite=Strict", encoded);

Then, to decrypt a header:

use simple_cookie::{parse_cookie_header_value, decode_cookie};

// You can create your own key or load it from somewhere.
// Don't use all zeros like this though. See the documentation for SigningKey for more info.
let signing_key = [0; 32];

// This is a standard HTTP Cookie header, pretty much exactly what the browser sends to your server.
let header = b"Cookie: session=mbccnoonilihadcgdodlnebagojggejpcljodhghaeglnomdpjhekoiepiljopgd; another-cookie=another-value";

// parse_cookie_header_value doesn't expect the header name.
// You don't normally need this step since HTTP libraries typically automatically parse
// the header name & value into separate parts of a tuple or struct or something.
let header = &header[8..];

// parse_cookie_header_value returns an iterator, so you can use it in a for loop or something.
// I'll just find the cookie we're interested in here.
let (name, encoded_value) = parse_cookie_header_value(header).find(|(name, _value)| *name == "session").unwrap();

println!("a: {:?}", name);
println!("b: {:?}", core::str::from_utf8(encoded_value));
let value = decode_cookie(signing_key, name, encoded_value);

assert!(value.is_ok())

Advanced Use

You can use this library without std or the rand crate by setting default-features = false.

let data = [56, 72, 81];

const ENCODE_BUFFER_SIZE: usize = 62; // or use: simple_cookie::encoded_buffer_size(data.len())
const DECODE_BUFFER_SIZE: usize = 3; // or use: simple_cookie::decode_buffer_size(encoded_buffer_size)

// Up to you to generate a signing key without the rand crate.
// See the docs on the [SigningKey] type for more info.
let signing_key = [0u8; 32];

// Up to you to generate an nonce without the rand crate.
// See the docs on [encode_cookie_advnaced] for requirements.
let nonce = [0u8; 12];

let mut encoded = [0u8; ENCODE_BUFFER_SIZE];

// The advanced version of encode_cookie takes an explicit nonce
// (rather than generating it automatically with the rand crate)
// and a mutable buffer to write into (rather than returning a Vec).
let output =
	simple_cookie::encode_cookie_advanced(
		signing_key,
		nonce,
		"account_id",
		&data,
		&mut encoded
	)
	.unwrap();

// Decoding is similar. Make sure you use the same signing key
// to decode that you used to encode!
let mut decoded = [0u8; DECODE_BUFFER_SIZE];
simple_cookie::decode_cookie_advanced(
	signing_key,
	"account_id",
	&encoded,
	&mut decoded
)
.unwrap();

assert_eq!(decoded, data);

Dependencies

~1–1.6MB
~28K SLoC