1use std::{borrow::Cow, error::Error, fmt, io::Cursor};
4
5use aead::{generic_array::GenericArray, Aead, AeadCore, Key, KeyInit};
6use aes_gcm::Aes256Gcm;
7use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
8use chacha20poly1305::ChaCha20Poly1305;
9use chrono::{prelude::*, Duration};
10use data_encoding::{BASE64, BASE64URL};
11use hmac::{Hmac, Mac};
12use rand::RngCore;
13use sha2::Sha256;
14
15type HmacSha256 = Hmac<Sha256>;
16
17#[derive(Debug, Hash, Eq, PartialEq, Clone)]
19pub enum CsrfError {
20 InternalError,
22 ValidationFailure(String),
24 EncryptionFailure(String),
26}
27
28impl Error for CsrfError {}
29
30impl fmt::Display for CsrfError {
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 match self {
33 CsrfError::InternalError => write!(f, "Library error"),
34 CsrfError::ValidationFailure(err) => write!(f, "Validation failed: {err}"),
35 CsrfError::EncryptionFailure(err) => write!(f, "Encryption failed: {err}"),
36 }
37 }
38}
39
40#[derive(Eq, PartialEq, Debug, Clone, Hash)]
42pub struct CsrfToken {
43 bytes: Vec<u8>,
44}
45
46impl CsrfToken {
47 pub fn new(bytes: Vec<u8>) -> Self {
49 CsrfToken { bytes }
51 }
52
53 pub fn b64_string(&self) -> String {
55 BASE64.encode(&self.bytes)
56 }
57
58 pub fn b64_url_string(&self) -> String {
60 BASE64URL.encode(&self.bytes)
61 }
62
63 pub fn value(&self) -> &[u8] {
65 &self.bytes
66 }
67}
68
69#[derive(Debug, Eq, PartialEq, Clone, Hash)]
71pub struct CsrfCookie {
72 bytes: Vec<u8>,
73}
74
75impl CsrfCookie {
76 pub fn new(bytes: Vec<u8>) -> Self {
78 CsrfCookie { bytes }
80 }
81
82 pub fn b64_string(&self) -> String {
84 BASE64.encode(&self.bytes)
85 }
86
87 pub fn value(&self) -> &[u8] {
89 &self.bytes
90 }
91}
92
93#[derive(Clone, Debug, Eq, PartialEq, Hash)]
95pub struct UnencryptedCsrfToken {
96 token: Vec<u8>,
97}
98
99impl UnencryptedCsrfToken {
100 pub fn new(token: Vec<u8>) -> Self {
102 UnencryptedCsrfToken { token }
103 }
104
105 #[deprecated]
107 pub fn token(&self) -> &[u8] {
108 &self.token
109 }
110
111 pub fn value(&self) -> &[u8] {
113 &self.token
114 }
115}
116
117#[derive(Clone, Debug, Eq, PartialEq, Hash)]
119pub struct UnencryptedCsrfCookie {
120 expires: i64,
121 token: Vec<u8>,
122}
123
124impl UnencryptedCsrfCookie {
125 pub fn new(expires: i64, token: Vec<u8>) -> Self {
127 UnencryptedCsrfCookie { expires, token }
128 }
129
130 pub fn value(&self) -> &[u8] {
132 &self.token
133 }
134}
135
136pub trait CsrfProtection: Send + Sync {
138 fn generate_cookie(
140 &self,
141 token_value: &[u8; 64],
142 ttl_seconds: i64,
143 ) -> Result<CsrfCookie, CsrfError>;
144
145 fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError>;
147
148 fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError>;
150
151 fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError>;
153
154 fn verify_token_pair(
157 &self,
158 token: &UnencryptedCsrfToken,
159 cookie: &UnencryptedCsrfCookie,
160 ) -> Result<(), CsrfError> {
161 if token.token != cookie.token {
162 return Err(CsrfError::ValidationFailure(format!(
163 "Token did not match cookie: T: {:?}, C: {:?}",
164 BASE64.encode(&token.token),
165 BASE64.encode(&cookie.token)
166 )));
167 }
168
169 let now = Utc::now().timestamp();
170 if cookie.expires <= now {
171 return Err(CsrfError::ValidationFailure(format!(
172 "Cookie expired. Expiration: {}, Current time: {}",
173 cookie.expires, now
174 )));
175 }
176
177 Ok(())
178 }
179
180 fn random_bytes(&self, buf: &mut [u8]) -> Result<(), CsrfError> {
182 rand::rngs::OsRng.fill_bytes(buf);
186 Ok(())
187 }
188
189 fn generate_token_pair(
191 &self,
192 previous_token_value: Option<&[u8; 64]>,
193 ttl_seconds: i64,
194 ) -> Result<(CsrfToken, CsrfCookie), CsrfError> {
195 let token: Cow<[u8; 64]> = match previous_token_value {
196 Some(v) => Cow::Borrowed(v),
197 None => {
198 let mut new_token = [0; 64];
199 self.random_bytes(&mut new_token)
200 .expect("Error filling random bytes");
201 Cow::Owned(new_token)
202 }
203 };
204
205 let generated_token = self.generate_token(&token)?;
206 let generated_cookie = self.generate_cookie(&token, ttl_seconds)?;
207 Ok((generated_token, generated_cookie))
208 }
209}
210
211#[derive(Clone)]
213pub struct HmacCsrfProtection {
214 hmac: HmacSha256,
215}
216
217impl HmacCsrfProtection {
218 pub fn new() -> Self {
220 HmacCsrfProtection {
221 hmac: <HmacSha256 as Mac>::new_from_slice(&HmacSha256::generate_key(
223 &mut rand::rngs::OsRng,
224 ))
225 .unwrap(),
226 }
227 }
228 pub fn from_key(hmac_key: [u8; 32]) -> Self {
230 HmacCsrfProtection {
231 hmac: <HmacSha256 as Mac>::new_from_slice(&hmac_key).unwrap(),
233 }
234 }
235}
236
237impl Default for HmacCsrfProtection {
238 fn default() -> Self {
239 Self::new()
240 }
241}
242
243impl CsrfProtection for HmacCsrfProtection {
244 fn generate_cookie(
245 &self,
246 token_value: &[u8; 64],
247 ttl_seconds: i64,
248 ) -> Result<CsrfCookie, CsrfError> {
249 let expires = (Utc::now() + Duration::seconds(ttl_seconds)).timestamp();
250 let mut expires_bytes = [0u8; 8];
251 (&mut expires_bytes[..])
252 .write_i64::<BigEndian>(expires)
253 .map_err(|_| CsrfError::InternalError)?;
254
255 let mut hmac = self.hmac.clone();
256 hmac.update(&expires_bytes);
257 hmac.update(token_value);
258 let mac = hmac.finalize();
259 let code = mac.into_bytes();
260
261 let mut transport = [0; 104];
262 transport[0..32].copy_from_slice(&code);
263 transport[32..40].copy_from_slice(&expires_bytes);
264 transport[40..].copy_from_slice(token_value);
265
266 Ok(CsrfCookie::new(transport.to_vec()))
267 }
268
269 fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError> {
270 let mut hmac = self.hmac.clone();
271 hmac.update(token_value);
272 let mac = hmac.finalize();
273 let code = mac.into_bytes();
274
275 let mut transport = [0; 96];
276 transport[0..32].copy_from_slice(&code);
277 transport[32..].copy_from_slice(token_value);
278
279 Ok(CsrfToken::new(transport.to_vec()))
280 }
281
282 fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError> {
283 if cookie.len() != 104 {
284 return Err(CsrfError::ValidationFailure(format!(
285 "Cookie wrong size. Not parsed. Cookie length {} != 104",
286 cookie.len()
287 )));
288 }
289
290 let mut hmac = self.hmac.clone();
291 hmac.update(&cookie[32..]);
292
293 hmac.verify_slice(&cookie[0..32])
294 .map_err(|err| CsrfError::ValidationFailure(format!("Cookie had bad MAC: {err}")))?;
295
296 let mut cur = Cursor::new(&cookie[32..40]);
297 let expires = cur
298 .read_i64::<BigEndian>()
299 .map_err(|_| CsrfError::InternalError)?;
300 Ok(UnencryptedCsrfCookie::new(expires, cookie[40..].to_vec()))
301 }
302
303 fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError> {
304 if token.len() != 96 {
305 return Err(CsrfError::ValidationFailure(format!(
306 "Token too small. Not parsed. Token length {} != 96",
307 token.len()
308 )));
309 }
310
311 let mut hmac = self.hmac.clone();
312 hmac.update(&token[32..]);
313
314 hmac.verify_slice(&token[0..32])
315 .map_err(|err| CsrfError::ValidationFailure(format!("Token had bad MAC: {err}")))?;
316
317 Ok(UnencryptedCsrfToken::new(token[32..].to_vec()))
318 }
319}
320
321#[derive(Clone)]
323pub struct AesGcmCsrfProtection {
324 aead: Aes256Gcm,
325}
326
327impl AesGcmCsrfProtection {
328 pub fn new() -> Self {
330 AesGcmCsrfProtection {
331 aead: {
332 let key = Aes256Gcm::generate_key(&mut rand::rngs::OsRng);
333 Aes256Gcm::new(&key)
334 },
335 }
336 }
337 pub fn from_key(aead_key: [u8; 32]) -> Self {
339 AesGcmCsrfProtection {
340 aead: {
341 let key = Key::<Aes256Gcm>::from_slice(&aead_key);
342 Aes256Gcm::new(key)
343 },
344 }
345 }
346}
347
348impl Default for AesGcmCsrfProtection {
349 fn default() -> Self {
350 Self::new()
351 }
352}
353
354impl CsrfProtection for AesGcmCsrfProtection {
355 fn generate_cookie(
356 &self,
357 token_value: &[u8; 64],
358 ttl_seconds: i64,
359 ) -> Result<CsrfCookie, CsrfError> {
360 let expires = (Utc::now() + Duration::seconds(ttl_seconds)).timestamp();
361 let mut expires_bytes = [0u8; 8];
362 (&mut expires_bytes[..])
363 .write_i64::<BigEndian>(expires)
364 .map_err(|_| CsrfError::InternalError)?;
365
366 let mut plaintext = [0; 104];
367 self.random_bytes(&mut plaintext[0..32])?; plaintext[32..40].copy_from_slice(&expires_bytes);
369 plaintext[40..].copy_from_slice(token_value);
370
371 let nonce = Aes256Gcm::generate_nonce(&mut rand::rngs::OsRng);
372
373 let ciphertext = self
374 .aead
375 .encrypt(&nonce, plaintext.as_ref())
376 .map_err(|err| {
377 CsrfError::EncryptionFailure(format!("Failed to encrypt cookie: {err}"))
378 })?;
379
380 let mut transport = [0; 132];
381 transport[0..12].copy_from_slice(&nonce);
382 transport[12..].copy_from_slice(&ciphertext);
383
384 Ok(CsrfCookie::new(transport.to_vec()))
385 }
386
387 fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError> {
388 let mut plaintext = [0; 96];
389 self.random_bytes(&mut plaintext[0..32])?; plaintext[32..].copy_from_slice(token_value);
391
392 let nonce = Aes256Gcm::generate_nonce(&mut rand::rngs::OsRng);
393
394 let ciphertext = self
395 .aead
396 .encrypt(&nonce, plaintext.as_ref())
397 .map_err(|err| {
398 CsrfError::EncryptionFailure(format!("Failed to encrypt token: {err}"))
399 })?;
400
401 let mut transport = [0; 124];
402 transport[0..12].copy_from_slice(&nonce);
403 transport[12..].copy_from_slice(&ciphertext);
404
405 Ok(CsrfToken::new(transport.to_vec()))
406 }
407
408 fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError> {
409 if cookie.len() != 132 {
410 return Err(CsrfError::ValidationFailure(format!(
411 "Cookie wrong size. Not parsed. Cookie length {} != 132",
412 cookie.len()
413 )));
414 }
415
416 let nonce = GenericArray::from_slice(&cookie[0..12]);
417
418 let plaintext = self
419 .aead
420 .decrypt(nonce, cookie[12..].as_ref())
421 .map_err(|err| {
422 CsrfError::ValidationFailure(format!("Failed to decrypt cookie: {err}"))
423 })?;
424
425 let mut cur = Cursor::new(&plaintext[32..40]);
426 let expires = cur
427 .read_i64::<BigEndian>()
428 .map_err(|_| CsrfError::InternalError)?;
429 Ok(UnencryptedCsrfCookie::new(
430 expires,
431 plaintext[40..].to_vec(),
432 ))
433 }
434
435 fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError> {
436 if token.len() != 124 {
437 return Err(CsrfError::ValidationFailure(format!(
438 "Token too small. Not parsed. Token length {} != 124",
439 token.len()
440 )));
441 }
442
443 let nonce = GenericArray::from_slice(&token[0..12]);
444
445 let plaintext = self
446 .aead
447 .decrypt(nonce, token[12..].as_ref())
448 .map_err(|err| {
449 CsrfError::ValidationFailure(format!("Failed to decrypt token: {err}"))
450 })?;
451
452 Ok(UnencryptedCsrfToken::new(plaintext[32..].to_vec()))
453 }
454}
455
456#[derive(Clone)]
458pub struct ChaCha20Poly1305CsrfProtection {
459 aead: ChaCha20Poly1305,
460}
461
462impl ChaCha20Poly1305CsrfProtection {
463 pub fn new() -> Self {
465 ChaCha20Poly1305CsrfProtection {
466 aead: ChaCha20Poly1305::new(&ChaCha20Poly1305::generate_key(&mut rand::rngs::OsRng)),
467 }
468 }
469 pub fn from_key(aead_key: [u8; 32]) -> Self {
471 ChaCha20Poly1305CsrfProtection {
472 aead: ChaCha20Poly1305::new_from_slice(&aead_key).unwrap(),
474 }
475 }
476}
477
478impl Default for ChaCha20Poly1305CsrfProtection {
479 fn default() -> Self {
480 Self::new()
481 }
482}
483
484impl CsrfProtection for ChaCha20Poly1305CsrfProtection {
485 fn generate_cookie(
486 &self,
487 token_value: &[u8; 64],
488 ttl_seconds: i64,
489 ) -> Result<CsrfCookie, CsrfError> {
490 let expires = (Utc::now() + Duration::seconds(ttl_seconds)).timestamp();
491 let mut expires_bytes = [0u8; 8];
492 (&mut expires_bytes[..])
493 .write_i64::<BigEndian>(expires)
494 .map_err(|_| CsrfError::InternalError)?;
495
496 let mut plaintext = [0; 104];
497 self.random_bytes(&mut plaintext[0..32])?; plaintext[32..40].copy_from_slice(&expires_bytes);
499 plaintext[40..].copy_from_slice(token_value);
500
501 let nonce = ChaCha20Poly1305::generate_nonce(&mut rand::rngs::OsRng);
502
503 let ciphertext = self
504 .aead
505 .encrypt(&nonce, plaintext.as_ref())
506 .map_err(|err| {
507 CsrfError::EncryptionFailure(format!("Failed to encrypt cookie: {err}"))
508 })?;
509
510 let mut transport = [0; 132];
511 transport[0..12].copy_from_slice(&nonce);
512 transport[12..].copy_from_slice(&ciphertext);
513
514 Ok(CsrfCookie::new(transport.to_vec()))
515 }
516
517 fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError> {
518 let mut plaintext = [0; 96];
519 self.random_bytes(&mut plaintext[0..32])?; plaintext[32..].copy_from_slice(token_value);
521
522 let nonce = ChaCha20Poly1305::generate_nonce(&mut rand::rngs::OsRng);
523
524 let ciphertext = self
525 .aead
526 .encrypt(&nonce, plaintext.as_ref())
527 .map_err(|err| {
528 CsrfError::EncryptionFailure(format!("Failed to encrypt token: {err}"))
529 })?;
530
531 let mut transport = [0; 124];
532 transport[0..12].copy_from_slice(&nonce);
533 transport[12..].copy_from_slice(&ciphertext);
534
535 Ok(CsrfToken::new(transport.to_vec()))
536 }
537
538 fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError> {
539 if cookie.len() != 132 {
540 return Err(CsrfError::ValidationFailure(format!(
541 "Cookie wrong size. Not parsed. Cookie length {} != 132",
542 cookie.len()
543 )));
544 }
545
546 let nonce = GenericArray::from_slice(&cookie[0..12]);
547
548 let plaintext = self
549 .aead
550 .decrypt(nonce, cookie[12..].as_ref())
551 .map_err(|err| {
552 CsrfError::ValidationFailure(format!("Failed to decrypt cookie: {err}"))
553 })?;
554
555 let mut cur = Cursor::new(&plaintext[32..40]);
556 let expires = cur
557 .read_i64::<BigEndian>()
558 .map_err(|_| CsrfError::InternalError)?;
559 Ok(UnencryptedCsrfCookie::new(
560 expires,
561 plaintext[40..].to_vec(),
562 ))
563 }
564
565 fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError> {
566 if token.len() != 124 {
567 return Err(CsrfError::ValidationFailure(format!(
568 "Token too small. Not parsed. Token length {} != 124",
569 token.len()
570 )));
571 }
572
573 let nonce = GenericArray::from_slice(&token[0..12]);
574
575 let plaintext = self
576 .aead
577 .decrypt(nonce, token[12..].as_ref())
578 .map_err(|err| {
579 CsrfError::ValidationFailure(format!("Failed to decrypt token: {err}"))
580 })?;
581
582 Ok(UnencryptedCsrfToken::new(plaintext[32..].to_vec()))
583 }
584}
585
586pub struct MultiCsrfProtection {
591 current: Box<dyn CsrfProtection>,
592 previous: Vec<Box<dyn CsrfProtection>>,
593}
594
595impl MultiCsrfProtection {
596 pub fn new(current: Box<dyn CsrfProtection>, previous: Vec<Box<dyn CsrfProtection>>) -> Self {
599 Self { current, previous }
600 }
601}
602
603impl CsrfProtection for MultiCsrfProtection {
604 fn generate_cookie(
605 &self,
606 token_value: &[u8; 64],
607 ttl_seconds: i64,
608 ) -> Result<CsrfCookie, CsrfError> {
609 self.current.generate_cookie(token_value, ttl_seconds)
610 }
611
612 fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError> {
613 self.current.generate_token(token_value)
614 }
615
616 fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError> {
617 match self.current.parse_cookie(cookie) {
618 ok @ Ok(_) => ok,
619 Err(_) => {
620 for protection in self.previous.iter() {
621 if let ok @ Ok(_) = protection.parse_cookie(cookie) {
622 return ok;
623 }
624 }
625 Err(CsrfError::ValidationFailure(
626 "Failed to validate the cookie against all provided keys".to_owned(),
627 ))
628 }
629 }
630 }
631
632 fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError> {
633 match self.current.parse_token(token) {
634 ok @ Ok(_) => ok,
635 Err(_) => {
636 for protection in self.previous.iter() {
637 if let ok @ Ok(_) = protection.parse_token(token) {
638 return ok;
639 }
640 }
641 Err(CsrfError::ValidationFailure(
642 "Failed to validate the token against all provided keys".to_owned(),
643 ))
644 }
645 }
646 }
647}
648
649#[cfg(feature = "iron")]
650impl typemap::Key for CsrfToken {
651 type Value = CsrfToken;
652}
653
654#[cfg(test)]
655mod tests {
656 const KEY_32: [u8; 32] = *b"01234567012345670123456701234567";
660 const KEY2_32: [u8; 32] = *b"76543210765432107654321076543210";
661
662 macro_rules! test_cases {
663 ($strct: ident, $md: ident) => {
664 mod $md {
665 use super::KEY_32;
666 use data_encoding::BASE64;
667 use $crate::{$strct, CsrfProtection};
668
669 #[test]
670 fn verification_succeeds() {
671 let protect = $strct::from_key(KEY_32);
672 let (token, cookie) = protect
673 .generate_token_pair(None, 300)
674 .expect("couldn't generate token/cookie pair");
675 let token = &BASE64
676 .decode(token.b64_string().as_bytes())
677 .expect("token not base64");
678 let token = protect.parse_token(&token).expect("token not parsed");
679 let cookie = &BASE64
680 .decode(cookie.b64_string().as_bytes())
681 .expect("cookie not base64");
682 let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
683 assert!(
684 protect.verify_token_pair(&token, &cookie).is_ok(),
685 "could not verify token/cookie pair"
686 );
687 }
688
689 #[test]
690 fn modified_cookie_value_fails() {
691 let protect = $strct::from_key(KEY_32);
692 let (_, mut cookie) = protect
693 .generate_token_pair(None, 300)
694 .expect("couldn't generate token/cookie pair");
695 cookie.bytes[0] ^= 0x01;
696 let cookie = &BASE64
697 .decode(cookie.b64_string().as_bytes())
698 .expect("cookie not base64");
699 assert!(protect.parse_cookie(&cookie).is_err());
700 }
701
702 #[test]
703 fn modified_token_value_fails() {
704 let protect = $strct::from_key(KEY_32);
705 let (mut token, _) = protect
706 .generate_token_pair(None, 300)
707 .expect("couldn't generate token/token pair");
708 token.bytes[0] ^= 0x01;
709 let token = &BASE64
710 .decode(token.b64_string().as_bytes())
711 .expect("token not base64");
712 assert!(protect.parse_token(&token).is_err());
713 }
714
715 #[test]
716 fn mismatched_cookie_token_fail() {
717 let protect = $strct::from_key(KEY_32);
718 let (token, _) = protect
719 .generate_token_pair(None, 300)
720 .expect("couldn't generate token/token pair");
721 let (_, cookie) = protect
722 .generate_token_pair(None, 300)
723 .expect("couldn't generate token/token pair");
724
725 let token = &BASE64
726 .decode(token.b64_string().as_bytes())
727 .expect("token not base64");
728 let token = protect.parse_token(&token).expect("token not parsed");
729 let cookie = &BASE64
730 .decode(cookie.b64_string().as_bytes())
731 .expect("cookie not base64");
732 let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
733 assert!(
734 !protect.verify_token_pair(&token, &cookie).is_ok(),
735 "verified token/cookie pair when failure expected"
736 );
737 }
738
739 #[test]
740 fn expired_token_fail() {
741 let protect = $strct::from_key(KEY_32);
742 let (token, cookie) = protect
743 .generate_token_pair(None, -1)
744 .expect("couldn't generate token/cookie pair");
745 let token = &BASE64
746 .decode(token.b64_string().as_bytes())
747 .expect("token not base64");
748 let token = protect.parse_token(&token).expect("token not parsed");
749 let cookie = &BASE64
750 .decode(cookie.b64_string().as_bytes())
751 .expect("cookie not base64");
752 let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
753 assert!(
754 !protect.verify_token_pair(&token, &cookie).is_ok(),
755 "verified token/cookie pair when failure expected"
756 );
757 }
758 }
759 };
760 }
761
762 test_cases!(AesGcmCsrfProtection, aesgcm);
763 test_cases!(ChaCha20Poly1305CsrfProtection, chacha20poly1305);
764 test_cases!(HmacCsrfProtection, hmac);
765
766 mod multi {
767 macro_rules! test_cases {
768 ($strct1: ident, $strct2: ident, $name: ident) => {
769 mod $name {
770 use super::super::{super::*, KEY2_32, KEY_32};
771 use data_encoding::BASE64;
772
773 #[test]
774 fn no_previous() {
775 let protect = $strct1::from_key(KEY_32);
776 let mut pairs = vec![];
777 let pair = protect
778 .generate_token_pair(None, 300)
779 .expect("couldn't generate token/cookie pair");
780 pairs.push(pair);
781
782 let protect = MultiCsrfProtection::new(Box::new(protect), vec![]);
783 let pair = protect
784 .generate_token_pair(None, 300)
785 .expect("couldn't generate token/cookie pair");
786 pairs.push(pair);
787
788 for &(ref token, ref cookie) in pairs.iter() {
789 let token = &BASE64
790 .decode(token.b64_string().as_bytes())
791 .expect("token not base64");
792 let token = protect.parse_token(&token).expect("token not parsed");
793 let cookie = &BASE64
794 .decode(cookie.b64_string().as_bytes())
795 .expect("cookie not base64");
796 let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
797 assert!(
798 protect.verify_token_pair(&token, &cookie).is_ok(),
799 "could not verify token/cookie pair"
800 );
801 }
802 }
803
804 #[test]
805 fn $name() {
806 let protect_1 = $strct1::from_key(KEY_32);
807 let mut pairs = vec![];
808 let pair = protect_1
809 .generate_token_pair(None, 300)
810 .expect("couldn't generate token/cookie pair");
811 pairs.push(pair);
812
813 let protect_2 = $strct2::from_key(KEY2_32);
814 let mut pairs = vec![];
815 let pair = protect_2
816 .generate_token_pair(None, 300)
817 .expect("couldn't generate token/cookie pair");
818 pairs.push(pair);
819
820 let protect = MultiCsrfProtection::new(
821 Box::new(protect_1),
822 vec![Box::new(protect_2)],
823 );
824 let pair = protect
825 .generate_token_pair(None, 300)
826 .expect("couldn't generate token/cookie pair");
827 pairs.push(pair);
828
829 for &(ref token, ref cookie) in pairs.iter() {
830 let token = &BASE64
831 .decode(token.b64_string().as_bytes())
832 .expect("token not base64");
833 let token = protect.parse_token(&token).expect("token not parsed");
834 let cookie = &BASE64
835 .decode(cookie.b64_string().as_bytes())
836 .expect("cookie not base64");
837 let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
838 assert!(
839 protect.verify_token_pair(&token, &cookie).is_ok(),
840 "could not verify token/cookie pair"
841 );
842 }
843 }
844 }
845 };
846 }
847
848 test_cases!(
849 AesGcmCsrfProtection,
850 AesGcmCsrfProtection,
851 aesgcm_then_aesgcm
852 );
853
854 test_cases!(
855 ChaCha20Poly1305CsrfProtection,
856 ChaCha20Poly1305CsrfProtection,
857 chacha20poly1305_then_chacha20poly1305
858 );
859
860 test_cases!(HmacCsrfProtection, HmacCsrfProtection, hmac_then_hmac);
861
862 test_cases!(
863 ChaCha20Poly1305CsrfProtection,
864 AesGcmCsrfProtection,
865 chacha20poly1305_then_aesgcm
866 );
867
868 test_cases!(HmacCsrfProtection, AesGcmCsrfProtection, hmac_then_aesgcm);
869
870 test_cases!(
871 AesGcmCsrfProtection,
872 ChaCha20Poly1305CsrfProtection,
873 aesgcm_then_chacha20poly1305
874 );
875 test_cases!(
876 HmacCsrfProtection,
877 ChaCha20Poly1305CsrfProtection,
878 hmac_then_chacha20poly1305
879 );
880
881 test_cases!(AesGcmCsrfProtection, HmacCsrfProtection, aesgcm_then_hmac);
882 test_cases!(
883 ChaCha20Poly1305CsrfProtection,
884 HmacCsrfProtection,
885 chacha20poly1305_then_hmac
886 );
887 }
888}