1#![cfg_attr(docsrs, feature(doc_cfg))]
6#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
7
8pub mod rust_crypto;
9
10use core::{error, fmt, hash::Hash};
11
12use inout::InOutBuf;
13use polyhash::ghash::GHash;
14use snowv::SnowV;
15use subtle::ConstantTimeEq;
16
17cfg_if::cfg_if! {
18 if #[cfg(feature = "zeroize")] {
19 use zeroize::Zeroizing;
20 } else {
21 struct Zeroizing<T>(T);
22 impl<T> Zeroizing<T> {
23 #[inline(always)]
24 #[allow(clippy::new_ret_no_self)]
25 fn new(t: T) -> T {
26 t
27 }
28 }
29 }
30}
31
32#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
34pub struct Error;
35
36impl error::Error for Error {}
37
38impl fmt::Display for Error {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "SNOW-V-GCM error")
41 }
42}
43
44impl From<snowv::Error> for Error {
45 #[inline]
46 fn from(_err: snowv::Error) -> Self {
47 Self
48 }
49}
50
51pub const KEY_SIZE: usize = 32;
53
54pub const NONCE_SIZE: usize = 16;
56
57pub const TAG_SIZE: usize = 16;
59
60pub const P_MAX: u64 = (snowv::MAX_BLOCKS * snowv::BLOCK_SIZE as u64) / 8;
62
63pub const C_MAX: u64 = P_MAX + TAG_SIZE as u64;
65
66pub const A_MAX: u64 = u64::MAX / 8;
68
69pub type Key = [u8; KEY_SIZE];
71
72pub type Nonce = [u8; NONCE_SIZE];
74
75pub type Tag = [u8; TAG_SIZE];
77
78#[derive(Clone)]
80pub struct SnowVGcm {
81 key: [u8; KEY_SIZE],
82}
83
84impl SnowVGcm {
85 #[inline]
87 pub fn new(key: &Key) -> Self {
88 Self { key: *key }
89 }
90
91 #[inline]
95 pub fn seal(
96 &self,
97 nonce: &Nonce,
98 mut data: InOutBuf<'_, '_, u8>,
99 additional_data: &[u8],
100 ) -> Result<Tag, Error> {
101 if !less_or_equal(data.len(), P_MAX) || !less_or_equal(additional_data.len(), A_MAX) {
102 return Err(Error);
103 }
104
105 let mut cipher = SnowV::new_for_aead(&self.key, nonce);
106
107 let mut ghash_key = Zeroizing::new([0; 16]); cipher.write_keystream_block(&mut ghash_key)?;
109
110 let mut mask = Zeroizing::new([0; 16]); cipher.write_keystream_block(&mut mask)?;
112
113 cipher.apply_keystream(data.reborrow())?;
114
115 let tag = self.compute_tag(&ghash_key, &mask, data.get_out(), additional_data);
116
117 Ok(tag)
118 }
119
120 #[inline]
124 pub fn open(
125 &self,
126 nonce: &Nonce,
127 data: InOutBuf<'_, '_, u8>,
128 tag: &Tag,
129 additional_data: &[u8],
130 ) -> Result<(), Error> {
131 if !less_or_equal(data.len(), C_MAX) || !less_or_equal(additional_data.len(), A_MAX) {
132 return Err(Error);
133 }
134
135 let mut cipher = SnowV::new_for_aead(&self.key, nonce);
136
137 let mut ghash_key = Zeroizing::new([0; 16]); cipher.write_keystream_block(&mut ghash_key)?;
139
140 let mut mask = Zeroizing::new([0; 16]); cipher.write_keystream_block(&mut mask)?;
142
143 let expected_tag = self.compute_tag(&ghash_key, &mask, data.get_in(), additional_data);
144 if !bool::from(expected_tag.ct_eq(tag)) {
145 return Err(Error);
146 }
147
148 cipher.apply_keystream(data)?;
149
150 Ok(())
151 }
152
153 #[allow(
154 clippy::arithmetic_side_effects,
155 reason = "We checked the lengths of `ct` and `at`"
156 )]
157 fn compute_tag(&self, ghash_key: &[u8; 16], mask: &[u8; 16], ct: &[u8], ad: &[u8]) -> Tag {
158 let mut ghash = GHash::new_unchecked(ghash_key);
159 ghash.update_padded(ad);
160 ghash.update_padded(ct);
161
162 let ad_bits = (ad.len() as u64) * 8;
163 let ct_bits = (ct.len() as u64) * 8;
164
165 let mut lengths = [0u8; 16];
166 lengths[..8].copy_from_slice(&ad_bits.to_be_bytes());
167 lengths[8..].copy_from_slice(&ct_bits.to_be_bytes());
168 ghash.update_block(&lengths);
169
170 let mut tag: [u8; 16] = ghash.tag().into();
171 for (t, m) in tag.iter_mut().zip(mask) {
172 *t ^= *m;
173 }
174 tag
175 }
176}
177
178impl fmt::Debug for SnowVGcm {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 f.debug_struct("SnowVGcm").finish_non_exhaustive()
181 }
182}
183
184#[inline(always)]
186fn less_or_equal(x: usize, y: u64) -> bool {
187 u64::try_from(x).is_ok_and(|n| n <= y)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 macro_rules! hex {
195 ($($s:literal)*) => {
196 &hex_literal::hex!($($s)*)
197 };
198 }
199
200 #[test]
201 fn test_round_trip() {
202 const PLAINTEXT: &[u8] = b"hello, world!";
203
204 let aead = SnowVGcm::new(&[0; KEY_SIZE]);
205 let nonce = [0; NONCE_SIZE];
206 let ad = b"additional data";
207 let mut data = PLAINTEXT.to_vec();
208 let tag = aead.seal(&nonce, data.as_mut_slice().into(), ad).unwrap();
209 aead.open(&nonce, data.as_mut_slice().into(), &tag, ad)
210 .unwrap();
211 assert_eq!(data, PLAINTEXT);
212 }
213
214 #[test]
215 fn test_vectors() {
216 type Test<'a> = (
217 &'a Key, &'a Nonce, &'a [u8], &'a [u8], &'a [u8], &'a Tag, );
224 const TESTS: &[Test<'_>] = &[
225 (
226 hex!(
227 "00000000000000000000000000000000"
228 "00000000000000000000000000000000"
229 ),
230 hex!("00000000000000000000000000000000"),
231 &[],
232 &[],
233 &[],
234 hex!("029a624cdaa4d46cb9a0ef4046956c9f"),
235 ),
236 (
237 hex!(
238 "505152535455565758595a5b5c5d5e5f"
239 "0a1a2a3a4a5a6a7a8a9aaabacadaeafa"
240 ),
241 hex!("0123456789abcdeffedcba9876543210"),
242 &[],
243 &[],
244 &[],
245 hex!("fc7cac574c49feae6150315b9685424c"),
246 ),
247 (
248 hex!(
249 "00000000000000000000000000000000"
250 "00000000000000000000000000000000"
251 ),
252 hex!("00000000000000000000000000000000"),
253 hex!("30313233343536373839616263646566"),
254 &[],
255 &[],
256 hex!("5a5aa5fbd635ef1ae129614203e10384"),
257 ),
258 (
259 hex!(
260 "505152535455565758595a5b5c5d5e5f"
261 "0a1a2a3a4a5a6a7a8a9aaabacadaeafa"
262 ),
263 hex!("0123456789abcdeffedcba9876543210"),
264 hex!("30313233343536373839616263646566"),
265 &[],
266 &[],
267 hex!("250ec8d77a022c087adf08b65adcbb1a"),
268 ),
269 (
270 hex!(
271 "505152535455565758595a5b5c5d5e5f"
272 "0a1a2a3a4a5a6a7a8a9aaabacadaeafa"
273 ),
274 hex!("0123456789abcdeffedcba9876543210"),
275 &[],
276 hex!("30313233343536373839"),
277 hex!("dd7e01b2b424a2ef8250"),
278 hex!("ddfe4e31e7bfe6902331ec5ce319d90d"),
279 ),
280 (
281 hex!(
282 "505152535455565758595a5b5c5d5e5f"
283 "0a1a2a3a4a5a6a7a8a9aaabacadaeafa"
284 ),
285 hex!("0123456789abcdeffedcba9876543210"),
286 hex!("41414420746573742076616c756521"),
287 hex!(
288 "30313233343536373839616263646566"
289 "20536e6f77562d41454144206d6f6465"
290 "21"
291 ),
292 hex!(
293 "dd7e01b2b424a2ef82502707e87a32c1"
294 "52b0d01818fd7f12243eb5a15659e91b"
295 "4c"
296 ),
297 hex!("907ea6a5b73a51de747c3e9ad9ee029b"),
298 ),
299 ];
300 for (i, &(key, nonce, ad, pt, ct, tag)) in TESTS.iter().enumerate() {
301 let mut data = pt.to_vec();
302
303 let aead = SnowVGcm::new(key);
304 let got_tag = aead.seal(nonce, data.as_mut_slice().into(), ad).unwrap();
305 assert_eq!(&got_tag, tag, "#{i}: incorrect tag");
306 assert_eq!(data, ct, "#{i}: incorrect ciphertext");
307 aead.open(nonce, data.as_mut_slice().into(), tag, ad)
308 .unwrap();
309 assert_eq!(data, pt, "#{i}: incorrect plaintext");
310
311 #[cfg(feature = "rust-crypto")]
312 {
313 use aead::{AeadInPlace, KeyInit};
314
315 let aead = <SnowVGcm as KeyInit>::new(key.into());
316 let got_tag = aead
317 .encrypt_in_place_detached(nonce.into(), ad, &mut data)
318 .unwrap();
319 assert_eq!(got_tag.as_slice(), tag, "#{i}: incorrect tag");
320 assert_eq!(data, ct, "#{i}: incorrect ciphertext");
321 aead.decrypt_in_place_detached(nonce.into(), ad, data.as_mut_slice(), tag.into())
322 .unwrap();
323 assert_eq!(data, pt, "#{i}: incorrect plaintext");
324 }
325 }
326 }
327}