1use cipher::inout::InOut;
6use cipher::typenum::consts::U16;
7use cipher::{
8 Array, BlockCipherDecBackend, BlockCipherDecClosure, BlockCipherEncBackend,
9 BlockCipherEncClosure, BlockSizeUser,
10};
11use static_assertions::assert_cfg;
12use storage_ptr_slice::{MutPtrByteSlice, PtrByteSlice};
13use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
14
15assert_cfg!(target_endian = "little");
17
18#[derive(IntoBytes, KnownLayout, FromBytes, Immutable, Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(C)]
20pub struct Tweak(pub u128);
21
22impl Tweak {
23 pub fn new(val: u128) -> Self {
24 Self(val)
25 }
26
27 fn update(&mut self) {
28 self.0 = (self.0 << 1) ^ ((self.0 as i128 >> 127) as u128 & 0x87);
29 }
30}
31
32pub struct XtsProcessor<'a, 'b> {
34 tweak: Tweak,
35 src: PtrByteSlice<'a>,
36 dst: MutPtrByteSlice<'b>,
37}
38
39impl<'a, 'b> XtsProcessor<'a, 'b> {
40 pub fn new(tweak: Tweak, src: PtrByteSlice<'a>, dst: MutPtrByteSlice<'b>) -> Self {
43 assert_eq!(src.len(), dst.len(), "Source and destination lengths must match");
44 assert!(src.as_ptr().cast::<u128>().is_aligned(), "src must be 16 byte aligned");
45 assert!(dst.as_ptr().cast::<u128>().is_aligned(), "dst must be 16 byte aligned");
46 Self { tweak, src, dst }
47 }
48
49 pub fn new_in_place(tweak: Tweak, buf: MutPtrByteSlice<'a>) -> XtsProcessor<'a, 'a> {
51 assert!(buf.as_ptr().cast::<u128>().is_aligned(), "buf must be 16 byte aligned");
52 let len = buf.len();
53 let ptr = buf.as_ptr_slice().as_ptr();
54 let src = unsafe { PtrByteSlice::new(std::ptr::slice_from_raw_parts(ptr, len)) };
59 XtsProcessor { tweak, src, dst: buf }
60 }
61}
62
63impl BlockSizeUser for XtsProcessor<'_, '_> {
64 type BlockSize = U16;
65}
66
67impl BlockCipherEncClosure for XtsProcessor<'_, '_> {
68 fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
69 let Self { mut tweak, src, mut dst } = self;
70 let src_chunks = src.chunks::<u128>();
71 let dst_chunks = dst.chunks_mut::<u128>();
72
73 for (src_chunk, dst_chunk) in src_chunks.zip(dst_chunks) {
74 let mut val = src_chunk.read();
75
76 val ^= tweak.0;
78
79 let arr: &mut Array<u8, U16> = val.as_mut_bytes().try_into().unwrap();
80 backend.encrypt_block(InOut::from(arr));
81
82 val ^= tweak.0;
84
85 dst_chunk.write(val);
86
87 tweak.update();
88 }
89 }
90}
91
92impl BlockCipherDecClosure for XtsProcessor<'_, '_> {
93 fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
94 let Self { mut tweak, src, mut dst } = self;
95 let src_chunks = src.chunks::<u128>();
96 let dst_chunks = dst.chunks_mut::<u128>();
97
98 for (src_chunk, dst_chunk) in src_chunks.zip(dst_chunks) {
99 let mut val = src_chunk.read();
100
101 val ^= tweak.0;
103
104 let arr: &mut Array<u8, U16> = val.as_mut_bytes().try_into().unwrap();
105 backend.decrypt_block(InOut::from(arr));
106
107 val ^= tweak.0;
109
110 dst_chunk.write(val);
111
112 tweak.update();
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use cipher::inout::InOut;
121 use cipher::typenum::consts::U1;
122 use cipher::{Block, ParBlocksSizeUser};
123 use std::cell::RefCell;
124
125 struct MockCipher {
126 recorded_blocks: RefCell<Vec<u128>>,
127 key: u128,
128 }
129
130 impl MockCipher {
131 fn new(key: u128) -> Self {
132 Self { recorded_blocks: RefCell::new(Vec::new()), key }
133 }
134 }
135
136 impl BlockSizeUser for MockCipher {
137 type BlockSize = U16;
138 }
139
140 impl ParBlocksSizeUser for MockCipher {
141 type ParBlocksSize = U1;
142 }
143
144 impl BlockCipherEncBackend for MockCipher {
145 fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
146 let mut val =
148 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
149 self.recorded_blocks.borrow_mut().push(val);
150 val ^= self.key;
151 unsafe {
153 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
154 }
155 }
156 }
157
158 #[repr(C)]
159 #[derive(FromBytes, IntoBytes, Immutable)]
160 struct Blocks<const N: usize>([u128; N]);
161
162 static_assertions::const_assert!(std::mem::align_of::<Blocks<1>>() == 16);
163 static_assertions::const_assert!(std::mem::align_of::<Blocks<2>>() == 16);
164
165 impl<const N: usize> Default for Blocks<N> {
166 fn default() -> Self {
167 Self([0u128; N])
168 }
169 }
170
171 #[test]
172 fn test_xts_out_of_place() {
173 let mut plaintext: Blocks<2> = Default::default();
174 for (i, x) in plaintext.as_mut_bytes().iter_mut().enumerate() {
175 *x = i as u8;
176 }
177 let mut ciphertext: Blocks<2> = Default::default();
178
179 let src = PtrByteSlice::from(plaintext.as_bytes());
180 let dst = MutPtrByteSlice::from(ciphertext.as_mut_bytes());
181
182 let tweak_val = 0x123456789abcdef0123456789abcdef0u128;
183 let tweak = Tweak::new(tweak_val);
184 let key = 0xffeeddccbbaa99887766554433221100u128;
185
186 let processor = XtsProcessor::new(tweak, src, dst);
187 let cipher = MockCipher::new(key);
188
189 BlockCipherEncClosure::call(processor, &cipher);
190
191 let expected_c0 =
195 u128::from_le_bytes(plaintext.as_bytes()[0..16].try_into().unwrap()) ^ key;
196 let expected_c1 =
197 u128::from_le_bytes(plaintext.as_bytes()[16..32].try_into().unwrap()) ^ key;
198
199 let actual_c0 = u128::from_le_bytes(ciphertext.as_bytes()[0..16].try_into().unwrap());
200 let actual_c1 = u128::from_le_bytes(ciphertext.as_bytes()[16..32].try_into().unwrap());
201
202 assert_eq!(actual_c0, expected_c0);
203 assert_eq!(actual_c1, expected_c1);
204
205 assert_eq!(cipher.recorded_blocks.borrow().len(), 2);
207
208 let p0 = u128::from_le_bytes(plaintext.as_bytes()[0..16].try_into().unwrap());
209 let p1 = u128::from_le_bytes(plaintext.as_bytes()[16..32].try_into().unwrap());
210
211 let mut t0 = tweak;
212 assert_eq!(cipher.recorded_blocks.borrow()[0], p0 ^ t0.0);
213 t0.update();
214 assert_eq!(cipher.recorded_blocks.borrow()[1], p1 ^ t0.0);
215 }
216
217 #[test]
218 fn test_xts_in_place() {
219 let mut buf: Blocks<2> = Default::default();
220 for (i, x) in buf.as_mut_bytes().iter_mut().enumerate() {
221 *x = i as u8;
222 }
223
224 let tweak_val = 0x123456789abcdef0123456789abcdef0u128;
225 let tweak = Tweak::new(tweak_val);
226 let key = 0xffeeddccbbaa99887766554433221100u128;
227
228 let p0 = u128::from_le_bytes(buf.as_bytes()[0..16].try_into().unwrap());
230 let p1 = u128::from_le_bytes(buf.as_bytes()[16..32].try_into().unwrap());
231
232 let slice = MutPtrByteSlice::from(buf.as_mut_bytes());
233 let processor = XtsProcessor::new_in_place(tweak, slice);
234 let cipher = MockCipher::new(key);
235
236 BlockCipherEncClosure::call(processor, &cipher);
237
238 let expected_c0 = p0 ^ key;
240 let expected_c1 = p1 ^ key;
241
242 let actual_c0 = u128::from_le_bytes(buf.as_bytes()[0..16].try_into().unwrap());
243 let actual_c1 = u128::from_le_bytes(buf.as_bytes()[16..32].try_into().unwrap());
244
245 assert_eq!(actual_c0, expected_c0);
246 assert_eq!(actual_c1, expected_c1);
247
248 assert_eq!(cipher.recorded_blocks.borrow().len(), 2);
250 let mut t0 = tweak;
251 assert_eq!(cipher.recorded_blocks.borrow()[0], p0 ^ t0.0);
252 t0.update();
253 assert_eq!(cipher.recorded_blocks.borrow()[1], p1 ^ t0.0);
254 }
255}