forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
2.2 KiB
1//! Access tokens: `forge_` + base64url(32 random bytes). Only the sha256 hash is stored.
2
3use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine as _};
4use sha2::{Digest, Sha256};
5use subtle::ConstantTimeEq;
6
7/// Fresh random 32 bytes from the thread RNG, a CSPRNG seeded from the OS. It is
8/// infallible, so callers need no error path.
9pub(crate) fn random_32() -> [u8; 32] {
10 let mut buf = [0u8; 32];
11 rand::fill(&mut buf);
12 buf
13}
14
15/// Alphabet for codes typed by people: lowercase letters and digits 2-9, without look-alikes
16/// such as `l`, `o`, `0` and `1`. It has exactly 32 symbols, so the low five bits of a random
17/// byte pick one uniformly.
18pub const READABLE_ALPHABET: &[u8; 32] = b"abcdefghijkmnpqrstuvwxyz23456789";
19
20/// `len` random characters from [`READABLE_ALPHABET`].
21pub fn random_readable(len: usize) -> String {
22 let mut buf = vec![0u8; len];
23 rand::fill(buf.as_mut_slice());
24 buf.iter()
25 .map(|b| char::from(READABLE_ALPHABET[usize::from(b & 31)]))
26 .collect()
27}
28
29pub fn generate_token() -> String {
30 format!("forge_{}", BASE64_URL_SAFE_NO_PAD.encode(random_32()))
31}
32
33/// Hex-encoded sha256 of the token.
34pub fn hash_token(token: &str) -> String {
35 hex::encode(Sha256::digest(token.as_bytes()))
36}
37
38/// Constant-time byte comparison.
39pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
40 a.ct_eq(b).into()
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46
47 #[test]
48 fn token_has_prefix_and_43_char_body() {
49 let t = generate_token();
50 let body = t.strip_prefix("forge_").unwrap();
51 assert_eq!(body.len(), 43);
52 assert!(
53 body.bytes()
54 .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
55 );
56 assert_ne!(t, generate_token());
57 }
58
59 #[test]
60 fn hash_is_hex_sha256() {
61 // Known vector: sha256("abc").
62 assert_eq!(
63 hash_token("abc"),
64 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
65 );
66 }
67
68 #[test]
69 fn constant_time_eq_behaves_like_eq() {
70 assert!(constant_time_eq(b"same", b"same"));
71 assert!(!constant_time_eq(b"same", b"diff"));
72 assert!(!constant_time_eq(b"short", b"longer"));
73 assert!(constant_time_eq(b"", b""));
74 }
75}