pasta_tokens/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![forbid(unsafe_code)]
3#![warn(missing_docs)]
4//! PASETO - **P**latform **A**gnostic **Se**curity **To**kens.
5//!
6//! > - [@conradludgate](https://github.com/conradludgate): "hmm, actually, I might switch to using something like paseto"
7//! > - [@ellie](https://github.com/ellie): "It sounds like a type of pasta"
8//!
9//! Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the [many design deficits that plague the JOSE standards](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid).
10//! See more about PASETO in the [specification](https://github.com/paseto-standard/paseto-spec)
11//!
12//! ## Features
13//!
14//! * `v4`: Enables all V4 PASETO/PASERK functions.
15//!   - `v4-paseto`
16//!     - `v4-local`: V4 symmetric encrypted tokens
17//!     - `v4-public`: V4 asymmetric signed tokens
18//!   - `v4-paserk`
19//!     - `v4-id`: V4 Key IDs
20//!     - `v4-pke`: V4 Sealed Keys
21//!     - `v4-pbkw`: V4 Password wrapped keys
22//!     - `v4-wrap`: v4 Wrapped Keys
23//! * `v3`: Enables all V3 PASETO/PASERK functions.
24//!   - `v3-paseto`
25//!     - `v3-local`: V3 symmetric encrypted tokens
26//!     - `v3-public`: V3 asymmetric signed tokens
27//!   - `v3-paserk`
28//!     - `v3-id`: V3 Key IDs
29//!     - `v3-pke`: V3 Sealed Keys
30//!     - `v3-pbkw`: V3 Password wrapped keys
31//!     - `v3-wrap`: v3 Wrapped Keys
32//!
33//! ## Examples
34//!
35//! ```
36//! use pasta_tokens::{v4, paserk::k4, purpose::public::Public, Json};
37//!
38//! #[derive(serde::Serialize, serde::Deserialize)]
39//! struct Footer {
40//!     /// The ID of the key used to sign the PASETO.
41//!     /// A footer should only contain types that are `SafeForFooter`
42//!     kid: k4::KeyId<Public>,
43//! }
44//!
45//! #[derive(serde::Serialize, serde::Deserialize)]
46//! struct Payload {
47//!     /// The expiration date of the token
48//!     #[serde(with = "time::serde::rfc3339", rename = "exp")]
49//!     expiration: time::OffsetDateTime,
50//!     /// The subject of the token
51//!     #[serde(rename = "sub")]
52//!     user_id: uuid::Uuid,
53//! }
54//!
55//! // load your secret key
56//! let secret_key = hex::decode("407796f4bc4b8184e9fe0c54b336822d34823092ad873d87ba14c3efb9db8c1d").unwrap();
57//! let secret_key = v4::SecretKey::from_secret_key(secret_key.try_into().unwrap());
58//!
59//! let user_id = uuid::Uuid::new_v4();
60//!
61//! // create the token payload and footer.
62//! let token = v4::UnsignedToken::new(Payload {
63//!     // expires in 1 hour
64//!     expiration: time::OffsetDateTime::now_utc() + time::Duration::hours(1),
65//!     user_id,
66//! })
67//! .with_footer(Json(Footer {
68//!     kid: secret_key.public_key().to_id(),
69//! }))
70//! // sign with the secret key
71//! .sign(&secret_key)
72//! .unwrap()
73//! .to_string();
74//!
75//! // Send off the token to the client
76//! println!("{token}");
77//! // "v4.public.eyJleHAiOiIyMDIzLTEwLTAxVDE0OjQ4OjI2LjM0NjA5MloiLCJzdWIiOiIxOTBhZjFmYS1lZGVlLTRiNGUtOGQxMC05ZmUwZjQ1ZGQ5OTQifXo-Vsr45NroJZ9pLkuN3xcxgFncGF3eject5GdZH7WwTEfCgmo6hD-zNh0txsLvZi1vC601oNCgXq_2cK4XKQw.eyJraWQiOiJrNC5waWQuQUdQQ09CUkI4UHowQ3dNOFFfQnNVUEw0OF8zZjRUbE0yc2Z0R3Y0ejkzVFkifQ"
78//!
79//! // load your public keys
80//! let public_key = hex::decode("b7715bd661458d928654d3e832f53ff5c9480542e0e3d4c9b032c768c7ce6023").unwrap();
81//! let public_key = v4::PublicKey::from_public_key(&public_key).unwrap();
82//!
83//! // keep a key cache of key IDs to public keys.
84//! // this will let you securely rotate your secret keys
85//! // and still validate multiple public keys safely
86//! let keys = std::collections::HashMap::from([
87//!     (public_key.to_id(), public_key)
88//! ]);
89//!
90//! // Parse the token from the client
91//! let token: v4::SignedToken<Json<Footer>> = token.parse().expect("should be a valid token format");
92//!
93//! // using the key ID, search for the public key
94//! let key = &keys[&token.unverified_footer().0.kid];
95//!
96//! // verify the token signature
97//! let token: v4::VerifiedToken<Payload, _> = token.verify(key).expect("token should be signed by us");
98//!
99//! // check if the token has expired
100//! assert!(token.message.expiration > time::OffsetDateTime::now_utc());
101//!
102//! // proceed to use the payload as you wish!
103//! assert_eq!(token.message.user_id, user_id);
104//! ```
105
106#[allow(dead_code)]
107type Bytes<N> = generic_array::GenericArray<u8, N>;
108
109/// PASETO V4 using only algorithms that are provided by libsodium
110pub mod v4 {
111    use crate::version::V4;
112    /// A symmetric key for `local` encrypted tokens
113    pub type SymmetricKey = crate::purpose::local::SymmetricKey<V4>;
114    /// An decrypted PASETO.
115    pub type DecryptedToken<M, F = (), E = crate::Json<()>> =
116        crate::purpose::local::DecryptedToken<V4, M, F, E>;
117    /// An encrypted PASETO.
118    pub type EncryptedToken<F = (), E = crate::Json<()>> =
119        crate::purpose::local::EncryptedToken<V4, F, E>;
120    /// An unencrypted PASETO.
121    pub type UnencryptedToken<M, F = (), E = crate::Json<()>> =
122        crate::purpose::local::UnencryptedToken<V4, M, F, E>;
123
124    /// A public key for verifying `public` tokens
125    pub type PublicKey = crate::purpose::public::PublicKey<V4>;
126    /// A secret key for signing `public` tokens
127    pub type SecretKey = crate::purpose::public::SecretKey<V4>;
128    /// A Verified PASETO that has been parsed and verified
129    pub type VerifiedToken<M, F = (), E = crate::Json<()>> =
130        crate::purpose::public::VerifiedToken<V4, M, F, E>;
131    /// A Signed PASETO.
132    pub type SignedToken<F = (), E = crate::Json<()>> =
133        crate::purpose::public::SignedToken<V4, F, E>;
134    /// A PASETO that is ready to be signed.
135    pub type UnsignedToken<M, F = (), E = crate::Json<()>> =
136        crate::purpose::public::UnsignedToken<V4, M, F, E>;
137}
138
139/// PASETO V3 using only NIST approved algorithms
140pub mod v3 {
141    use crate::version::V3;
142    /// A symmetric key for `local` encrypted tokens
143    pub type SymmetricKey = crate::purpose::local::SymmetricKey<V3>;
144    /// An decrypted PASETO.
145    pub type DecryptedToken<M, F = (), E = crate::Json<()>> =
146        crate::purpose::local::DecryptedToken<V3, M, F, E>;
147    /// An encrypted PASETO.
148    pub type EncryptedToken<F = (), E = crate::Json<()>> =
149        crate::purpose::local::EncryptedToken<V3, F, E>;
150    /// An unencrypted PASETO.
151    pub type UnencryptedToken<M, F = (), E = crate::Json<()>> =
152        crate::purpose::local::UnencryptedToken<V3, M, F, E>;
153
154    /// A public key for verifying `public` tokens
155    pub type PublicKey = crate::purpose::public::PublicKey<V3>;
156    /// A secret key for signing `public` tokens
157    pub type SecretKey = crate::purpose::public::SecretKey<V3>;
158    /// A Verified PASETO that has been parsed and verified
159    pub type VerifiedToken<M, F = (), E = crate::Json<()>> =
160        crate::purpose::public::VerifiedToken<V3, M, F, E>;
161    /// A Signed PASETO.
162    pub type SignedToken<F = (), E = crate::Json<()>> =
163        crate::purpose::public::SignedToken<V3, F, E>;
164    /// A PASETO that is ready to be signed.
165    pub type UnsignedToken<M, F = (), E = crate::Json<()>> =
166        crate::purpose::public::UnsignedToken<V3, M, F, E>;
167}
168
169pub mod purpose {
170    //! Purpose of the PASETO. Supports either [`local`] or [`public`]
171
172    pub mod local;
173    pub mod public;
174
175    pub use local::Local;
176    pub use public::{Public, Secret};
177
178    /// Purpose of the PASETO.
179    ///
180    /// * `public` - signed tokens. payload included in plaintext
181    /// * `local` - encrypted tokens. payload is not readable without key
182    pub trait Purpose: Default {
183        /// "local" or "public"
184        const HEADER: &'static str;
185    }
186}
187
188pub mod key;
189
190// #[cfg(feature = "paserk")]
191pub mod paserk;
192
193pub mod version {
194    //! Versions of PASETO. Supports [`V3`] or [`V4`]
195
196    /// PASETO Version 3 (NIST)
197    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
198    pub struct V3;
199
200    /// PASETO Version 4 (Sodium)
201    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
202    pub struct V4;
203
204    /// General information about a PASETO/PASERK version.
205    ///
206    /// This library supports only version [`V3`] and [`V4`].
207    pub trait Version: Default + crate::sealed::Sealed {
208        /// Header for PASETO
209        const PASETO_HEADER: &'static str;
210        /// Header for PASERK
211        const PASERK_HEADER: &'static str;
212    }
213
214    impl Version for V3 {
215        const PASETO_HEADER: &'static str = "v3";
216        const PASERK_HEADER: &'static str = "k3";
217    }
218
219    impl Version for V4 {
220        const PASETO_HEADER: &'static str = "v4";
221        const PASERK_HEADER: &'static str = "k4";
222    }
223
224    impl crate::sealed::Sealed for V3 {}
225
226    impl crate::sealed::Sealed for V4 {}
227}
228
229/// PASETO Message encodings. Currently supports [`Json`]
230///
231/// PASETO serializes its payload as a JSON string.
232/// Future documents MAY specify using PASETO with non-JSON encoding.
233/// When this happens, a suffix will be appended to the version tag when a non-JSON encoding rule is used.
234///
235/// > For example, a future PASETO-CBOR proposal might define its versions as v1c, v2c, v3c, and v4c.
236/// The underlying cryptography will be the same as v1, v2, v3, and v4 respectively.
237/// Keys SHOULD be portable across different underlying encodings,
238/// but tokens MUST NOT be transmutable between encodings without access to the symmetric key (local tokens) or secret key (public tokens).
239pub mod encodings {
240    use serde::{de::DeserializeOwned, Serialize};
241
242    use crate::Json;
243
244    /// A payload protocol. Currently only supports [`Json`]
245    pub trait Payload: Default {
246        /// Suffix for this encoding type
247        const SUFFIX: &'static str;
248    }
249
250    /// Payload encoding implementation. Currently only supports [`Json`]
251    pub trait MessageEncoding<M>: Payload {
252        /// Encode the message
253        fn encode(&self, s: &M) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>>;
254    }
255
256    /// Payload decoding implementation. Currently only supports [`Json`]
257    pub trait MessageDecoding<M>: Payload {
258        /// Decode the message
259        fn decode(&self, from: &[u8]) -> Result<M, Box<dyn std::error::Error + Send + Sync>>;
260    }
261
262    impl Payload for Json<()> {
263        /// JSON is the standard payload and requires no version suffix
264        const SUFFIX: &'static str = "";
265    }
266
267    impl<M: Serialize> MessageEncoding<M> for Json<()> {
268        fn encode(&self, s: &M) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
269            serde_json::to_vec(s).map_err(From::from)
270        }
271    }
272
273    impl<M: DeserializeOwned> MessageDecoding<M> for Json<()> {
274        fn decode(&self, from: &[u8]) -> Result<M, Box<dyn std::error::Error + Send + Sync>> {
275            serde_json::from_slice(from).map_err(From::from)
276        }
277    }
278}
279
280mod pae;
281
282/// Encoding scheme for PASETO footers.
283///
284/// Footers are allowed to be any encoding, but JSON is the standard.
285/// You can use the `Json` type to encode serde structs using JSON.
286///
287/// Footers are also optional, so the `()` empty type is considered as a missing footer.
288pub trait Footer: Sized {
289    /// Encode the footer to bytes
290    fn encode(&self) -> Vec<u8>;
291    /// Decode the footer from bytes
292    fn decode(footer: &[u8]) -> Result<Self, Box<dyn std::error::Error + Send + Sync>>;
293}
294
295/// `Json` is a type wrapper to implement `Footer` for all types that implement
296/// [`serde::Serialize`] and [`serde::Deserialize`]
297///
298/// When using a JSON footer, you should be aware of the risks of parsing user provided JSON.
299/// <https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/01-Payload-Processing.md#storing-json-in-the-footer>.
300///
301/// Currently, this uses [`serde_json`] internally, which by default offers a stack-overflow protection limit on parsing JSON.
302/// You should also parse into a known struct layout, and avoid arbitrary key-value mappings.
303///
304/// If you need stricter checks, you can make your own [`Footer`] encodings that give access to the bytes before
305/// the footer is decoded.
306#[derive(Default)]
307pub struct Json<T>(pub T);
308
309impl<T: serde::Serialize + serde::de::DeserializeOwned> Footer for Json<T> {
310    fn encode(&self) -> Vec<u8> {
311        serde_json::to_vec(&self.0).expect("json serialization should not panic")
312    }
313
314    fn decode(footer: &[u8]) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
315        match footer {
316            x if x.is_empty() => Err("missing footer".into()),
317            x => serde_json::from_slice(x).map(Self).map_err(|e| e.into()),
318        }
319    }
320}
321
322impl Footer for Vec<u8> {
323    fn encode(&self) -> Vec<u8> {
324        self.clone()
325    }
326
327    fn decode(footer: &[u8]) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
328        Ok(footer.to_owned())
329    }
330}
331
332impl Footer for () {
333    fn encode(&self) -> Vec<u8> {
334        Vec::new()
335    }
336
337    fn decode(footer: &[u8]) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
338        match footer {
339            x if x.is_empty() => Ok(()),
340            x => Err(format!("unexpected footer {x:?}").into()),
341        }
342    }
343}
344
345mod sealed {
346    pub trait Sealed {}
347}
348
349#[derive(Default)]
350struct TokenMetadata<V, T, E> {
351    version_header: V,
352    token_type: T,
353    #[allow(dead_code)]
354    encoding: E,
355}
356
357pub mod tokens {
358    //! Generic Tokens
359
360    use core::fmt;
361
362    use base64ct::Encoding;
363
364    use crate::{encodings::Payload, purpose, version, Footer, Json, PasetoError, TokenMetadata};
365
366    /// A validated token.
367    ///
368    /// This represents a PASETO which has had signatures or encryption validated.
369    /// Using one of the following aliases is suggested
370    /// * [`VerifiedToken`](purpose::public::VerifiedToken) - A `public` PASETO which has had signature validated.
371    /// * [`DecryptedToken`](purpose::local::DecryptedToken) - A `local` PASETO which has successfully been decrypted.
372    ///
373    /// This type is un-serializable as it isn't secured. For that you will want [`SecuredToken`].
374    pub struct ValidatedToken<V, T, M, F = (), E = Json<()>> {
375        pub(crate) meta: TokenMetadata<V, T, E>,
376        /// The message that was contained in the token
377        pub message: M,
378        /// The footer that was sent with the token
379        pub footer: F,
380    }
381
382    impl<V, T, M, E> TokenBuilder<V, T, M, (), E> {
383        /// Set the footer for this token.
384        ///
385        /// Footers are embedded into the token as base64 only. They are authenticated but not encrypted.
386        pub fn with_footer<F>(self, footer: F) -> TokenBuilder<V, T, M, F, E> {
387            TokenBuilder(ValidatedToken {
388                meta: self.0.meta,
389                message: self.0.message,
390                footer,
391            })
392        }
393    }
394
395    impl<V, T, M, F> TokenBuilder<V, T, M, F, Json<()>> {
396        /// Set the payload encoding for this token.
397        ///
398        /// The PASETO spec only allows JSON _for now_, but one day this might be extended.
399        pub fn with_encoding<E>(self, encoding: E) -> TokenBuilder<V, T, M, F, E> {
400            TokenBuilder(ValidatedToken {
401                message: self.0.message,
402                footer: self.0.footer,
403                meta: TokenMetadata {
404                    version_header: self.0.meta.version_header,
405                    token_type: self.0.meta.token_type,
406                    encoding,
407                },
408            })
409        }
410    }
411
412    /// A builder of tokens
413    pub struct TokenBuilder<V, T, M, F = (), E = Json<()>>(
414        pub(crate) ValidatedToken<V, T, M, F, E>,
415    );
416
417    impl<V: crate::version::Version, T: crate::purpose::Purpose, M> TokenBuilder<V, T, M> {
418        /// Create a new [`TokenBuilder`] builder with the given message payload
419        pub fn new(message: M) -> Self {
420            Self(ValidatedToken {
421                meta: TokenMetadata::default(),
422                message,
423                footer: (),
424            })
425        }
426    }
427
428    /// A secured token.
429    ///
430    /// This represents a PASETO that is signed or encrypted.
431    /// Using one of the following aliases is suggested
432    /// * [`SignedToken`](purpose::public::SignedToken) - A `public` PASETO that is signed.
433    /// * [`EncryptedToken`](purpose::local::EncryptedToken) - A `local` PASETO that is encryption.
434    ///
435    /// This type has a payload that is currently inaccessible. To access it, you will need to
436    /// decrypt/verify the contents. For that you will want [`ValidatedToken`].
437    ///
438    /// To convert to an [`ValidatedToken`], you will need to use either
439    /// * [`SignedToken::verify`](purpose::public::SignedToken::verify)
440    /// * [`EncryptedToken::decrypt`](purpose::local::EncryptedToken::decrypt)
441    pub struct SecuredToken<V, T, F = (), E = Json<()>> {
442        #[allow(dead_code)]
443        pub(crate) meta: TokenMetadata<V, T, E>,
444        pub(crate) payload: Vec<u8>,
445        pub(crate) encoded_footer: Vec<u8>,
446        pub(crate) footer: F,
447    }
448
449    impl<V: version::Version, T: purpose::Purpose, F, E: Payload> fmt::Display
450        for SecuredToken<V, T, F, E>
451    {
452        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453            f.write_str(V::PASETO_HEADER)?;
454            f.write_str(E::SUFFIX)?;
455            f.write_str(".")?;
456            f.write_str(T::HEADER)?;
457            f.write_str(".")?;
458            f.write_str(&base64ct::Base64UrlUnpadded::encode_string(&self.payload))?;
459
460            if !self.encoded_footer.is_empty() {
461                f.write_str(".")?;
462                f.write_str(&base64ct::Base64UrlUnpadded::encode_string(
463                    &self.encoded_footer,
464                ))?;
465            }
466
467            Ok(())
468        }
469    }
470
471    impl<V: version::Version, K: purpose::Purpose, F, E: Payload> serde::Serialize
472        for SecuredToken<V, K, F, E>
473    {
474        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
475        where
476            S: serde::Serializer,
477        {
478            serializer.collect_str(self)
479        }
480    }
481
482    impl<'de, V: version::Version, K: purpose::Purpose, F: Footer, E: Payload>
483        serde::Deserialize<'de> for SecuredToken<V, K, F, E>
484    {
485        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
486        where
487            D: serde::Deserializer<'de>,
488        {
489            struct FromStrVisitor<V, T, F, E>(std::marker::PhantomData<(V, T, F, E)>);
490            impl<'de, V: version::Version, K: purpose::Purpose, F: Footer, E: Payload>
491                serde::de::Visitor<'de> for FromStrVisitor<V, K, F, E>
492            {
493                type Value = SecuredToken<V, K, F, E>;
494
495                fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
496                    write!(
497                        formatter,
498                        "a \"{}.{}.\" paseto",
499                        V::PASETO_HEADER,
500                        K::HEADER,
501                    )
502                }
503                fn visit_str<Err>(self, v: &str) -> Result<Self::Value, Err>
504                where
505                    Err: serde::de::Error,
506                {
507                    v.parse().map_err(Err::custom)
508                }
509            }
510            deserializer.deserialize_str(FromStrVisitor(std::marker::PhantomData))
511        }
512    }
513
514    impl<V: version::Version, T: purpose::Purpose, F: Footer, E: Payload> std::str::FromStr
515        for SecuredToken<V, T, F, E>
516    {
517        type Err = PasetoError;
518
519        fn from_str(s: &str) -> Result<Self, Self::Err> {
520            let s = s
521                .strip_prefix(V::PASETO_HEADER)
522                .ok_or(PasetoError::InvalidToken)?;
523            let s = s.strip_prefix(E::SUFFIX).ok_or(PasetoError::InvalidToken)?;
524            let s = s.strip_prefix('.').ok_or(PasetoError::InvalidToken)?;
525            let s = s.strip_prefix(T::HEADER).ok_or(PasetoError::InvalidToken)?;
526            let s = s.strip_prefix('.').ok_or(PasetoError::InvalidToken)?;
527
528            let (payload, footer) = match s.split_once('.') {
529                Some((payload, footer)) => (payload, Some(footer)),
530                None => (s, None),
531            };
532
533            let payload = base64ct::Base64UrlUnpadded::decode_vec(payload)
534                .map_err(|_| PasetoError::Base64DecodeError)?;
535            let encoded_footer = footer
536                .map(base64ct::Base64UrlUnpadded::decode_vec)
537                .transpose()
538                .map_err(|_| PasetoError::Base64DecodeError)?
539                .unwrap_or_default();
540            let footer = F::decode(&encoded_footer).map_err(PasetoError::PayloadError)?;
541
542            Ok(Self {
543                meta: TokenMetadata::default(),
544                payload,
545                encoded_footer,
546                footer,
547            })
548        }
549    }
550
551    impl<V, T, F, E> SecuredToken<V, T, F, E> {
552        /// View the **unverified** footer for this token
553        pub fn unverified_footer(&self) -> &F {
554            &self.footer
555        }
556    }
557}
558
559#[derive(Debug)]
560#[non_exhaustive]
561/// Error returned for all PASETO and PASERK operations that can fail
562pub enum PasetoError {
563    /// The token was not Base64 URL encoded correctly.
564    Base64DecodeError,
565    /// Could not decode the provided key string
566    InvalidKey,
567    /// The PASETO or PASERK was not of a valid form
568    InvalidToken,
569    /// Could not verify/decrypt the PASETO/PASERK.
570    CryptoError,
571    /// There was an error with payload processing
572    PayloadError(Box<dyn std::error::Error + Send + Sync>),
573}
574
575impl std::error::Error for PasetoError {
576    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
577        match self {
578            PasetoError::PayloadError(x) => Some(&**x),
579            _ => None,
580        }
581    }
582}
583
584impl std::fmt::Display for PasetoError {
585    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586        match self {
587            PasetoError::Base64DecodeError => f.write_str("The token could not be base64 decoded"),
588            PasetoError::InvalidKey => f.write_str("Could not parse the key"),
589            PasetoError::InvalidToken => f.write_str("Could not parse the token"),
590            PasetoError::CryptoError => f.write_str("Token signature could not be validated"),
591            PasetoError::PayloadError(x) => {
592                write!(f, "there was an error with the payload encoding: {x}")
593            }
594        }
595    }
596}
597
598#[cfg(fuzzing)]
599pub mod fuzzing {
600    use rand::{CryptoRng, RngCore};
601
602    use crate::purpose::local::{DecryptedToken, EncryptedToken, Local, LocalVersion};
603    use crate::purpose::public::{Secret, SignedToken, UnsignedToken, VerifiedToken};
604    use crate::version::{V3, V4};
605    use crate::{
606        key::{Key, KeyType},
607        purpose::local::UnencryptedToken,
608        version::Version,
609        Json,
610    };
611
612    #[derive(Clone, Debug)]
613    /// a consistent rng store
614    pub struct FakeRng<const N: usize> {
615        pub bytes: [u8; N],
616        pub start: usize,
617    }
618
619    impl<'a, const N: usize> arbitrary::Arbitrary<'a> for FakeRng<N>
620    where
621        [u8; N]: arbitrary::Arbitrary<'a>,
622    {
623        fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
624            Ok(Self {
625                bytes: <[u8; N]>::arbitrary(u)?,
626                start: 0,
627            })
628        }
629    }
630
631    impl<const N: usize> RngCore for FakeRng<N> {
632        fn next_u32(&mut self) -> u32 {
633            unimplemented!()
634        }
635
636        fn next_u64(&mut self) -> u64 {
637            unimplemented!()
638        }
639
640        fn fill_bytes(&mut self, dest: &mut [u8]) {
641            let remaining = N - self.start;
642            let requested = dest.len();
643            if requested > remaining {
644                panic!("not enough entropy");
645            }
646            dest.copy_from_slice(&self.bytes[self.start..self.start + requested]);
647            self.start += requested;
648        }
649
650        fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
651            self.fill_bytes(dest);
652            Ok(())
653        }
654    }
655
656    // not really
657    impl<const N: usize> CryptoRng for FakeRng<N> {}
658
659    #[derive(Debug)]
660    pub struct FuzzInput<V: Version, K: KeyType<V>> {
661        key: Key<V, K>,
662        ephemeral: FakeRng<32>,
663        data1: String,
664        data2: String,
665        data3: String,
666    }
667
668    impl<'a, V: Version, K: KeyType<V>> arbitrary::Arbitrary<'a> for FuzzInput<V, K>
669    where
670        Key<V, K>: arbitrary::Arbitrary<'a>,
671    {
672        fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
673            Ok(Self {
674                key: u.arbitrary()?,
675                ephemeral: u.arbitrary()?,
676                data1: u.arbitrary()?,
677                data2: u.arbitrary()?,
678                data3: u.arbitrary()?,
679            })
680        }
681    }
682
683    #[derive(serde::Deserialize, serde::Serialize)]
684    struct Data {
685        data: String,
686    }
687
688    impl<V: LocalVersion> FuzzInput<V, Local> {
689        pub fn run(self) {
690            let token = UnencryptedToken::<V, Data>::new(Data {
691                data: self.data1.clone(),
692            })
693            .with_footer(Json(Data {
694                data: self.data2.clone(),
695            }))
696            .encrypt_with_assertions_and_rng(&self.key, self.data3.as_bytes(), self.ephemeral)
697            .unwrap();
698            let token: EncryptedToken<V, Json<Data>> = token.to_string().parse().unwrap();
699            assert_eq!(token.unverified_footer().0.data, self.data2);
700            let token: DecryptedToken<V, Data, Json<Data>> = token
701                .decrypt_with_assertions(&self.key, self.data3.as_bytes())
702                .unwrap();
703            assert_eq!(token.message.data, self.data1);
704        }
705    }
706    impl FuzzInput<V3, Secret> {
707        pub fn run(self) {
708            let token = UnsignedToken::<V3, Data>::new(Data {
709                data: self.data1.clone(),
710            })
711            .with_footer(Json(Data {
712                data: self.data2.clone(),
713            }))
714            .sign_with_assertions(&self.key, self.data3.as_bytes())
715            .unwrap();
716            let token: SignedToken<V3, Json<Data>> = token.to_string().parse().unwrap();
717            assert_eq!(token.unverified_footer().0.data, self.data2);
718            let token: VerifiedToken<V3, Data, Json<Data>> = token
719                .verify_with_assertions(&self.key.public_key(), self.data3.as_bytes())
720                .unwrap();
721            assert_eq!(token.message.data, self.data1);
722        }
723    }
724    impl FuzzInput<V4, Secret> {
725        pub fn run(self) {
726            let token = UnsignedToken::<V4, Data>::new(Data {
727                data: self.data1.clone(),
728            })
729            .with_footer(Json(Data {
730                data: self.data2.clone(),
731            }))
732            .sign_with_assertions(&self.key, self.data3.as_bytes())
733            .unwrap();
734            let token: SignedToken<V4, Json<Data>> = token.to_string().parse().unwrap();
735            assert_eq!(token.unverified_footer().0.data, self.data2);
736            let token: VerifiedToken<V4, Data, Json<Data>> = token
737                .verify_with_assertions(&self.key.public_key(), self.data3.as_bytes())
738                .unwrap();
739            assert_eq!(token.message.data, self.data1);
740        }
741    }
742}