uselesskey_core_negative_der/
lib.rs1#![forbid(unsafe_code)]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4extern crate alloc;
12
13use alloc::vec::Vec;
14
15use uselesskey_core_hash::hash32;
16
17pub fn truncate_der(der: &[u8], len: usize) -> Vec<u8> {
19 if len >= der.len() {
20 return der.to_vec();
21 }
22 der[..len].to_vec()
23}
24
25pub fn flip_byte(der: &[u8], offset: usize) -> Vec<u8> {
27 if offset >= der.len() {
28 return der.to_vec();
29 }
30
31 let mut out = der.to_vec();
32 out[offset] ^= 0x01;
33 out
34}
35
36pub fn corrupt_der_deterministic(der: &[u8], variant: &str) -> Vec<u8> {
40 let digest = hash32(variant.as_bytes());
41 let bytes = digest.as_bytes();
42
43 match bytes[0] % 3 {
44 0 => {
45 let len = derived_truncate_len_bytes(der.len(), bytes);
46 truncate_der(der, len)
47 }
48 1 => {
49 let offset = derived_offset(der.len(), bytes[1]);
50 flip_byte(der, offset)
51 }
52 _ => {
53 let offset = derived_offset(der.len(), bytes[1]);
54 let flipped = flip_byte(der, offset);
55 let len = derived_truncate_len_bytes(flipped.len(), bytes);
56 truncate_der(&flipped, len)
57 }
58 }
59}
60
61fn derived_offset(len: usize, selector: u8) -> usize {
62 if len == 0 {
63 return 0;
64 }
65 selector as usize % len
66}
67
68fn derived_truncate_len_bytes(len: usize, digest: &[u8; 32]) -> usize {
69 if len <= 1 {
70 return 0;
71 }
72 let span = len - 1;
73 digest[2] as usize % span
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn flip_byte_changes_only_target_offset() {
82 let der = vec![0x30, 0x82, 0x01, 0x22];
83 let flipped = flip_byte(&der, 0);
84
85 assert_eq!(flipped[0], 0x31);
86 assert_eq!(&flipped[1..], &der[1..]);
87 }
88
89 #[test]
90 fn truncate_der_shortens_when_len_smaller() {
91 let der = vec![0x30, 0x82, 0x01, 0x22];
92 let truncated = truncate_der(&der, 2);
93 assert_eq!(truncated, vec![0x30, 0x82]);
94 }
95
96 #[test]
97 fn deterministic_der_corruption_is_stable_for_same_variant() {
98 let der = vec![0x30, 0x82, 0x01, 0x22, 0x10, 0x20];
99 let first = corrupt_der_deterministic(&der, "corrupt:variant-a");
100 let second = corrupt_der_deterministic(&der, "corrupt:variant-a");
101 assert_eq!(first, second);
102 assert_ne!(first, der);
103 }
104
105 #[test]
106 fn derived_truncate_len_bytes_exact_arithmetic() {
107 let mut digest = [0u8; 32];
108 digest[2] = 0x0B; assert_eq!(derived_truncate_len_bytes(5, &digest), 3);
110 }
111
112 #[test]
113 fn derived_truncate_len_bytes_single_returns_zero() {
114 let digest = [0u8; 32];
115 assert_eq!(derived_truncate_len_bytes(1, &digest), 0);
116 }
117
118 #[test]
119 fn derived_offset_exact_arithmetic() {
120 assert_eq!(derived_offset(5, 7), 2); }
122
123 #[test]
124 fn derived_offset_zero_len_returns_zero() {
125 assert_eq!(derived_offset(0, 7), 0);
126 }
127
128 #[test]
129 fn flip_byte_xor_vs_or_on_set_bit() {
130 let data = vec![0x01];
132 let result = flip_byte(&data, 0);
133 assert_eq!(result[0], 0x00);
134 }
135
136 #[test]
137 fn deterministic_der_arm0_truncation() {
138 let der = vec![0x30, 0x82, 0x01, 0x22, 0x10, 0x20];
139 let variant = find_der_variant(0);
140 let out = corrupt_der_deterministic(&der, &variant);
141 assert!(out.len() < der.len());
142 assert_eq!(&out[..], &der[..out.len()]);
143 }
144
145 #[test]
146 fn deterministic_der_arm1_flip() {
147 let der = vec![0x30, 0x82, 0x01, 0x22, 0x10, 0x20];
148 let variant = find_der_variant(1);
149 let out = corrupt_der_deterministic(&der, &variant);
150 assert_eq!(out.len(), der.len());
151 let diffs = out.iter().zip(der.iter()).filter(|(a, b)| a != b).count();
152 assert_eq!(diffs, 1);
153 }
154
155 #[test]
156 fn deterministic_der_arm2_flip_truncate() {
157 let der = vec![0x30, 0x82, 0x01, 0x22, 0x10, 0x20];
158 let variant = find_der_variant(2);
159 let out = corrupt_der_deterministic(&der, &variant);
160 assert!(out.len() < der.len());
161 }
162
163 #[test]
164 fn deterministic_der_not_constant() {
165 let der1 = vec![0x30, 0x82, 0x01, 0x22, 0x10, 0x20];
166 let der2 = vec![0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA];
167 let out1 = corrupt_der_deterministic(&der1, "same-variant");
168 let out2 = corrupt_der_deterministic(&der2, "same-variant");
169 assert_ne!(out1, out2);
170 }
171
172 #[test]
173 fn truncate_der_noop_when_len_exceeds_input() {
174 let der = vec![0x30, 0x82];
175 let out = truncate_der(&der, 100);
176 assert_eq!(
177 out, der,
178 "truncate with len >= der.len() must return original"
179 );
180 }
181
182 #[test]
183 fn flip_byte_noop_when_offset_exceeds_input() {
184 let der = vec![0x30, 0x82];
185 let out = flip_byte(&der, 100);
186 assert_eq!(
187 out, der,
188 "flip with offset >= der.len() must return original"
189 );
190 }
191
192 #[test]
193 fn derived_truncate_len_bytes_nonzero_for_large_input() {
194 let mut digest = [0u8; 32];
197 digest[2] = 50;
198 assert_eq!(derived_truncate_len_bytes(10, &digest), 5);
200 }
201
202 #[test]
203 fn derived_truncate_len_bytes_zero_len_returns_zero() {
204 let digest = [0xFF; 32];
205 assert_eq!(derived_truncate_len_bytes(0, &digest), 0);
206 }
207
208 fn find_der_variant(target: u8) -> String {
209 use uselesskey_core_hash::hash32;
210
211 for i in 0u64.. {
212 let variant = format!("v{i}");
213 if hash32(variant.as_bytes()).as_bytes()[0] % 3 == target {
214 return variant;
215 }
216 }
217 unreachable!()
218 }
219}