uselesskey_core_x509_derive/
lib.rs1#![forbid(unsafe_code)]
38#![warn(missing_docs)]
39
40use rand_chacha10::ChaCha20Rng;
41use rand_core10::{Rng, SeedableRng};
42use rcgen::SerialNumber;
43use time::OffsetDateTime;
44use uselesskey_core_hash::Hasher;
45pub use uselesskey_core_hash::write_len_prefixed;
46use uselesskey_core_seed::Seed;
47
48pub const BASE_TIME_EPOCH_UNIX: i64 = 1_735_689_600;
50
51pub const BASE_TIME_WINDOW_DAYS: u32 = 365;
53
54pub const SERIAL_NUMBER_BYTES: usize = 16;
56
57pub fn deterministic_base_time_from_parts(parts: &[&[u8]]) -> OffsetDateTime {
61 let mut hasher = Hasher::new();
62 for part in parts {
63 write_len_prefixed(&mut hasher, part);
64 }
65 deterministic_base_time(hasher)
66}
67
68pub fn deterministic_base_time(hasher: Hasher) -> OffsetDateTime {
72 let epoch = OffsetDateTime::from_unix_timestamp(BASE_TIME_EPOCH_UNIX)
73 .expect("failed to construct deterministic X.509 epoch");
74
75 let hash = hasher.finalize();
76 let bytes = hash.as_bytes();
77 let day_offset =
78 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) % BASE_TIME_WINDOW_DAYS;
79 epoch + time::Duration::days(i64::from(day_offset))
80}
81
82pub fn deterministic_serial_number(seed: Seed) -> SerialNumber {
86 let mut rng = ChaCha20Rng::from_seed(*seed.bytes());
87 deterministic_serial_number_with_rng(&mut rng)
88}
89
90fn deterministic_serial_number_with_rng(rng: &mut impl Rng) -> SerialNumber {
91 let mut bytes = [0u8; SERIAL_NUMBER_BYTES];
92 rng.fill_bytes(&mut bytes);
93 bytes[0] &= 0x7F;
94 SerialNumber::from_slice(&bytes)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use uselesskey_core_seed::Seed;
101
102 #[test]
103 fn deterministic_base_time_is_within_one_year() {
104 let epoch = OffsetDateTime::from_unix_timestamp(BASE_TIME_EPOCH_UNIX).unwrap();
105 let max = epoch + time::Duration::days(i64::from(BASE_TIME_WINDOW_DAYS - 1));
106
107 let base = deterministic_base_time(Hasher::new());
108 assert!(base >= epoch, "base time should be after epoch");
109 assert!(base <= max, "base time should be within one year");
110 }
111
112 #[test]
113 fn deterministic_base_time_is_deterministic() {
114 let a = deterministic_base_time_from_parts(&[b"label", b"leaf", b"root", b"2048"]);
115 let b = deterministic_base_time_from_parts(&[b"label", b"leaf", b"root", b"2048"]);
116 assert_eq!(a, b);
117 }
118
119 #[test]
120 fn deterministic_base_time_from_parts_is_boundary_safe() {
121 let a = deterministic_base_time_from_parts(&[b"ab", b"c"]);
122 let b = deterministic_base_time_from_parts(&[b"a", b"bc"]);
123 assert_ne!(a, b);
124 }
125
126 #[test]
127 fn deterministic_serial_number_is_positive_and_fixed_size() {
128 let serial = deterministic_serial_number(Seed::new([7u8; 32]));
129 let bytes = serial.to_bytes();
130
131 assert_eq!(bytes.len(), SERIAL_NUMBER_BYTES);
132 assert_eq!(bytes[0] & 0x80, 0, "high bit should be cleared");
133 }
134
135 #[test]
136 fn deterministic_serial_number_is_seed_stable() {
137 assert_eq!(
138 deterministic_serial_number(Seed::new([42u8; 32])).to_bytes(),
139 deterministic_serial_number(Seed::new([42u8; 32])).to_bytes()
140 );
141 }
142
143 #[test]
144 fn deterministic_serial_number_varies_by_seed() {
145 assert_ne!(
146 deterministic_serial_number(Seed::new([1u8; 32])).to_bytes(),
147 deterministic_serial_number(Seed::new([2u8; 32])).to_bytes()
148 );
149 }
150}