Skip to main content

pasetors/
version2.rs

1#![cfg_attr(docsrs, doc(cfg(feature = "v2")))]
2
3use crate::common::{encode_b64, validate_footer_untrusted_token};
4use crate::errors::Error;
5use crate::keys::{
6    AsymmetricKeyPair, AsymmetricPublicKey, AsymmetricSecretKey, Generate, SymmetricKey,
7};
8use crate::pae;
9use crate::token::{Local, Public, TrustedToken, UntrustedToken};
10use crate::version::private::Version;
11use alloc::string::String;
12use alloc::vec::Vec;
13use core::convert::TryFrom;
14use core::marker::PhantomData;
15use ed25519_compact::{KeyPair, PublicKey, SecretKey as SigningKey, Seed, Signature};
16use orion::hazardous::aead::xchacha20poly1305::*;
17use orion::hazardous::mac::blake2b;
18use orion::hazardous::mac::poly1305::POLY1305_OUTSIZE;
19use orion::hazardous::stream::xchacha20::XCHACHA_NONCESIZE;
20use subtle::ConstantTimeEq;
21
22#[derive(Debug, PartialEq, Eq, Clone)]
23/// Version 2 of the PASETO spec.
24pub struct V2;
25
26impl Version for V2 {
27    const LOCAL_KEY: usize = 32;
28    const SECRET_KEY: usize = 32 + Self::PUBLIC_KEY; // Seed || PK
29    const PUBLIC_KEY: usize = 32;
30    const PUBLIC_SIG: usize = 64;
31    const LOCAL_NONCE: usize = 24;
32    const LOCAL_TAG: usize = 16;
33    const PUBLIC_HEADER: &'static str = "v2.public.";
34    const LOCAL_HEADER: &'static str = "v2.local.";
35    #[cfg(feature = "paserk")]
36    const PASERK_ID: usize = 44;
37
38    fn validate_local_key(key_bytes: &[u8]) -> Result<(), Error> {
39        if key_bytes.len() != Self::LOCAL_KEY {
40            return Err(Error::Key);
41        }
42
43        Ok(())
44    }
45
46    fn validate_secret_key(key_bytes: &[u8]) -> Result<(), Error> {
47        if key_bytes.len() != Self::SECRET_KEY {
48            return Err(Error::Key);
49        }
50
51        let seed = Seed::from_slice(&key_bytes[..32]).map_err(|_| Error::Key)?;
52        let kp = KeyPair::from_seed(seed);
53
54        if !bool::from(kp.pk.as_slice().ct_eq(&key_bytes[32..])) {
55            return Err(Error::Key);
56        }
57
58        Ok(())
59    }
60
61    fn validate_public_key(key_bytes: &[u8]) -> Result<(), Error> {
62        if key_bytes.len() != Self::PUBLIC_KEY {
63            return Err(Error::Key);
64        }
65
66        Ok(())
67    }
68}
69
70impl TryFrom<&AsymmetricSecretKey<V2>> for AsymmetricPublicKey<V2> {
71    type Error = Error;
72
73    fn try_from(value: &AsymmetricSecretKey<V2>) -> Result<Self, Self::Error> {
74        AsymmetricPublicKey::<V2>::from(&value.as_bytes()[32..])
75    }
76}
77
78impl Generate<AsymmetricKeyPair<V2>, V2> for AsymmetricKeyPair<V2> {
79    fn generate() -> Result<AsymmetricKeyPair<V2>, Error> {
80        let key_pair = KeyPair::generate();
81
82        let secret = AsymmetricSecretKey::<V2>::from(key_pair.sk.as_ref())
83            .map_err(|_| Error::KeyGeneration)?;
84        let public = AsymmetricPublicKey::<V2>::from(key_pair.pk.as_ref())
85            .map_err(|_| Error::KeyGeneration)?;
86
87        Ok(Self { public, secret })
88    }
89}
90
91impl Generate<SymmetricKey<V2>, V2> for SymmetricKey<V2> {
92    fn generate() -> Result<SymmetricKey<V2>, Error> {
93        let mut rng_bytes = vec![0u8; V2::LOCAL_KEY];
94        V2::validate_local_key(&rng_bytes)?;
95        getrandom::fill(&mut rng_bytes)?;
96
97        Ok(Self {
98            bytes: rng_bytes,
99            phantom: PhantomData,
100        })
101    }
102}
103
104/// PASETO v2 public tokens.
105pub struct PublicToken;
106
107impl PublicToken {
108    /// The header and purpose for the public token: `v2.public.`.
109    pub const HEADER: &'static str = "v2.public.";
110
111    /// Create a public token.
112    pub fn sign(
113        secret_key: &AsymmetricSecretKey<V2>,
114        message: &[u8],
115        footer: Option<&[u8]>,
116    ) -> Result<String, Error> {
117        if message.is_empty() {
118            return Err(Error::EmptyPayload);
119        }
120
121        let sk = SigningKey::from_slice(secret_key.as_bytes()).map_err(|_| Error::Key)?;
122        let f = footer.unwrap_or(&[]);
123        let m2 = pae::pae(&[Self::HEADER.as_bytes(), message, f])?;
124        let sig = sk.sign(m2, None);
125
126        let mut m_sig: Vec<u8> = Vec::from(message);
127        m_sig.extend_from_slice(sig.as_ref());
128
129        let token_no_footer = format!("{}{}", Self::HEADER, encode_b64(m_sig)?);
130
131        if f.is_empty() {
132            Ok(token_no_footer)
133        } else {
134            Ok(format!("{}.{}", token_no_footer, encode_b64(f)?))
135        }
136    }
137
138    /// Verify a public token.
139    ///
140    /// If `footer.is_none()`, then it will be validated but not compared to a known value.
141    /// If `footer.is_some()`, then it will be validated AND compared to the known value.
142    pub fn verify(
143        public_key: &AsymmetricPublicKey<V2>,
144        token: &UntrustedToken<Public, V2>,
145        footer: Option<&[u8]>,
146    ) -> Result<TrustedToken, Error> {
147        validate_footer_untrusted_token(token, footer)?;
148
149        let f = token.untrusted_footer();
150        let sm = token.untrusted_message();
151        let m = token.untrusted_payload();
152        let s = sm[m.len()..m.len() + V2::PUBLIC_SIG].as_ref();
153
154        let m2 = pae::pae(&[Self::HEADER.as_bytes(), m, f])?;
155        let pk: PublicKey = PublicKey::from_slice(public_key.as_bytes()).map_err(|_| Error::Key)?;
156
157        debug_assert!(s.len() == V2::PUBLIC_SIG);
158        // If the below fails, it is an invalid signature.
159        let sig = Signature::from_slice(s).map_err(|_| Error::TokenValidation)?;
160
161        if pk.verify(m2, &sig).is_ok() {
162            TrustedToken::_new(Self::HEADER, m, f, &[])
163        } else {
164            Err(Error::TokenValidation)
165        }
166    }
167}
168
169/// PASETO v2 local tokens.
170pub struct LocalToken;
171
172impl LocalToken {
173    /// The header and purpose for the local token: `v2.local.`.
174    pub const HEADER: &'static str = "v2.local.";
175
176    /// Encrypt and authenticate a message using nonce_key_bytes to derive a nonce
177    /// using BLAKE2b.
178    pub(crate) fn encrypt_with_derived_nonce(
179        secret_key: &SymmetricKey<V2>,
180        nonce_key_bytes: &[u8],
181        message: &[u8],
182        footer: Option<&[u8]>,
183    ) -> Result<String, Error> {
184        debug_assert!(nonce_key_bytes.len() == XCHACHA_NONCESIZE);
185
186        // Safe unwrap()s due to lengths.
187        let nonce_key = blake2b::SecretKey::from_slice(nonce_key_bytes).unwrap();
188        let mut blake2b = blake2b::Blake2b::new(&nonce_key, XCHACHA_NONCESIZE).unwrap();
189        blake2b.update(message.as_ref()).unwrap();
190        let nonce = Nonce::from_slice(blake2b.finalize().unwrap().unprotected_as_bytes()).unwrap();
191
192        let f = footer.unwrap_or(&[]);
193
194        let pre_auth = pae::pae(&[Self::HEADER.as_bytes(), nonce.as_ref(), f])?;
195        let mut out = vec![0u8; message.len() + POLY1305_OUTSIZE + nonce.len()];
196        let sk = match SecretKey::from_slice(secret_key.as_bytes()) {
197            Ok(val) => val,
198            Err(orion::errors::UnknownCryptoError) => return Err(Error::Key),
199        };
200
201        match seal(
202            &sk,
203            &nonce,
204            message,
205            Some(&pre_auth),
206            &mut out[nonce.len()..],
207        ) {
208            Ok(()) => (),
209            Err(orion::errors::UnknownCryptoError) => return Err(Error::Encryption),
210        }
211
212        out[..nonce.len()].copy_from_slice(nonce.as_ref());
213        let token_no_footer = format!("{}{}", Self::HEADER, encode_b64(out)?);
214
215        if f.is_empty() {
216            Ok(token_no_footer)
217        } else {
218            Ok(format!("{}.{}", token_no_footer, encode_b64(f)?))
219        }
220    }
221
222    /// Create a local token.
223    pub fn encrypt(
224        secret_key: &SymmetricKey<V2>,
225        message: &[u8],
226        footer: Option<&[u8]>,
227    ) -> Result<String, Error> {
228        if message.is_empty() {
229            return Err(Error::EmptyPayload);
230        }
231
232        let mut rng_bytes = [0u8; XCHACHA_NONCESIZE];
233        getrandom::fill(&mut rng_bytes)?;
234
235        Self::encrypt_with_derived_nonce(secret_key, &rng_bytes, message, footer)
236    }
237
238    /// Verify and decrypt a local token.
239    ///
240    /// If `footer.is_none()`, then it will be validated but not compared to a known value.
241    /// If `footer.is_some()`, then it will be validated AND compared to the known value.
242    pub fn decrypt(
243        secret_key: &SymmetricKey<V2>,
244        token: &UntrustedToken<Local, V2>,
245        footer: Option<&[u8]>,
246    ) -> Result<TrustedToken, Error> {
247        validate_footer_untrusted_token(token, footer)?;
248
249        let f = token.untrusted_footer();
250        let nc = token.untrusted_message();
251        let n = nc[..XCHACHA_NONCESIZE].as_ref();
252        let c = nc[n.len()..].as_ref();
253
254        let pre_auth = pae::pae(&[Self::HEADER.as_bytes(), n, f])?;
255        let mut out = vec![0u8; c.len() - POLY1305_OUTSIZE];
256
257        let sk = match SecretKey::from_slice(secret_key.as_bytes()) {
258            Ok(val) => val,
259            Err(orion::errors::UnknownCryptoError) => return Err(Error::Key),
260        };
261
262        match open(
263            &sk,
264            &Nonce::from_slice(n).unwrap(),
265            c,
266            Some(pre_auth.as_ref()),
267            &mut out,
268        ) {
269            Ok(()) => TrustedToken::_new(Self::HEADER, &out, f, &[]),
270            Err(orion::errors::UnknownCryptoError) => Err(Error::TokenValidation),
271        }
272    }
273}
274
275#[cfg(test)]
276#[cfg(feature = "std")]
277mod test_vectors {
278
279    use hex;
280
281    use super::*;
282    use core::convert::TryFrom;
283    use std::fs::File;
284    use std::io::BufReader;
285
286    use crate::claims::Claims;
287    use crate::common::tests::*;
288
289    fn test_local(test: &PasetoTest) {
290        debug_assert!(test.nonce.is_some());
291        debug_assert!(test.key.is_some());
292
293        let sk =
294            SymmetricKey::<V2>::from(&hex::decode(test.key.as_ref().unwrap()).unwrap()).unwrap();
295
296        let nonce = hex::decode(test.nonce.as_ref().unwrap()).unwrap();
297        let footer: Option<&[u8]> = if test.footer.is_empty() {
298            None
299        } else {
300            Some(test.footer.as_bytes())
301        };
302
303        // payload is null when we expect failure
304        if test.expect_fail {
305            if let Ok(ut) = UntrustedToken::<Local, V2>::try_from(&test.token) {
306                assert!(LocalToken::decrypt(&sk, &ut, footer).is_err());
307            }
308
309            return;
310        }
311
312        let message = test.payload.as_ref().unwrap().as_str().unwrap();
313
314        let actual =
315            LocalToken::encrypt_with_derived_nonce(&sk, &nonce, message.as_bytes(), footer)
316                .unwrap();
317        assert_eq!(actual, test.token, "Failed {:?}", test.name);
318
319        let ut = UntrustedToken::<Local, V2>::try_from(&test.token).unwrap();
320        let trusted = LocalToken::decrypt(&sk, &ut, footer).unwrap();
321        assert_eq!(trusted.payload(), message, "Failed {:?}", test.name);
322        assert_eq!(trusted.footer(), test.footer.as_bytes());
323        assert_eq!(trusted.header(), LocalToken::HEADER);
324        assert!(trusted.implicit_assert().is_empty());
325
326        let parsed_claims = Claims::from_bytes(trusted.payload().as_bytes()).unwrap();
327        let test_vector_claims = serde_json::from_str::<Payload>(message).unwrap();
328
329        assert_eq!(
330            parsed_claims.get_claim("data").unwrap().as_str().unwrap(),
331            test_vector_claims.data,
332        );
333        assert_eq!(
334            parsed_claims.get_claim("exp").unwrap().as_str().unwrap(),
335            test_vector_claims.exp,
336        );
337    }
338
339    fn test_public(test: &PasetoTest) {
340        debug_assert!(test.public_key.is_some());
341        debug_assert!(test.secret_key.is_some());
342
343        let sk = AsymmetricSecretKey::<V2>::from(
344            &hex::decode(test.secret_key.as_ref().unwrap()).unwrap(),
345        )
346        .unwrap();
347        let pk = AsymmetricPublicKey::<V2>::from(
348            &hex::decode(test.public_key.as_ref().unwrap()).unwrap(),
349        )
350        .unwrap();
351        let footer: Option<&[u8]> = if test.footer.is_empty() {
352            None
353        } else {
354            Some(test.footer.as_bytes())
355        };
356
357        // payload is null when we expect failure
358        if test.expect_fail {
359            if let Ok(ut) = UntrustedToken::<Public, V2>::try_from(&test.token) {
360                assert!(PublicToken::verify(&pk, &ut, footer).is_err());
361            }
362
363            return;
364        }
365
366        let message = test.payload.as_ref().unwrap().as_str().unwrap();
367
368        let actual = PublicToken::sign(&sk, message.as_bytes(), footer).unwrap();
369        assert_eq!(actual, test.token, "Failed {:?}", test.name);
370        let ut = UntrustedToken::<Public, V2>::try_from(&test.token).unwrap();
371
372        let trusted = PublicToken::verify(&pk, &ut, footer).unwrap();
373        assert_eq!(trusted.payload(), message);
374        assert_eq!(trusted.footer(), test.footer.as_bytes());
375        assert_eq!(trusted.header(), PublicToken::HEADER);
376        assert!(trusted.implicit_assert().is_empty());
377    }
378
379    #[test]
380    fn run_test_vectors() {
381        let path = "./test_vectors/v2.json";
382        let file = File::open(path).unwrap();
383        let reader = BufReader::new(file);
384        let tests: TestFile = serde_json::from_reader(reader).unwrap();
385
386        for t in tests.tests {
387            // v2.public
388            if t.public_key.is_some() {
389                test_public(&t);
390            }
391            // v2.local
392            if t.nonce.is_some() {
393                test_local(&t);
394            }
395        }
396    }
397}
398
399#[cfg(test)]
400mod test_tokens {
401    use super::*;
402    use crate::common::decode_b64;
403    use crate::keys::{AsymmetricKeyPair, Generate};
404    use crate::token::UntrustedToken;
405    use core::convert::TryFrom;
406
407    const TEST_LOCAL_SK_BYTES: [u8; 32] = [
408        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
409        130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
410    ];
411
412    pub(crate) const TEST_SK_BYTES: [u8; 64] = [
413        180, 203, 251, 67, 223, 76, 226, 16, 114, 125, 149, 62, 74, 113, 51, 7, 250, 25, 187, 125,
414        159, 133, 4, 20, 56, 217, 225, 27, 148, 42, 55, 116, 30, 185, 219, 187, 188, 4, 124, 3,
415        253, 112, 96, 78, 0, 113, 240, 152, 126, 22, 178, 139, 117, 114, 37, 193, 31, 0, 65, 93,
416        14, 32, 177, 162,
417    ];
418
419    const TEST_PK_BYTES: [u8; 32] = [
420        30, 185, 219, 187, 188, 4, 124, 3, 253, 112, 96, 78, 0, 113, 240, 152, 126, 22, 178, 139,
421        117, 114, 37, 193, 31, 0, 65, 93, 14, 32, 177, 162,
422    ];
423
424    const MESSAGE: &str =
425        "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}";
426    const FOOTER: &str = "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}";
427    const VALID_PUBLIC_TOKEN: &str = "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
428    const VALID_LOCAL_TOKEN: &str = "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
429
430    #[test]
431    fn test_gen_keypair() {
432        let kp = AsymmetricKeyPair::<V2>::generate().unwrap();
433
434        let token = PublicToken::sign(&kp.secret, MESSAGE.as_bytes(), None).unwrap();
435
436        let ut = UntrustedToken::<Public, V2>::try_from(&token).unwrap();
437        assert!(PublicToken::verify(&kp.public, &ut, None).is_ok());
438    }
439
440    #[test]
441    fn test_untrusted_token_usage() {
442        // Local
443        let sk = SymmetricKey::<V2>::generate().unwrap();
444        let token = LocalToken::encrypt(&sk, MESSAGE.as_bytes(), Some(FOOTER.as_bytes())).unwrap();
445
446        let untrusted_token = UntrustedToken::<Local, V2>::try_from(token.as_str()).unwrap();
447        let _ = LocalToken::decrypt(
448            &sk,
449            &untrusted_token,
450            Some(untrusted_token.untrusted_footer()),
451        )
452        .unwrap();
453
454        // Public
455        let kp = AsymmetricKeyPair::<V2>::generate().unwrap();
456        let token =
457            PublicToken::sign(&kp.secret, MESSAGE.as_bytes(), Some(FOOTER.as_bytes())).unwrap();
458
459        let untrusted_token = UntrustedToken::<Public, V2>::try_from(token.as_str()).unwrap();
460        assert!(PublicToken::verify(&kp.public, &untrusted_token, Some(FOOTER.as_bytes())).is_ok());
461    }
462
463    #[test]
464    fn test_roundtrip_local() {
465        let sk = SymmetricKey::<V2>::generate().unwrap();
466        let message = "token payload";
467
468        let token = LocalToken::encrypt(&sk, message.as_bytes(), None).unwrap();
469        let ut = UntrustedToken::<Local, V2>::try_from(&token).unwrap();
470        let trusted_token = LocalToken::decrypt(&sk, &ut, None).unwrap();
471
472        assert_eq!(trusted_token.payload(), message);
473    }
474
475    #[test]
476    fn test_roundtrip_public() {
477        let test_sk = AsymmetricSecretKey::<V2>::from(&TEST_SK_BYTES).unwrap();
478        let test_pk = AsymmetricPublicKey::<V2>::from(&TEST_PK_BYTES).unwrap();
479
480        let token = PublicToken::sign(&test_sk, MESSAGE.as_bytes(), None).unwrap();
481        let ut = UntrustedToken::<Public, V2>::try_from(&token).unwrap();
482
483        assert!(PublicToken::verify(&test_pk, &ut, None).is_ok());
484    }
485
486    #[test]
487    fn footer_logic() {
488        let test_local_sk = SymmetricKey::<V2>::from(&TEST_LOCAL_SK_BYTES).unwrap();
489        let test_sk = AsymmetricSecretKey::<V2>::from(&TEST_SK_BYTES).unwrap();
490        let test_pk = AsymmetricPublicKey::<V2>::from(&TEST_PK_BYTES).unwrap();
491        let message =
492            b"{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}";
493
494        // We create a token with Some(footer) and with None
495        let actual_some = UntrustedToken::<Public, V2>::try_from(
496            &PublicToken::sign(&test_sk, message, Some(FOOTER.as_bytes())).unwrap(),
497        )
498        .unwrap();
499        let actual_none = UntrustedToken::<Public, V2>::try_from(
500            &PublicToken::sign(&test_sk, message, None).unwrap(),
501        )
502        .unwrap();
503
504        // token = Some(footer) = validate and compare
505        // token = None(footer) = validate only
506
507        // We should be able to validate with None if created with Some() (excludes constant-time
508        // comparison with known value)
509        assert!(PublicToken::verify(&test_pk, &actual_some, None).is_ok());
510        // We should be able to validate with Some() if created with Some()
511        assert!(PublicToken::verify(&test_pk, &actual_some, Some(FOOTER.as_bytes())).is_ok());
512        // We should NOT be able to validate with Some() if created with None
513        assert!(PublicToken::verify(&test_pk, &actual_none, Some(FOOTER.as_bytes())).is_err());
514
515        let actual_some = UntrustedToken::<Local, V2>::try_from(
516            &LocalToken::encrypt(&test_local_sk, message, Some(FOOTER.as_bytes())).unwrap(),
517        )
518        .unwrap();
519        let actual_none = UntrustedToken::<Local, V2>::try_from(
520            &LocalToken::encrypt(&test_local_sk, message, None).unwrap(),
521        )
522        .unwrap();
523
524        // They don't equal because the nonce is random. So we only check decryption.
525        assert!(LocalToken::decrypt(&test_local_sk, &actual_some, None).is_ok());
526        assert!(LocalToken::decrypt(&test_local_sk, &actual_some, Some(FOOTER.as_bytes())).is_ok());
527        assert!(
528            LocalToken::decrypt(&test_local_sk, &actual_none, Some(FOOTER.as_bytes())).is_err()
529        );
530    }
531
532    #[test]
533    // NOTE: See https://github.com/paseto-standard/paseto-spec/issues/17
534    fn empty_payload() {
535        let test_local_sk = SymmetricKey::<V2>::from(&TEST_LOCAL_SK_BYTES).unwrap();
536        let test_sk = AsymmetricSecretKey::<V2>::from(&TEST_SK_BYTES).unwrap();
537
538        assert_eq!(
539            PublicToken::sign(&test_sk, b"", None).unwrap_err(),
540            Error::EmptyPayload
541        );
542        assert_eq!(
543            LocalToken::encrypt(&test_local_sk, b"", None).unwrap_err(),
544            Error::EmptyPayload
545        );
546    }
547
548    #[test]
549    fn err_on_modified_footer() {
550        let test_pk = AsymmetricPublicKey::<V2>::from(&TEST_PK_BYTES).unwrap();
551        let test_local_sk = SymmetricKey::<V2>::from(&TEST_LOCAL_SK_BYTES).unwrap();
552
553        assert_eq!(
554            PublicToken::verify(
555                &test_pk,
556                &UntrustedToken::<Public, V2>::try_from(VALID_PUBLIC_TOKEN).unwrap(),
557                Some(FOOTER.replace("kid", "mid").as_bytes())
558            )
559            .unwrap_err(),
560            Error::TokenValidation
561        );
562        assert_eq!(
563            LocalToken::decrypt(
564                &test_local_sk,
565                &UntrustedToken::<Local, V2>::try_from(VALID_LOCAL_TOKEN).unwrap(),
566                Some(FOOTER.replace("kid", "mid").as_bytes())
567            )
568            .unwrap_err(),
569            Error::TokenValidation
570        );
571    }
572
573    #[test]
574    fn err_on_footer_in_token_none_supplied() {
575        let test_pk = AsymmetricPublicKey::<V2>::from(&TEST_PK_BYTES).unwrap();
576        let test_local_sk = SymmetricKey::<V2>::from(&TEST_LOCAL_SK_BYTES).unwrap();
577
578        assert_eq!(
579            PublicToken::verify(
580                &test_pk,
581                &UntrustedToken::<Public, V2>::try_from(VALID_PUBLIC_TOKEN).unwrap(),
582                Some(b"")
583            )
584            .unwrap_err(),
585            Error::TokenValidation
586        );
587        assert_eq!(
588            LocalToken::decrypt(
589                &test_local_sk,
590                &UntrustedToken::<Local, V2>::try_from(VALID_LOCAL_TOKEN).unwrap(),
591                Some(b"")
592            )
593            .unwrap_err(),
594            Error::TokenValidation
595        );
596    }
597
598    #[test]
599    fn err_on_no_footer_in_token_some_supplied() {
600        let test_pk = AsymmetricPublicKey::<V2>::from(&TEST_PK_BYTES).unwrap();
601        let test_local_sk = SymmetricKey::<V2>::from(&TEST_LOCAL_SK_BYTES).unwrap();
602
603        let split_public = VALID_PUBLIC_TOKEN.split('.').collect::<Vec<&str>>();
604        let invalid_public: String = format!(
605            "{}.{}.{}",
606            split_public[0], split_public[1], split_public[2]
607        );
608
609        let split_local = VALID_LOCAL_TOKEN.split('.').collect::<Vec<&str>>();
610        let invalid_local: String =
611            format!("{}.{}.{}", split_local[0], split_local[1], split_local[2]);
612
613        assert_eq!(
614            PublicToken::verify(
615                &test_pk,
616                &UntrustedToken::<Public, V2>::try_from(&invalid_public).unwrap(),
617                Some(FOOTER.as_bytes())
618            )
619            .unwrap_err(),
620            Error::TokenValidation
621        );
622        assert_eq!(
623            LocalToken::decrypt(
624                &test_local_sk,
625                &UntrustedToken::<Local, V2>::try_from(&invalid_local).unwrap(),
626                Some(FOOTER.as_bytes())
627            )
628            .unwrap_err(),
629            Error::TokenValidation
630        );
631    }
632
633    #[test]
634    fn err_on_modified_signature() {
635        let test_pk = AsymmetricPublicKey::<V2>::from(&TEST_PK_BYTES).unwrap();
636
637        let mut split_public = VALID_PUBLIC_TOKEN.split('.').collect::<Vec<&str>>();
638        let mut bad_sig = decode_b64(split_public[2]).unwrap();
639        bad_sig.copy_within(0..32, 32);
640        let tmp = encode_b64(bad_sig).unwrap();
641        split_public[2] = &tmp;
642        let invalid_public: String = format!(
643            "{}.{}.{}.{}",
644            split_public[0], split_public[1], split_public[2], split_public[3]
645        );
646
647        assert_eq!(
648            PublicToken::verify(
649                &test_pk,
650                &UntrustedToken::<Public, V2>::try_from(&invalid_public).unwrap(),
651                Some(FOOTER.as_bytes())
652            )
653            .unwrap_err(),
654            Error::TokenValidation
655        );
656    }
657
658    #[test]
659    fn err_on_modified_tag() {
660        let test_local_sk = SymmetricKey::<V2>::from(&TEST_LOCAL_SK_BYTES).unwrap();
661
662        let mut split_local = VALID_LOCAL_TOKEN.split('.').collect::<Vec<&str>>();
663        let mut bad_tag = decode_b64(split_local[2]).unwrap();
664        let tlen = bad_tag.len();
665        bad_tag.copy_within(0..16, tlen - 16);
666        let tmp = encode_b64(bad_tag).unwrap();
667        split_local[2] = &tmp;
668        let invalid_local: String = format!(
669            "{}.{}.{}.{}",
670            split_local[0], split_local[1], split_local[2], split_local[3]
671        );
672
673        assert_eq!(
674            LocalToken::decrypt(
675                &test_local_sk,
676                &UntrustedToken::<Local, V2>::try_from(&invalid_local).unwrap(),
677                Some(FOOTER.as_bytes())
678            )
679            .unwrap_err(),
680            Error::TokenValidation
681        );
682    }
683
684    #[test]
685    fn err_on_modified_ciphertext() {
686        let test_local_sk = SymmetricKey::<V2>::from(&TEST_LOCAL_SK_BYTES).unwrap();
687
688        let mut split_local = VALID_LOCAL_TOKEN.split('.').collect::<Vec<&str>>();
689        let mut bad_ct = decode_b64(split_local[2]).unwrap();
690        let ctlen = bad_ct.len();
691        bad_ct.copy_within((ctlen - 16)..ctlen, 24);
692        let tmp = encode_b64(bad_ct).unwrap();
693        split_local[2] = &tmp;
694        let invalid_local: String = format!(
695            "{}.{}.{}.{}",
696            split_local[0], split_local[1], split_local[2], split_local[3]
697        );
698
699        assert_eq!(
700            LocalToken::decrypt(
701                &test_local_sk,
702                &UntrustedToken::<Local, V2>::try_from(&invalid_local).unwrap(),
703                Some(FOOTER.as_bytes())
704            )
705            .unwrap_err(),
706            Error::TokenValidation
707        );
708    }
709
710    #[test]
711    fn err_on_modified_nonce() {
712        let test_local_sk = SymmetricKey::<V2>::from(&TEST_LOCAL_SK_BYTES).unwrap();
713
714        let mut split_local = VALID_LOCAL_TOKEN.split('.').collect::<Vec<&str>>();
715        let mut bad_nonce = decode_b64(split_local[2]).unwrap();
716        let nlen = bad_nonce.len();
717        bad_nonce.copy_within((nlen - 24)..nlen, 0);
718        let tmp = encode_b64(bad_nonce).unwrap();
719        split_local[2] = &tmp;
720        let invalid_local: String = format!(
721            "{}.{}.{}.{}",
722            split_local[0], split_local[1], split_local[2], split_local[3]
723        );
724
725        assert_eq!(
726            LocalToken::decrypt(
727                &test_local_sk,
728                &UntrustedToken::<Local, V2>::try_from(&invalid_local).unwrap(),
729                Some(FOOTER.as_bytes())
730            )
731            .unwrap_err(),
732            Error::TokenValidation
733        );
734    }
735
736    #[test]
737    fn err_on_invalid_public_secret_key() {
738        let bad_pk = AsymmetricPublicKey::<V2>::from(&[0u8; 32]).unwrap();
739
740        assert_eq!(
741            PublicToken::verify(
742                &bad_pk,
743                &UntrustedToken::<Public, V2>::try_from(VALID_PUBLIC_TOKEN).unwrap(),
744                Some(FOOTER.as_bytes())
745            )
746            .unwrap_err(),
747            Error::TokenValidation
748        );
749    }
750
751    #[test]
752    fn err_on_invalid_shared_secret_key() {
753        let bad_local_sk = SymmetricKey::<V2>::from(&[0u8; 32]).unwrap();
754
755        assert_eq!(
756            LocalToken::decrypt(
757                &bad_local_sk,
758                &UntrustedToken::<Local, V2>::try_from(VALID_LOCAL_TOKEN).unwrap(),
759                Some(FOOTER.as_bytes())
760            )
761            .unwrap_err(),
762            Error::TokenValidation
763        );
764    }
765}
766
767#[cfg(test)]
768mod test_keys {
769    use super::*;
770    use crate::version2::test_tokens::TEST_SK_BYTES;
771
772    #[test]
773    fn test_symmetric_gen() {
774        let randomv = SymmetricKey::<V2>::generate().unwrap();
775        assert_ne!(randomv.as_bytes(), &[0u8; 32]);
776    }
777
778    #[test]
779    fn test_invalid_sizes() {
780        assert!(AsymmetricSecretKey::<V2>::from(&[1u8; 63]).is_err());
781        assert!(AsymmetricSecretKey::<V2>::from(&TEST_SK_BYTES).is_ok());
782        assert!(AsymmetricSecretKey::<V2>::from(&[1u8; 65]).is_err());
783
784        assert!(AsymmetricPublicKey::<V2>::from(&[1u8; 31]).is_err());
785        assert!(AsymmetricPublicKey::<V2>::from(&[1u8; 32]).is_ok());
786        assert!(AsymmetricPublicKey::<V2>::from(&[1u8; 33]).is_err());
787
788        assert!(SymmetricKey::<V2>::from(&[0u8; 31]).is_err());
789        assert!(SymmetricKey::<V2>::from(&[0u8; 32]).is_ok());
790        assert!(SymmetricKey::<V2>::from(&[0u8; 33]).is_err());
791    }
792
793    #[test]
794    fn try_from_secret_to_public() {
795        let kpv2 = AsymmetricKeyPair::<V2>::generate().unwrap();
796        let pubv2 = AsymmetricPublicKey::<V2>::try_from(&kpv2.secret).unwrap();
797        assert_eq!(pubv2.as_bytes(), kpv2.public.as_bytes());
798        assert_eq!(pubv2, kpv2.public);
799        assert_eq!(&kpv2.secret.as_bytes()[32..], pubv2.as_bytes());
800    }
801
802    #[test]
803    fn test_trait_impls() {
804        let debug = format!("{:?}", SymmetricKey::<V2>::generate().unwrap());
805        assert_eq!(debug, "SymmetricKey {***OMITTED***}");
806
807        let randomv = SymmetricKey::<V2>::generate().unwrap();
808        let zero = SymmetricKey::<V2>::from(&[0u8; V2::LOCAL_KEY]).unwrap();
809        assert_ne!(randomv, zero);
810
811        let debug = format!("{:?}", AsymmetricKeyPair::<V2>::generate().unwrap().secret);
812        assert_eq!(debug, "AsymmetricSecretKey {***OMITTED***}");
813
814        let random1 = AsymmetricKeyPair::<V2>::generate().unwrap();
815        let random2 = AsymmetricKeyPair::<V2>::generate().unwrap();
816        assert_ne!(random1.secret, random2.secret);
817    }
818
819    #[test]
820    fn test_clone() {
821        let sk = SymmetricKey::<V2>::generate().unwrap();
822        assert_eq!(sk, sk.clone());
823
824        let kp = AsymmetricKeyPair::<V2>::generate().unwrap();
825        assert_eq!(kp.secret, kp.secret.clone());
826        assert_eq!(kp.public, kp.public.clone());
827    }
828}