Skip to main content

pasetors/
version4.rs

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