├── .gitignore ├── ci ├── rustfmt.sh └── tests.sh ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── src ├── serializer_traits.rs ├── separator.rs ├── key_derivation.rs ├── lib.rs ├── timestamp.rs ├── multi_serializer.rs ├── algorithm.rs ├── traits.rs ├── timed.rs ├── error.rs ├── base64.rs ├── signer.rs └── serde_serializer.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock -------------------------------------------------------------------------------- /ci/rustfmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")"/.. 4 | set -ex 5 | 6 | rustup component add rustfmt 7 | 8 | cargo fmt --all -- --check -------------------------------------------------------------------------------- /ci/tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")"/.. 4 | set -ex 5 | 6 | export RUSTFLAGS="-D warnings" 7 | 8 | cargo check --no-default-features 9 | cargo check --bins --examples --tests 10 | cargo test --all-features -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | cache: cargo 4 | 5 | branches: 6 | only: 7 | - master 8 | - staging 9 | - trying 10 | 11 | matrix: 12 | fast_finish: true 13 | include: 14 | 15 | # Run tests + checks against stable 16 | - rust: stable 17 | name: "itsdangerous-rs on stable" 18 | script: ./ci/tests.sh 19 | 20 | # Check formatting. 21 | - rust: stable 22 | name: "rustfmt" 23 | script: ./ci/rustfmt.sh -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "itsdangerous" 3 | version = "0.4.1" 4 | authors = ["Jake "] 5 | edition = "2018" 6 | include = [ 7 | "src/*.rs", 8 | "Cargo.toml", 9 | ] 10 | license = "MIT" 11 | readme = "README.md" 12 | repository = "https://github.com/discordapp/itsdangerous-rs" 13 | homepage = "https://github.com/discordapp/itsdangerous-rs" 14 | documentation = "https://docs.rs/itsdangerous" 15 | description = "Rust port of the popular itsdangerous python library for signing strings and sending them over untrusted channels." 16 | 17 | [features] 18 | serializer = ["serde", "serde_json"] 19 | nightly = [] 20 | 21 | [package.metadata.docs.rs] 22 | all-features = true 23 | 24 | [dependencies] 25 | hmac = "0.7.0" 26 | sha-1 = "0.8.1" 27 | base64 = "0.10.1" 28 | generic-array = "0.12.0" 29 | typenum = "1.10.0" 30 | serde = { version = "1.0", optional = true } 31 | serde_json = { version = "1.0", optional = true } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Discord 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/serializer_traits.rs: -------------------------------------------------------------------------------- 1 | // TODO: Doc these traits. 2 | use std::time::SystemTime; 3 | 4 | use serde::{de::DeserializeOwned, Serialize}; 5 | 6 | use crate::{BadSignature, BadTimedSignature, PayloadError, UnsignedTimedSerializerValue}; 7 | 8 | pub trait Encoding { 9 | fn encode<'a>(&self, serialized_input: String) -> String; 10 | fn decode<'a>(&self, encoded_input: String) -> Result; 11 | } 12 | 13 | pub trait Serializer { 14 | fn sign(&self, value: &T) -> serde_json::Result; 15 | fn unsign<'a, T: DeserializeOwned>(&'a self, value: &'a str) -> Result>; 16 | } 17 | 18 | pub trait TimedSerializer { 19 | fn sign(&self, value: &T) -> serde_json::Result; 20 | fn sign_with_timestamp( 21 | &self, 22 | value: &T, 23 | timestamp: SystemTime, 24 | ) -> serde_json::Result; 25 | fn unsign<'a, T: DeserializeOwned>( 26 | &'a self, 27 | value: &'a str, 28 | ) -> Result, BadTimedSignature<'a>>; 29 | } 30 | 31 | pub trait UnsignToString { 32 | fn unsign_to_string<'a>(&'a self, value: &'a str) -> Result>; 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `itsdangerous-rs` 2 | 3 | [![Build Status](https://travis-ci.org/discordapp/itsdangerous-rs.svg?branch=master)](https://travis-ci.org/discordapp/itsdangerous-rs) 4 | [![License](https://img.shields.io/github/license/discordapp/itsdangerous-rs.svg)](LICENSE) 5 | [![Documentation](https://docs.rs/itsdangerous/badge.svg)](https://docs.rs/itsdangerous) 6 | [![Cargo](https://img.shields.io/crates/v/itsdangerous.svg)](https://crates.io/crates/itsdangerous) 7 | 8 | A rust re-implementation of the Python library [itsdangerous](https://github.com/pallets/itsdangerous/). 9 | 10 | Essentially, this crate provides various helpers to pass data to untrusted environments 11 | and get it back safe and sound. Data is cryptographically signed to ensure that it has 12 | not been tampered with. 13 | 14 | ## Basic Usage 15 | 16 | Add this to your `Cargo.toml`: 17 | 18 | ```toml 19 | [dependencies] 20 | itsdangerous = "0.3" 21 | ``` 22 | 23 | Next, get to signing some dangerous strings: 24 | 25 | ```rust 26 | use itsdangerous::{default_builder, Signer}; 27 | 28 | fn main() { 29 | // Create a signer using the default builder, and an arbitrary secret key. 30 | let signer = default_builder("secret key").build(); 31 | 32 | // Sign an arbitrary string, and send it somewhere dangerous. 33 | let signed = signer.sign("hello world!"); 34 | 35 | // Unsign the string and validate that it hasn't been tampered with. 36 | let unsigned = signer.unsign(&signed).expect("Signature was not valid"); 37 | assert_eq!(unsigned, "hello world!"); 38 | } 39 | ``` 40 | 41 | For more in-depth examples, check out the [documentation](https://docs.rs/itsdangerous)! 42 | 43 | ## License 44 | 45 | Licensed under the [MIT license](LICENSE). 46 | -------------------------------------------------------------------------------- /src/separator.rs: -------------------------------------------------------------------------------- 1 | use crate::base64; 2 | use crate::error::{InvalidSeparator, SeparatorNotFound}; 3 | 4 | /// A separator character that can be used in [`crate::SignerBuilder::with_separator`]. 5 | /// 6 | /// This is used to join the various parts of the signed payload. 7 | /// 8 | /// # Basic Usage 9 | /// ```rust 10 | /// use itsdangerous::{default_builder, Separator}; 11 | /// 12 | /// // Creates a separator using a given character. 13 | /// let separator = Separator::new('!').expect("Invalid separator :("); 14 | /// // Use that separator in the builder. 15 | /// let signer = default_builder("hello") 16 | /// .with_separator(separator) 17 | /// .build(); 18 | /// ``` 19 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 20 | pub struct Separator(pub(crate) char); 21 | 22 | impl Separator { 23 | /// Creates a new separator, checking to make sure it is valid. 24 | /// 25 | /// A valid separator is a character that is not in the 26 | /// base-64 url-safe alphabet. 27 | pub fn new(separator: char) -> Result { 28 | if base64::in_alphabet(separator) { 29 | Err(InvalidSeparator(separator)) 30 | } else { 31 | Ok(Self(separator)) 32 | } 33 | } 34 | 35 | #[inline(always)] 36 | pub fn split<'a>(&self, value: &'a str) -> Result<(&'a str, &'a str), SeparatorNotFound> { 37 | let mut iterator = value.rsplitn(2, self.0); 38 | let second = iterator.next().unwrap(); 39 | let first = match iterator.next() { 40 | None => return Err(SeparatorNotFound { separator: *self }), 41 | Some(val) => val, 42 | }; 43 | Ok((first, second)) 44 | } 45 | } 46 | 47 | impl Default for Separator { 48 | fn default() -> Self { 49 | Self('.') 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/key_derivation.rs: -------------------------------------------------------------------------------- 1 | use generic_array::{ArrayLength, GenericArray}; 2 | use hmac::crypto_mac::Mac; 3 | use hmac::digest::{BlockInput, Digest, FixedOutput, Input, Reset}; 4 | 5 | /// This trait is called to derive a key for signing from a given key + salt. 6 | /// 7 | /// ## Remarks 8 | /// Key derivation is not indended to be used as a security method to make a 9 | /// complex key out of a short password. Instead, you should use large random 10 | /// secret keys. 11 | pub trait DeriveKey { 12 | fn derive_key(secret_key: &str, salt: &str) -> GenericArray 13 | where 14 | Digest: Input + BlockInput + FixedOutput + Reset + Default + Clone, 15 | Digest::BlockSize: ArrayLength + Clone, 16 | Digest::OutputSize: ArrayLength; 17 | } 18 | 19 | /// Derives a key by doing `digest(salt + secret_key)` 20 | pub struct Concat; 21 | 22 | /// Derives a key by doing `digest(salt + "signer" + secret_key)` 23 | pub struct DjangoConcat; 24 | 25 | /// Derives a secret key by doing `hmac(secret_key, input=salt)` 26 | pub struct Hmac; 27 | 28 | macro_rules! derive_key_impl { 29 | ($type:ty, ($secret_key:ident, $salt:ident) => $impl: block) => { 30 | impl DeriveKey for $type { 31 | fn derive_key( 32 | $secret_key: &str, 33 | $salt: &str, 34 | ) -> GenericArray 35 | where 36 | Digest: Input + BlockInput + FixedOutput + Reset + Default + Clone, 37 | Digest::BlockSize: ArrayLength + Clone, 38 | Digest::OutputSize: ArrayLength, 39 | { 40 | $impl 41 | } 42 | } 43 | }; 44 | } 45 | 46 | derive_key_impl!(Concat, (secret_key, salt) => { 47 | let mut digest = Digest::new(); 48 | digest.input(salt.as_bytes()); 49 | digest.input(secret_key.as_bytes()); 50 | digest.result() 51 | }); 52 | 53 | derive_key_impl!(DjangoConcat, (secret_key, salt) => { 54 | let mut digest = Digest::new(); 55 | digest.input(salt.as_bytes()); 56 | digest.input("signer".as_bytes()); 57 | digest.input(secret_key.as_bytes()); 58 | digest.result() 59 | }); 60 | 61 | derive_key_impl!(Hmac, (secret_key, salt) => { 62 | let mut mac: hmac::Hmac = 63 | hmac::Hmac::new_varkey(secret_key.as_bytes()).unwrap(); 64 | mac.input(salt.as_bytes()); 65 | mac.result().code() 66 | }); 67 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A rust re-implementation of the Python library [`itsdangerous`]. 2 | //! 3 | //! Essentially, this crate provides various helpers to pass data to untrusted environments 4 | //! and get it back safe and sound. Data is cryptographically signed to ensure that it has 5 | //! not been tampered with. 6 | //! 7 | //! ## Signers 8 | //! 9 | //! * [`Signer`], a signer that signs/unsigns arbitrary values. 10 | //! * [`TimestampSigner`], a signer that signs/unsigns arbitrary values attaching a signed 11 | //! timestamp so you know when the value was signed. 12 | //! 13 | //! ## Basic Example 14 | //! ```rust 15 | //! use std::time::Duration; 16 | //! use itsdangerous::{default_builder, Signer}; 17 | //! 18 | //! // Create a signer using the default builder, and an arbitrary secret key. 19 | //! let signer = default_builder("secret key").build(); 20 | //! 21 | //! // Sign an arbitrary string, and send it somewhere dangerous. 22 | //! let signed = signer.sign("hello world!"); 23 | //! 24 | //! // Unsign the string and validate that it hasn't been tampered with. 25 | //! let unsigned = signer.unsign(&signed).expect("Signature was not valid"); 26 | //! assert_eq!(unsigned, "hello world!"); 27 | //! ``` 28 | //! 29 | //! [`itsdangerous`]: https://github.com/pallets/itsdangerous/ 30 | 31 | #![cfg_attr(feature = "nightly", feature(test))] 32 | 33 | // TODO: One day un-comment this. 34 | // #![warn(missing_docs)] 35 | 36 | mod base64; 37 | mod error; 38 | mod separator; 39 | mod signer; 40 | mod timed; 41 | mod timestamp; 42 | mod traits; 43 | 44 | pub mod algorithm; 45 | pub mod key_derivation; 46 | 47 | #[cfg(feature = "serializer")] 48 | mod multi_serializer; 49 | #[cfg(feature = "serializer")] 50 | mod serde_serializer; 51 | #[cfg(feature = "serializer")] 52 | mod serializer_traits; 53 | 54 | pub use error::{ 55 | BadSignature, BadTimedSignature, InvalidSeparator, PayloadError, TimestampExpired, 56 | }; 57 | pub use separator::Separator; 58 | pub use signer::{default_builder, SignerBuilder}; 59 | pub use timed::UnsignedValue; 60 | pub use traits::{AsSigner, IntoTimestampSigner, Signer, TimestampSigner}; 61 | 62 | #[cfg(feature = "serializer")] 63 | pub use multi_serializer::MultiSerializer; 64 | #[cfg(feature = "serializer")] 65 | pub use serde_serializer::{ 66 | serializer_with_signer, timed_serializer_with_signer, NullEncoding, URLSafeEncoding, 67 | UnsignedTimedSerializerValue, UnverifiedTimedValue, UnverifiedValue, 68 | }; 69 | #[cfg(feature = "serializer")] 70 | pub use serializer_traits::{Encoding, Serializer, TimedSerializer}; 71 | -------------------------------------------------------------------------------- /src/timestamp.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 3 | 4 | use generic_array::{self, ArrayLength, GenericArray}; 5 | use typenum::{Unsigned, U8}; 6 | 7 | use crate::base64::{self, Base64Sized, Base64SizedEncoder}; 8 | use crate::error::BadTimedSignature; 9 | 10 | pub(crate) struct EncodedTimestamp> { 11 | array: GenericArray, 12 | length: usize, 13 | } 14 | 15 | impl> EncodedTimestamp { 16 | #[inline(always)] 17 | pub(crate) fn as_slice(&self) -> &[u8] { 18 | &self.array[..self.length] 19 | } 20 | 21 | #[inline(always)] 22 | pub(crate) fn length(&self) -> usize { 23 | self.length 24 | } 25 | 26 | #[inline(always)] 27 | pub(crate) fn as_str(&self) -> &str { 28 | // This is safe, because we know that an encoded timestamp's bytes 29 | // are within the url-safe base64 alphabet, which is plain ascii, 30 | // and totally fine to coerce to utf8. 31 | unsafe { std::str::from_utf8_unchecked(self.as_slice()) } 32 | } 33 | } 34 | 35 | type TimestampEncoder = Base64SizedEncoder; 36 | 37 | #[inline(always)] 38 | pub(crate) fn encode( 39 | timestamp: SystemTime, 40 | ) -> EncodedTimestamp<::OutputSize> { 41 | type InputSize = ::InputSize; 42 | // This is compatible with itsdangerous 1.x, which is what we're using in prod right now. 43 | let epoch_delta: u64 = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs(); 44 | 45 | // Fastest transform + strip + encode in the west. 46 | // - The nice thing is that this is compile time checked to be a sane transformation, e.g., 47 | // if TimestampEncoder was initialized using say a , the code just wouldn't compile! 48 | let timestamp_bytes: [u8; InputSize::USIZE] = unsafe { mem::transmute(epoch_delta.to_be()) }; 49 | 50 | // We need to strip the leading zero bytes, to do that, we take the leading 51 | // zeroes, and count em. 52 | let zero_index = timestamp_bytes.iter().take_while(|b| **b == 0u8).count(); 53 | 54 | // Finally, we can do the encoding. 55 | let mut array = GenericArray::default(); 56 | let length = base64::encode_slice(×tamp_bytes[zero_index..], array.as_mut_slice()); 57 | EncodedTimestamp { array, length } 58 | } 59 | 60 | #[inline(always)] 61 | pub(crate) fn decode(timestamp: &str) -> Result { 62 | type InputSize = ::InputSize; 63 | 64 | // Decode the base-64 encoded timestamp to bytes. 65 | let timestamp_bytes = base64::decode::(timestamp) 66 | .map_err(|_| BadTimedSignature::TimestampInvalid { timestamp })?; 67 | 68 | let timestamp_bytes = timestamp_bytes.as_slice(); 69 | 70 | // We need to then re-pad the bytes so we can then transmute it into an array. 71 | let mut input_array: GenericArray = GenericArray::default(); 72 | input_array[InputSize::USIZE - timestamp_bytes.len()..].copy_from_slice(timestamp_bytes); 73 | 74 | // Finally, take those bytes and re-interpret them 75 | let timestamp_secs: u64 = unsafe { generic_array::transmute(input_array) }; 76 | let timestamp_duration = Duration::from_secs(timestamp_secs.to_be()); 77 | 78 | // Convert from timestamp to a SystemTime - handle the overflow by returning TimestampInvalid. 79 | UNIX_EPOCH 80 | .checked_add(timestamp_duration) 81 | .ok_or_else(|| BadTimedSignature::TimestampInvalid { timestamp }) 82 | } 83 | -------------------------------------------------------------------------------- /src/multi_serializer.rs: -------------------------------------------------------------------------------- 1 | use serde::{de::DeserializeOwned, Serialize}; 2 | 3 | use crate::serializer_traits::UnsignToString; 4 | use crate::{BadSignature, Serializer}; 5 | 6 | /// The [`MultiSerializer`] provides the ability to sign values with a 7 | /// given serializer, but also try a series of fallback serializers. 8 | /// This is useful if you are rotating keys, and want to sign things 9 | /// using a new key, but allow an old serializer to unsign values. 10 | /// 11 | /// # Exmaple 12 | /// ```rust 13 | /// use itsdangerous::*; 14 | /// 15 | /// let primary = serializer_with_signer(default_builder("new key").build(), URLSafeEncoding); 16 | /// let fallback = serializer_with_signer(default_builder("old key").build(), URLSafeEncoding); 17 | /// 18 | /// let signed_with_new_key = primary.sign(&"Signed with new key".to_owned()).unwrap(); 19 | /// let signed_with_old_key = fallback.sign(&"Signed with old key".to_owned()).unwrap(); 20 | /// 21 | /// let multi = MultiSerializer::new(primary).add_fallback(fallback); 22 | /// 23 | /// assert_eq!(multi.unsign::(&signed_with_new_key).unwrap(), "Signed with new key"); 24 | /// assert_eq!(multi.unsign::(&signed_with_old_key).unwrap(), "Signed with old key"); 25 | /// ``` 26 | pub struct MultiSerializer { 27 | primary_serializer: PrimarySerializer, 28 | fallback_serializers: Vec>, 29 | } 30 | 31 | impl MultiSerializer 32 | where 33 | PrimarySerializer: Serializer, 34 | { 35 | /// Constructs a new [`MultiSerializer`] with a given [`Serializer`] as the primary 36 | /// serializer. The primary serializer is the one that will be used to sign values, 37 | /// and the first serializer that will be attempted while trying to unsign. 38 | /// 39 | /// # Remarks 40 | /// If the primary serializer is used to unsign a value, no dynamic dispatch takes 41 | /// place. That is to say, the [`MultiSerializer`] is its fastest when only the 42 | /// primary serializer is required to unsign a value, and when signing a value, 43 | /// it is a zero-cost abstraction. 44 | pub fn new(primary_serializer: PrimarySerializer) -> Self { 45 | Self { 46 | primary_serializer, 47 | fallback_serializers: Vec::new(), 48 | } 49 | } 50 | 51 | /// Adds a [`Serializer`] to as a fallback, that will be attempted to be used to 52 | /// unsign a value if the primary serializer fails to unsign a value. 53 | /// 54 | /// # Remarks 55 | /// Fallback serializers are attempted in the order they are added. For optimal 56 | /// performance when using fallbacks, add them in the order they will probably 57 | /// be used. Meaning, if you have a 2 fallbacks, consider adding the one you 58 | /// expect to be sucecessful before the other. 59 | pub fn add_fallback( 60 | mut self, 61 | fallback_serializer: FallbackSerializer, 62 | ) -> Self 63 | where 64 | FallbackSerializer: UnsignToString + 'static, 65 | { 66 | self.fallback_serializers 67 | .push(Box::new(fallback_serializer)); 68 | 69 | self 70 | } 71 | } 72 | 73 | impl Serializer for MultiSerializer 74 | where 75 | PrimarySerializer: Serializer, 76 | { 77 | fn sign(&self, value: &T) -> serde_json::Result { 78 | self.primary_serializer.sign(value) 79 | } 80 | 81 | fn unsign<'a, T: DeserializeOwned>(&'a self, value: &'a str) -> Result> { 82 | let primary_serializer_error = match self.primary_serializer.unsign(value) { 83 | Ok(unsigned) => return Ok(unsigned), 84 | Err(err) => err, 85 | }; 86 | 87 | for serializer in &self.fallback_serializers { 88 | if let Ok(unsigned) = serializer.unsign_to_string(value) { 89 | return serde_json::from_str(&unsigned).map_err(|e| BadSignature::PayloadInvalid { 90 | value, 91 | error: e.into(), 92 | }); 93 | } 94 | } 95 | 96 | Err(primary_serializer_error) 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use crate::*; 103 | 104 | #[test] 105 | fn test_multi_serializer() { 106 | let primary = serializer_with_signer(default_builder("primary").build(), URLSafeEncoding); 107 | let secondary = serializer_with_signer(default_builder("secondary").build(), NullEncoding); 108 | let irrelevant = 109 | serializer_with_signer(default_builder("irrelevant").build(), NullEncoding); 110 | 111 | let a = primary.sign(&"hello".to_owned()).unwrap(); 112 | let b = secondary.sign(&"world".to_owned()).unwrap(); 113 | let c = irrelevant.sign(&"danger!".to_owned()).unwrap(); 114 | 115 | let multi = MultiSerializer::new(primary).add_fallback(secondary); 116 | 117 | assert_eq!(multi.sign(&"hello".to_owned()).unwrap(), a); 118 | assert_eq!(multi.unsign::(&a).unwrap(), "hello".to_owned()); 119 | assert_eq!(multi.unsign::(&b).unwrap(), "world".to_owned()); 120 | assert!(multi.unsign::(&c).is_err()); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/algorithm.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use generic_array::{arr, typenum, ArrayLength, GenericArray}; 4 | use hmac::crypto_mac::{Mac, MacResult}; 5 | use hmac::digest::{BlockInput, FixedOutput, Input, Reset}; 6 | use hmac::Hmac; 7 | use typenum::Unsigned; 8 | 9 | use crate::base64::{self, URLSafeBase64Encode}; 10 | 11 | /// A trait which implements signature generation functionality. 12 | pub trait SigningAlgorithm { 13 | type OutputSize: ArrayLength + Unsigned; 14 | type Signer: Signer; 15 | 16 | /// Returns a signer that can be used to build a signature for a given key + values. 17 | fn get_signer(key: &[u8]) -> Self::Signer { 18 | Self::Signer::new(key) 19 | } 20 | 21 | /// Returns the signature for a given key + value. 22 | fn get_signature(key: &[u8], value: &[u8]) -> Signature { 23 | Self::get_signer(key).input_chained(value).sign() 24 | } 25 | } 26 | 27 | /// A trait which implements a Signer, which you can append 28 | /// inputs to, and then generate a final signature with. 29 | pub trait Signer: Sized { 30 | type OutputSize: ArrayLength; 31 | 32 | fn new(key: &[u8]) -> Self; 33 | 34 | fn input(&mut self, value: &[u8]); 35 | 36 | fn sign(self) -> Signature; 37 | 38 | fn input_chained(mut self, value: &[u8]) -> Self { 39 | self.input(value); 40 | self 41 | } 42 | } 43 | 44 | /// Provides an algorithm that does not perform any signing and 45 | /// returns an empty signature. 46 | pub struct NoneAlgorithm; 47 | 48 | impl SigningAlgorithm for NoneAlgorithm { 49 | type OutputSize = typenum::U0; 50 | type Signer = NoneSigner; 51 | } 52 | 53 | #[doc(hidden)] 54 | pub struct NoneSigner; 55 | impl Signer for NoneSigner { 56 | type OutputSize = typenum::U0; 57 | 58 | fn new(_key: &[u8]) -> Self { 59 | Self {} 60 | } 61 | 62 | #[inline(always)] 63 | fn input(&mut self, _value: &[u8]) { 64 | // Does nothing. 65 | } 66 | 67 | #[inline(always)] 68 | fn sign(self) -> Signature { 69 | MacResult::new(arr![u8; ]).into() 70 | } 71 | } 72 | 73 | /// Provides an algorithm that does signature generation using HMAC's, 74 | /// given a specific Digest. 75 | pub struct HMACAlgorithm(PhantomData); 76 | 77 | impl SigningAlgorithm for HMACAlgorithm 78 | where 79 | Digest: Input + BlockInput + FixedOutput + Reset + Default + Clone, 80 | Digest::BlockSize: ArrayLength + Clone, 81 | Digest::OutputSize: ArrayLength, 82 | { 83 | type OutputSize = Digest::OutputSize; 84 | type Signer = HMACSigner; 85 | } 86 | 87 | #[doc(hidden)] 88 | pub struct HMACSigner(Hmac) 89 | where 90 | Digest: Input + BlockInput + FixedOutput + Reset + Default + Clone, 91 | Digest::BlockSize: ArrayLength + Clone, 92 | Digest::OutputSize: ArrayLength; 93 | 94 | impl Signer for HMACSigner 95 | where 96 | Digest: Input + BlockInput + FixedOutput + Reset + Default + Clone, 97 | Digest::BlockSize: ArrayLength + Clone, 98 | Digest::OutputSize: ArrayLength, 99 | { 100 | type OutputSize = Digest::OutputSize; 101 | 102 | fn new(key: &[u8]) -> Self { 103 | let mac: Hmac = Hmac::new_varkey(key).unwrap(); 104 | Self(mac) 105 | } 106 | 107 | #[inline(always)] 108 | fn input(&mut self, value: &[u8]) { 109 | self.0.input(value) 110 | } 111 | 112 | #[inline(always)] 113 | fn sign(self) -> Signature { 114 | self.0.result().into() 115 | } 116 | } 117 | 118 | /// Represents a computed signature. 119 | /// 120 | /// Two signatures of the same type can be compared safely using Eq/PartialEq, 121 | /// thanks to the underlying constant time comparison provided by MacResult. 122 | #[derive(Eq)] 123 | pub struct Signature>(MacResult); 124 | 125 | impl> Signature { 126 | #[inline(always)] 127 | fn code(self) -> GenericArray { 128 | self.0.code() 129 | } 130 | } 131 | 132 | impl> URLSafeBase64Encode for Signature { 133 | fn base64_encode_str(self, target: &mut String) { 134 | base64::encode_str(self.code().as_slice(), target) 135 | } 136 | } 137 | 138 | impl> PartialEq for Signature { 139 | fn eq(&self, x: &Signature) -> bool { 140 | self.0 == x.0 141 | } 142 | } 143 | 144 | impl> From> for Signature { 145 | fn from(mac: MacResult) -> Self { 146 | Self(mac) 147 | } 148 | } 149 | 150 | impl> From> for Signature { 151 | fn from(code: GenericArray) -> Self { 152 | Self(MacResult::new(code)) 153 | } 154 | } 155 | 156 | #[cfg(test)] 157 | mod test { 158 | use super::*; 159 | use sha1::Sha1; 160 | #[test] 161 | fn test_hmac_algorithm() { 162 | type Algorithm = HMACAlgorithm; 163 | let signature = Algorithm::get_signature(b"foo", b"bar"); 164 | let signature2 = Algorithm::get_signer(b"foo").input_chained(b"bar").sign(); 165 | 166 | assert!(signature == signature2); 167 | // This is tested against Python's `HMACAlgorithm` implementation. 168 | assert_eq!(signature.base64_encode(), "RrTsWGEXFU2s1J1mTl1j_ciO-1E"); 169 | } 170 | 171 | #[test] 172 | fn test_none_algorithm() { 173 | type Algorithm = NoneAlgorithm; 174 | let signature = Algorithm::get_signature(b"foo", b"bar"); 175 | let signature2 = Algorithm::get_signer(b"foo").input_chained(b"bar").sign(); 176 | 177 | assert!(signature == signature2); 178 | assert_eq!(signature.base64_encode(), ""); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use generic_array::ArrayLength; 4 | use typenum::Unsigned; 5 | 6 | use crate::algorithm::{Signature, Signer as AlgorithmSigner}; 7 | use crate::error::BadSignature; 8 | use crate::{BadTimedSignature, Separator, UnsignedValue}; 9 | 10 | /// A signer can sign and unsign bytes, validating the signature provided. 11 | /// 12 | /// A salt can be used to namespace the hash, so that a signed string is only 13 | /// valid for a given namespace. Leaving this at the default value or re-using a salt value 14 | /// across different parts of your application where the same signed value in one part can 15 | /// mean something different in another part is a security risk. 16 | /// 17 | /// # Basic Usage 18 | /// ```rust 19 | /// use itsdangerous::{default_builder, Signer}; 20 | /// 21 | /// // Create a signer using the default builder, and an arbitrary secret key. 22 | /// let signer = default_builder("secret key").build(); 23 | /// 24 | /// // Sign an arbitrary string. 25 | /// let signed = signer.sign("hello world!"); 26 | /// 27 | /// // Unsign the string and validate whether or not its expired. 28 | /// let unsigned = signer.unsign(&signed).expect("Signature was not valid"); 29 | /// assert_eq!(unsigned, "hello world!"); 30 | /// ``` 31 | pub trait Signer { 32 | /// Signs the given string. 33 | fn sign>(&self, value: S) -> String; 34 | 35 | /// Unsigns the given string. The logical inverse of [`sign`]. 36 | /// 37 | /// # Remarks 38 | /// 39 | /// This method performs zero copies or heap allocations and returns a reference to a slice 40 | /// of the provided `value`, If you need a copy, consider doing `unsign(..).to_owned()` 41 | /// to convert the [`&str`] to a [`String`]. 42 | /// 43 | /// [`&str`]: std::str 44 | /// [`sign`]: Signer::sign 45 | fn unsign<'a>(&'a self, value: &'a str) -> Result<&'a str, BadSignature<'a>>; 46 | 47 | fn separator(&self) -> Separator; 48 | 49 | /// Given a base-64 encoded signature, attempt to verify whether or not 50 | /// it is valid for the given `value`. 51 | fn verify_encoded_signature(&self, value: &[u8], encoded_signature: &[u8]) -> bool; 52 | 53 | /// Gets the output size in bytes of the base-64 encoded signature part that this 54 | /// signer will emit. 55 | fn signature_output_size(&self) -> usize; 56 | } 57 | 58 | pub trait GetSigner { 59 | type OutputSize: ArrayLength + Unsigned; 60 | type Signer: AlgorithmSigner; 61 | 62 | /// Returns a signer that can be used to build a signature for a given key + values. 63 | fn get_signer(&self) -> Self::Signer; 64 | 65 | /// Returns the signature for a given key + value. 66 | fn get_signature(&self, value: &[u8]) -> Signature { 67 | self.get_signer().input_chained(value).sign() 68 | } 69 | } 70 | 71 | /// A TimestampSigner wraps an inner Signer, giving it the ability to dish 72 | /// out signatures with timestamps. 73 | /// 74 | /// # Basic Usage 75 | /// ```rust 76 | /// use std::time::Duration; 77 | /// use itsdangerous::{default_builder, Signer, TimestampSigner, IntoTimestampSigner}; 78 | /// 79 | /// // Create a signer using the default builder, and an arbitrary secret key. 80 | /// let signer = default_builder("secret key").build().into_timestamp_signer(); 81 | /// 82 | /// // Sign an arbitrary string. 83 | /// let signed = signer.sign("hello world!"); 84 | /// 85 | /// // Unsign the string and validate whether or not its expired. 86 | /// let unsigned = signer.unsign(&signed).expect("Signature was not valid"); 87 | /// let value = unsigned 88 | /// .value_if_not_expired(Duration::from_secs(60)) 89 | /// .expect("Signature was expired"); 90 | /// assert_eq!(value, "hello world!"); 91 | /// ``` 92 | pub trait TimestampSigner { 93 | fn separator(&self) -> Separator; 94 | 95 | /// Signs a value with an arbitrary timestamp. 96 | fn sign_with_timestamp>(&self, value: S, timestamp: SystemTime) -> String; 97 | 98 | /// Signs a value using the current system timestamp (as provided by [`SystemTime::now`]). 99 | fn sign>(&self, value: S) -> String; 100 | 101 | /// The inverse of [`sign`] / [`sign_with_timestamp`], returning an [`UnsignedValue`], which you 102 | /// can grab the value, timestamp, and assert the max age of the signed value with. 103 | /// 104 | /// # Remarks 105 | /// 106 | /// This method performs zero copies or heap allocations and returns a reference to a slice 107 | /// of the provided `value`, inside of the [`UnsignedValue`] that is returned. If you need a 108 | /// copy, consider doing `unsigned_value.value().to_owned()` to convert the [`&str`] to a [`String`]. 109 | /// 110 | /// [`&str`]: std::str 111 | /// [`sign`]: TimestampSigner::sign 112 | /// [`sign_with_timestamp`]: TimestampSigner::sign_with_timestamp 113 | fn unsign<'a>(&'a self, value: &'a str) -> Result>; 114 | } 115 | 116 | pub trait IntoTimestampSigner { 117 | type TimestampSigner: TimestampSigner; 118 | 119 | /// Converts this [`Signer`] into a [`TimestampSigner`], giving it the ability 120 | /// to do signing with timestamps! 121 | fn into_timestamp_signer(self) -> Self::TimestampSigner; 122 | } 123 | 124 | /// Returns a referenec to the underlying [`Signer`]. 125 | pub trait AsSigner { 126 | type Signer: Signer; 127 | 128 | /// Returns a reference to the underlying [`Signer`] if you wish to use its methods. 129 | /// 130 | /// # Example 131 | /// ```rust 132 | /// use itsdangerous::{default_builder, TimestampSigner, IntoTimestampSigner, Signer, AsSigner}; 133 | /// 134 | /// let timestamp_signer = default_builder("hello world").build().into_timestamp_signer(); 135 | /// let signer = timestamp_signer.as_signer(); 136 | /// let signer = signer.sign("hello without a timestamp!"); 137 | /// ``` 138 | fn as_signer(&self) -> &Self::Signer; 139 | } 140 | -------------------------------------------------------------------------------- /src/timed.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, SystemTime}; 2 | 3 | use crate::algorithm::Signer as AlgorithmSigner; 4 | use crate::base64::URLSafeBase64Encode; 5 | use crate::error::BadTimedSignature; 6 | use crate::timestamp; 7 | use crate::traits::GetSigner; 8 | use crate::{AsSigner, Separator, Signer, TimestampSigner}; 9 | 10 | pub struct TimestampSignerImpl(TSigner); 11 | 12 | impl TimestampSignerImpl 13 | where 14 | TSigner: Signer + GetSigner, 15 | { 16 | pub(crate) fn with_signer(signer: TSigner) -> Self { 17 | Self(signer) 18 | } 19 | 20 | pub(crate) fn split<'a>( 21 | &'a self, 22 | value: &'a str, 23 | ) -> Result<(&'a str, &'a str), BadTimedSignature<'a>> { 24 | // Then we split it again, to extract the value & timestamp. 25 | self.0 26 | .separator() 27 | .split(value) 28 | .map_err(|_| BadTimedSignature::TimestampMissing { value }) 29 | } 30 | } 31 | 32 | impl TimestampSigner for TimestampSignerImpl 33 | where 34 | TSigner: Signer + GetSigner, 35 | { 36 | fn separator(&self) -> Separator { 37 | self.0.separator() 38 | } 39 | 40 | /// Signs a value with an arbitrary timestamp. 41 | fn sign_with_timestamp>(&self, value: S, timestamp: SystemTime) -> String { 42 | let value = value.as_ref(); 43 | let encoded_timestamp = timestamp::encode(timestamp); 44 | let separator = self.0.separator().0; 45 | 46 | // Generate the signature. 47 | let signature = self 48 | .0 49 | .get_signer() 50 | .input_chained(value.as_bytes()) 51 | .input_chained(&[separator as u8]) 52 | .input_chained(encoded_timestamp.as_slice()) 53 | .sign(); 54 | 55 | // Generate the signed output string. 56 | let mut output = String::with_capacity( 57 | value.len() + 1 + encoded_timestamp.length() + 1 + self.0.signature_output_size(), 58 | ); 59 | 60 | output.push_str(value); 61 | output.push(separator); 62 | output.push_str(encoded_timestamp.as_str()); 63 | output.push(separator); 64 | signature.base64_encode_str(&mut output); 65 | 66 | output 67 | } 68 | 69 | /// Signs a value using the current system timestamp (as provided by [`SystemTime::now`]). 70 | fn sign>(&self, value: S) -> String { 71 | self.sign_with_timestamp(value, SystemTime::now()) 72 | } 73 | 74 | /// The inverse of [`sign`] / [`sign_with_timestamp`], returning an [`UnsignedValue`], which you 75 | /// can grab the value, timestamp, and assert the max age of the signed value with. 76 | /// 77 | /// # Remarks 78 | /// 79 | /// This method performs zero copies or heap allocations and returns a reference to a slice 80 | /// of the provided `value`, inside of the [`UnsignedValue`] that is returned. If you need a 81 | /// copy, consider doing `unsigned_value.value().to_owned()` to convert the [`&str`] to a [`String`]. 82 | /// 83 | /// [`&str`]: std::str 84 | /// [`sign`]: TimestampSigner::sign 85 | /// [`sign_with_timestamp`]: TimestampSigner::sign_with_timestamp 86 | fn unsign<'a>(&'a self, value: &'a str) -> Result> { 87 | // The base unsigner gives us {value}{sep}{timestamp}. 88 | let value = self.0.unsign(value)?; 89 | let (value, timestamp) = self.split(value)?; 90 | let timestamp = timestamp::decode(timestamp)?; 91 | 92 | Ok(UnsignedValue { timestamp, value }) 93 | } 94 | } 95 | 96 | impl AsSigner for TimestampSignerImpl 97 | where 98 | TSigner: Signer, 99 | { 100 | type Signer = TSigner; 101 | 102 | fn as_signer(&self) -> &Self::Signer { 103 | &self.0 104 | } 105 | } 106 | 107 | /// Represents a value + timestamp that has been successfully unsigned by [`TimestampSigner::unsign`]. 108 | pub struct UnsignedValue<'a> { 109 | value: &'a str, 110 | timestamp: SystemTime, 111 | } 112 | 113 | impl<'a> UnsignedValue<'a> { 114 | /// The value that has been [`unsigned`]. This value is safe to use and 115 | /// was part of a payload that has been successfully [`unsigned`]. 116 | /// 117 | /// [`unsigned`]: TimestampSigner::unsign 118 | pub fn value(&self) -> &'a str { 119 | &self.value 120 | } 121 | 122 | /// The timestamp that the value was signed with. 123 | /// 124 | /// For conveniently unwrapping the value and enforcing a max age, 125 | /// consider using [`value_if_not_expired`]. 126 | /// 127 | /// [`value_if_not_expired`]: UnsignedValue::value_if_not_expired 128 | pub fn timestamp(&self) -> SystemTime { 129 | self.timestamp 130 | } 131 | 132 | /// Returns the value if the timestamp is not older than `max_age`. 133 | /// In the event that the timestamp is in the future, we'll consider that valid. 134 | /// 135 | /// If the value is expired, returns the [`BadTimedSignature::TimestampExpired`] 136 | /// vairant of [`BadTimedSignature`]. 137 | pub fn value_if_not_expired(self, max_age: Duration) -> Result<&'a str, BadTimedSignature<'a>> { 138 | match self.timestamp.elapsed() { 139 | Ok(duration) if duration > max_age => Err(BadTimedSignature::TimestampExpired { 140 | timestamp: self.timestamp, 141 | value: self.value, 142 | max_age, 143 | }), 144 | // Timestamp is in the future or hasn't expired yet. 145 | Ok(_) | Err(_) => Ok(self.value), 146 | } 147 | } 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use crate::{default_builder, IntoTimestampSigner, TimestampSigner}; 153 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 154 | 155 | #[test] 156 | fn test_sign() { 157 | let signer = default_builder("hello").build().into_timestamp_signer(); 158 | let timestamp = UNIX_EPOCH + Duration::from_secs(1560181622); 159 | let signed = signer.sign_with_timestamp("hello world", timestamp); 160 | 161 | assert_eq!(signed, "hello world.XP57dg.uBK_KvrfABr48ZHk6IrBINjpqp8"); 162 | let unsigned = signer.unsign(&signed).unwrap(); 163 | assert_eq!(unsigned.value(), "hello world"); 164 | assert_eq!(unsigned.timestamp(), timestamp); 165 | } 166 | 167 | #[test] 168 | fn test_sign_expired() { 169 | let signer = default_builder("hello").build().into_timestamp_signer(); 170 | let timestamp = SystemTime::now() - Duration::from_secs(60); 171 | let signed = signer.sign_with_timestamp("hello world", timestamp); 172 | let unsigned = signer.unsign(&signed).unwrap(); 173 | 174 | assert!(unsigned 175 | .value_if_not_expired(Duration::from_secs(30)) 176 | .is_err()); 177 | } 178 | #[test] 179 | fn test_sign_not_expired() { 180 | let signer = default_builder("hello").build().into_timestamp_signer(); 181 | let timestamp = SystemTime::now() - Duration::from_secs(60); 182 | let signed = signer.sign_with_timestamp("hello world", timestamp); 183 | let unsigned = signer.unsign(&signed).unwrap(); 184 | 185 | assert!(unsigned 186 | .value_if_not_expired(Duration::from_secs(90)) 187 | .is_ok()); 188 | } 189 | } 190 | 191 | #[cfg(all(test, feature = "nightly"))] 192 | mod bench { 193 | use crate::*; 194 | use std::time::{Duration, UNIX_EPOCH}; 195 | extern crate test; 196 | use test::Bencher; 197 | 198 | #[bench] 199 | fn bench_sign(bench: &mut Bencher) { 200 | let signer = default_builder("hello").build().into_timestamp_signer(); 201 | let timestamp = UNIX_EPOCH + Duration::from_secs(1560181622); 202 | 203 | bench.iter(|| signer.sign_with_timestamp("hello world", timestamp)) 204 | } 205 | 206 | #[bench] 207 | fn bench_unsign(bench: &mut Bencher) { 208 | let signer = default_builder("hello").build().into_timestamp_signer(); 209 | 210 | bench.iter(|| signer.unsign("hello world.D-AM9g.T7AHtE1DsJn4dzUb-oeOwpWWoX8")) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, SystemTime}; 2 | use std::{error, fmt, str}; 3 | 4 | use crate::base64; 5 | use crate::Separator; 6 | 7 | #[derive(Debug)] 8 | pub enum PayloadError { 9 | #[cfg(feature = "serializer")] 10 | Serde(serde_json::Error), 11 | Base64(base64::DecodeError), 12 | Utf8Error(str::Utf8Error), 13 | } 14 | 15 | #[derive(Debug)] 16 | pub struct SeparatorNotFound { 17 | pub separator: Separator, 18 | } 19 | 20 | /// Errors that can occur while unsigning a "signed value". 21 | #[derive(Debug)] 22 | pub enum BadSignature<'a> { 23 | /// A string was provided to unsign, but it did not contain 24 | /// the expected separator. 25 | SeparatorNotFound { separator: Separator }, 26 | /// The signature did not match what we expected it to be. 27 | SignatureMismatch { signature: &'a str, value: &'a str }, 28 | /// The payload is invalid, e.g. it cannot be parsed. 29 | PayloadInvalid { value: &'a str, error: PayloadError }, 30 | } 31 | 32 | /// Errors that can occur while unsigning a "signed value" using the timed signer. 33 | #[derive(Debug)] 34 | pub enum BadTimedSignature<'a> { 35 | /// A string was provided to unsign, but it did not contain 36 | /// the expected separator. 37 | SeparatorNotFound { separator: Separator }, 38 | /// The signature did not match what we expected it to be. 39 | SignatureMismatch { signature: &'a str, value: &'a str }, 40 | /// The payload is invalid, e.g. it cannot be parsed. 41 | PayloadInvalid { value: &'a str, error: PayloadError }, 42 | /// The timestamp is missing, but the value was signed with a correct 43 | /// secret key + salt. 44 | TimestampMissing { value: &'a str }, 45 | /// The timestamp was present and signed, but we weren't able to parse it back to 46 | /// a SystemTime. 47 | TimestampInvalid { timestamp: &'a str }, 48 | /// The timestamp expired - meaning that it was more than `max_age` ago. 49 | TimestampExpired { 50 | timestamp: SystemTime, 51 | max_age: Duration, 52 | value: &'a str, 53 | }, 54 | } 55 | 56 | pub struct TimestampExpired { 57 | pub timestamp: SystemTime, 58 | pub max_age: Duration, 59 | pub value: T, 60 | } 61 | 62 | /// Error that occurs when trying to construct a Separator with 63 | /// a char is in the base64 url-safe alphabet. 64 | #[derive(Debug)] 65 | pub struct InvalidSeparator(pub char); 66 | 67 | impl<'a> fmt::Display for BadSignature<'a> { 68 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 69 | match self { 70 | BadSignature::SeparatorNotFound { separator } => { 71 | write!(f, "Separator {:?} not found in value.", separator.0) 72 | } 73 | BadSignature::SignatureMismatch { signature, .. } => { 74 | write!(f, "Signature {:?} does not match.", signature) 75 | } 76 | BadSignature::PayloadInvalid { error, .. } => { 77 | write!(f, "Payload cannot be parsed because {:?}.", error) 78 | } 79 | } 80 | } 81 | } 82 | 83 | impl<'a> error::Error for BadSignature<'a> { 84 | fn description(&self) -> &str { 85 | match *self { 86 | BadSignature::SeparatorNotFound { .. } => "separator not found", 87 | BadSignature::SignatureMismatch { .. } => "signature does not match", 88 | BadSignature::PayloadInvalid { .. } => "payload invalid", 89 | } 90 | } 91 | 92 | fn cause(&self) -> Option<&dyn error::Error> { 93 | None 94 | } 95 | } 96 | 97 | impl<'a> fmt::Display for BadTimedSignature<'a> { 98 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 99 | match self { 100 | BadTimedSignature::SeparatorNotFound { separator, .. } => { 101 | write!(f, "Separator {:?} not found in value.", separator.0) 102 | } 103 | BadTimedSignature::SignatureMismatch { signature, .. } => { 104 | write!(f, "Signature {:?} does not match.", signature) 105 | } 106 | BadTimedSignature::PayloadInvalid { error, .. } => { 107 | write!(f, "Payload cannot be parsed because {:?}.", error) 108 | } 109 | BadTimedSignature::TimestampMissing { .. } => write!(f, "Timestamp missing"), 110 | BadTimedSignature::TimestampInvalid { timestamp } => { 111 | write!(f, "Timestamp {:?} is invalid", timestamp) 112 | } 113 | BadTimedSignature::TimestampExpired { 114 | timestamp, max_age, .. 115 | } => write!( 116 | f, 117 | "Timestamp {:?} is older than {:?} and is expired.", 118 | timestamp, max_age 119 | ), 120 | } 121 | } 122 | } 123 | 124 | impl<'a> error::Error for BadTimedSignature<'a> { 125 | fn description(&self) -> &str { 126 | match *self { 127 | BadTimedSignature::SeparatorNotFound { .. } => "separator not found", 128 | BadTimedSignature::SignatureMismatch { .. } => "signature does not match", 129 | BadTimedSignature::TimestampMissing { .. } => "timestamp missing", 130 | BadTimedSignature::TimestampInvalid { .. } => "timestamp invalid", 131 | BadTimedSignature::TimestampExpired { .. } => "timestamp expired", 132 | BadTimedSignature::PayloadInvalid { .. } => "payload invalid", 133 | } 134 | } 135 | 136 | fn cause(&self) -> Option<&dyn error::Error> { 137 | None 138 | } 139 | } 140 | 141 | impl<'a> From> for BadTimedSignature<'a> { 142 | fn from(bad_signature: BadSignature<'a>) -> Self { 143 | match bad_signature { 144 | BadSignature::SeparatorNotFound { separator } => { 145 | BadTimedSignature::SeparatorNotFound { separator } 146 | } 147 | BadSignature::SignatureMismatch { signature, value } => { 148 | BadTimedSignature::SignatureMismatch { signature, value } 149 | } 150 | BadSignature::PayloadInvalid { error, value } => { 151 | BadTimedSignature::PayloadInvalid { error, value } 152 | } 153 | } 154 | } 155 | } 156 | 157 | impl fmt::Display for InvalidSeparator { 158 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 159 | write!( 160 | f, 161 | "Separator {:?} is in the base64 alphabet, and thus cannot be used", 162 | self.0 163 | ) 164 | } 165 | } 166 | 167 | impl error::Error for InvalidSeparator { 168 | fn description(&self) -> &str { 169 | "invalid separator" 170 | } 171 | 172 | fn cause(&self) -> Option<&dyn error::Error> { 173 | None 174 | } 175 | } 176 | 177 | impl fmt::Display for SeparatorNotFound { 178 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 179 | write!(f, "Separator {:?} not found in value.", self.separator) 180 | } 181 | } 182 | 183 | impl error::Error for SeparatorNotFound { 184 | fn description(&self) -> &str { 185 | "separator not foundr" 186 | } 187 | 188 | fn cause(&self) -> Option<&dyn error::Error> { 189 | None 190 | } 191 | } 192 | 193 | impl<'a> From for BadSignature<'a> { 194 | fn from(error: SeparatorNotFound) -> Self { 195 | BadSignature::SeparatorNotFound { 196 | separator: error.separator, 197 | } 198 | } 199 | } 200 | 201 | impl<'a> From for BadTimedSignature<'a> { 202 | fn from(error: SeparatorNotFound) -> Self { 203 | BadTimedSignature::SeparatorNotFound { 204 | separator: error.separator, 205 | } 206 | } 207 | } 208 | 209 | impl fmt::Display for TimestampExpired { 210 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 211 | write!( 212 | f, 213 | "Timestamp {:?} is older than {:?} and is expired.", 214 | self.timestamp, self.max_age 215 | ) 216 | } 217 | } 218 | 219 | impl fmt::Debug for TimestampExpired { 220 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 221 | write!( 222 | f, 223 | "TimestampExpired {{ max_age: {:?}, timestamp: {:?} }}", 224 | self.max_age, self.timestamp 225 | ) 226 | } 227 | } 228 | 229 | impl error::Error for TimestampExpired { 230 | fn description(&self) -> &str { 231 | "timestamp expired" 232 | } 233 | 234 | fn cause(&self) -> Option<&dyn error::Error> { 235 | None 236 | } 237 | } 238 | 239 | impl From for PayloadError { 240 | fn from(error: base64::DecodeError) -> Self { 241 | PayloadError::Base64(error) 242 | } 243 | } 244 | 245 | #[cfg(feature = "serializer")] 246 | impl From for PayloadError { 247 | fn from(error: serde_json::Error) -> Self { 248 | PayloadError::Serde(error) 249 | } 250 | } 251 | 252 | #[cfg(feature = "serializer")] 253 | impl From for PayloadError { 254 | fn from(error: str::Utf8Error) -> Self { 255 | PayloadError::Utf8Error(error) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/base64.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Div, Mul, Rem}; 2 | 3 | use hmac::digest::generic_array::typenum::*; 4 | 5 | use base64; 6 | pub use base64::DecodeError; 7 | 8 | use hmac::digest::generic_array::{ArrayLength, GenericArray}; 9 | 10 | static BASE64_ALPHABET: &'static str = 11 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_="; 12 | 13 | /// A trait that allows a type to be safely encoded as a url-safe 14 | /// base64 string. 15 | pub trait URLSafeBase64Encode: Sized { 16 | #[cfg(test)] 17 | fn base64_encode(self) -> String { 18 | let mut target = String::new(); 19 | self.base64_encode_str(&mut target); 20 | target 21 | } 22 | 23 | fn base64_encode_str(self, target: &mut String); 24 | } 25 | 26 | /// Encodes a string as url safe base64. 27 | #[inline(always)] 28 | #[allow(dead_code)] 29 | pub(crate) fn encode(input: &T) -> String 30 | where 31 | T: ?Sized + AsRef<[u8]>, 32 | { 33 | base64::encode_config(input, base64::URL_SAFE_NO_PAD) 34 | } 35 | 36 | /// Encodes a string as url safe base64. 37 | #[inline(always)] 38 | #[allow(dead_code)] 39 | pub(crate) fn encode_slice(input: &T, target: &mut [u8]) -> usize 40 | where 41 | T: ?Sized + AsRef<[u8]>, 42 | { 43 | base64::encode_config_slice(input, base64::URL_SAFE_NO_PAD, target) 44 | } 45 | 46 | /// Encodes a string as url safe base64. 47 | #[inline(always)] 48 | pub(crate) fn encode_str(input: &T, target: &mut String) 49 | where 50 | T: ?Sized + AsRef<[u8]>, 51 | { 52 | base64::encode_config_buf(input, base64::URL_SAFE_NO_PAD, target) 53 | } 54 | 55 | pub(crate) struct DecodeResult> { 56 | array: GenericArray, 57 | length: usize, 58 | } 59 | 60 | impl> DecodeResult { 61 | pub(crate) fn as_slice(&self) -> &[u8] { 62 | &self.array[..self.length] 63 | } 64 | 65 | pub(crate) fn into_exact_inner(self) -> Result, DecodeError> { 66 | if self.array.len() != self.length { 67 | Err(DecodeError::InvalidLength) 68 | } else { 69 | Ok(self.array) 70 | } 71 | } 72 | } 73 | 74 | /// Decodes a base64 encoded string from `URLSafeBase64Encode` to a sized GenericArray. 75 | #[inline(always)] 76 | pub(crate) fn decode(input: &T) -> Result, DecodeError> 77 | where 78 | N: ArrayLength, 79 | T: ?Sized + AsRef<[u8]>, 80 | { 81 | let mut array = GenericArray::default(); 82 | let input = input.as_ref(); 83 | let input_len = input.len(); 84 | let output_len = array.len(); 85 | let required_output_len = input_len / 4 * 3; 86 | // There are not enough bytes present in the output array to decode this value, meaning it's 87 | // too big. We can give up here. 88 | if required_output_len > output_len { 89 | return Err(DecodeError::InvalidLength); 90 | } 91 | let length = base64::decode_config_slice(input, base64::URL_SAFE_NO_PAD, &mut array)?; 92 | Ok(DecodeResult { array, length }) 93 | } 94 | 95 | /// Decodes a base64 encoded string from `URLSafeBase64Encode` to a sized GenericArray. 96 | #[inline(always)] 97 | #[allow(dead_code)] 98 | pub(crate) fn decode_str(input: &T) -> Result, DecodeError> 99 | where 100 | T: ?Sized + AsRef<[u8]>, 101 | { 102 | base64::decode_config(input, base64::URL_SAFE_NO_PAD) 103 | } 104 | 105 | /// Returns whether or not a given character is in the base64 alphabet. 106 | pub(crate) fn in_alphabet(c: char) -> bool { 107 | BASE64_ALPHABET.contains(c) 108 | } 109 | 110 | /// A trait that is implemented by `Base64SizedEncoder` that provides facilities 111 | /// for encoding a GenericArray as base64. All sizing is computed during compile 112 | /// time. 113 | pub trait Base64Sized { 114 | type InputSize: ArrayLength; 115 | type OutputSize: ArrayLength; 116 | 117 | fn encode(input: GenericArray) -> GenericArray; 118 | fn output_size() -> usize; 119 | } 120 | 121 | pub struct Base64SizedEncoder(N); 122 | 123 | /// Implementation of the `Base64Sized` trait. This does the actual computation. 124 | /// 125 | /// A simple example is as follows: 126 | /// ```rust,compile_fail 127 | /// use crate::base64::{Base64Sized, Base64SizedEncoder}; 128 | /// use generic_array::*; 129 | /// 130 | /// let arr = arr![u8; 1, 2, 3]; 131 | /// let result = Base64SizedEncoder::encode(arr); 132 | /// ``` 133 | impl Base64Sized for Base64SizedEncoder 134 | where 135 | // ----------------------------------------------------------------------- 136 | // The algorithm for computing the size of a non-padded base-64 array is: 137 | // ((N / 3) * 4) + (N % 3) + Min((N % 3), 1) 138 | // The code below does that computation using the `typenum` type system. 139 | // The computation must be broken into partial type assertions for each 140 | // operation that will be performed. 141 | // 142 | // Breaking down the formula above involves breaking down each binary 143 | // operation (e.g. A [op] B, or A + B), and then building upon them. 144 | // 145 | // For example: (N / 3) * 4 is broken down into two operations, and 146 | // composes the computed type as follows, with trait assertions for 147 | // each incremental operation, eventually building up to the result (R) 148 | // 1: N / 3 = X (N: Div) 149 | // 2: X * 4 (Quot: Mul) 150 | // R: Prod, U4>> 151 | // ----------------------------------------------------------------------- 152 | // N is a type that represents an Unsigned number and an Array Length 153 | N: Unsigned + ArrayLength, 154 | // (N % 3) 155 | N: Rem, 156 | // (N / 3) 157 | N: Div, 158 | // (N / 3) * 4 159 | Quot: Mul, 160 | // Min((N % 3), 1) 161 | Mod: Min, 162 | // (N % 3) + Min(N % 3, 1) 163 | Mod: Add, U1>>, 164 | // ((N / 3) * 4) + (N % 3) + Min((N % 3), 1) 165 | Prod, U4>: Add, Minimum, U1>>>, 166 | // Finally evaluate the formula in parts above and enforce the trait bounds. 167 | Sum, U4>, Sum, Minimum, U1>>>: 168 | Unsigned + ArrayLength, 169 | { 170 | type InputSize = N; 171 | // This is a copy-pasta of the final `where` clause type assertion. 172 | type OutputSize = Sum, U4>, Sum, Minimum, U1>>>; 173 | 174 | fn encode(input: GenericArray) -> GenericArray { 175 | let mut output = GenericArray::default(); 176 | let size = encode_slice(input.as_slice(), output.as_mut_slice()); 177 | debug_assert_eq!(size, Self::OutputSize::to_usize()); 178 | output 179 | } 180 | 181 | fn output_size() -> usize { 182 | Self::OutputSize::to_usize() 183 | } 184 | } 185 | 186 | #[cfg(test)] 187 | mod tests { 188 | use super::*; 189 | 190 | #[test] 191 | fn test_base64_sized_encoder() { 192 | fn gen_string>() -> GenericArray { 193 | let mut output: GenericArray<_, _> = Default::default(); 194 | for c in output.as_mut_slice() { 195 | *c = 'a' as u8; 196 | } 197 | output 198 | } 199 | 200 | // Sanity check length calculation. 201 | assert_eq!(Base64SizedEncoder::::output_size(), 2); 202 | assert_eq!( 203 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 204 | &[89, 81] 205 | ); 206 | assert_eq!( 207 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 208 | &[89, 87, 69] 209 | ); 210 | assert_eq!( 211 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 212 | &[89, 87, 70, 104] 213 | ); 214 | assert_eq!( 215 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 216 | &[89, 87, 70, 104, 89, 81] 217 | ); 218 | assert_eq!( 219 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 220 | &[89, 87, 70, 104, 89, 87, 69] 221 | ); 222 | assert_eq!( 223 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 224 | &[89, 87, 70, 104, 89, 87, 70, 104] 225 | ); 226 | assert_eq!( 227 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 228 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 81] 229 | ); 230 | assert_eq!( 231 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 232 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 69] 233 | ); 234 | assert_eq!( 235 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 236 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104] 237 | ); 238 | assert_eq!( 239 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 240 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 81] 241 | ); 242 | assert_eq!( 243 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 244 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 69] 245 | ); 246 | assert_eq!( 247 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 248 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104] 249 | ); 250 | assert_eq!( 251 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 252 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 81] 253 | ); 254 | assert_eq!( 255 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 256 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 69] 257 | ); 258 | assert_eq!( 259 | Base64SizedEncoder::encode(gen_string::()).as_slice(), 260 | &[89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104, 89, 87, 70, 104] 261 | ); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/signer.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::marker::PhantomData; 3 | 4 | use generic_array::{ArrayLength, GenericArray}; 5 | use hmac::digest::{BlockInput, FixedOutput, Input, Reset}; 6 | use typenum::Unsigned; 7 | 8 | use crate::algorithm::{self, Signature, Signer as AlgorithmSigner}; 9 | use crate::base64::{self, Base64Sized, Base64SizedEncoder, URLSafeBase64Encode}; 10 | use crate::key_derivation; 11 | use crate::timed::TimestampSignerImpl; 12 | use crate::traits::GetSigner; 13 | use crate::{AsSigner, BadSignature, IntoTimestampSigner, Separator, Signer}; 14 | 15 | static DEFAULT_SALT: Cow<'static, str> = Cow::Borrowed("itsdangerous.Signer"); 16 | 17 | pub struct SignerBuilder { 18 | secret_key: Cow<'static, str>, 19 | salt: Cow<'static, str>, 20 | separator: Separator, 21 | _phantom: PhantomData<(Digest, Algorithm, KeyDerivation)>, 22 | } 23 | 24 | /// Constructs a default signer builder, using the [`sha1`] digest, [`hmac`], 25 | /// and the [`django concat`] key derivation. 26 | /// 27 | /// [`django concat`]: crate::key_derivation::DjangoConcat 28 | pub fn default_builder>>( 29 | secret_key: S, 30 | ) -> SignerBuilder, key_derivation::DjangoConcat> { 31 | SignerBuilder::new(secret_key) 32 | } 33 | 34 | impl SignerBuilder 35 | where 36 | Digest: Input + BlockInput + FixedOutput + Reset + Default + Clone, 37 | Digest::BlockSize: ArrayLength + Clone, 38 | Digest::OutputSize: ArrayLength, 39 | Algorithm: algorithm::SigningAlgorithm, 40 | Algorithm::OutputSize: ArrayLength, 41 | KeyDerivation: key_derivation::DeriveKey, 42 | { 43 | /// Constructs a new signer builder with a given secret key. 44 | pub fn new>>(secret_key: S) -> Self { 45 | Self { 46 | secret_key: secret_key.into(), 47 | salt: DEFAULT_SALT.clone(), 48 | separator: Default::default(), 49 | _phantom: PhantomData, 50 | } 51 | } 52 | 53 | /// Uses a specific salt with the signer. If no salt is defined, will 54 | /// default to `DEFAULT_SALT`. 55 | pub fn with_salt>>(mut self, salt: S) -> Self { 56 | self.salt = salt.into(); 57 | self 58 | } 59 | 60 | /// Uses a specific separator with the signer. If no separator is 61 | /// defined, will default to '.' 62 | pub fn with_separator(mut self, separator: Separator) -> Self { 63 | self.separator = separator; 64 | self 65 | } 66 | 67 | /// Builds a Signer using the configuration specified in this builder. 68 | pub fn build( 69 | self, 70 | ) -> SignerImpl> { 71 | let derived_key = KeyDerivation::derive_key::(&self.secret_key, &self.salt); 72 | 73 | SignerImpl { 74 | derived_key, 75 | separator: self.separator, 76 | _phantom: PhantomData, 77 | } 78 | } 79 | } 80 | 81 | pub struct SignerImpl 82 | where 83 | DerivedKeySize: ArrayLength, 84 | { 85 | derived_key: GenericArray, 86 | pub(crate) separator: Separator, 87 | _phantom: PhantomData<(Algorithm, SignatureEncoder)>, 88 | } 89 | 90 | impl 91 | SignerImpl 92 | where 93 | Algorithm: algorithm::SigningAlgorithm, 94 | DerivedKeySize: ArrayLength, 95 | SignatureEncoder: Base64Sized, 96 | { 97 | /// Given a base64-encoded signature, attempt to decode it and convert it 98 | /// to a Signature. 99 | /// 100 | /// A signature is considered base64 encoded if it was encoded using 101 | /// `URLSafeBase64Encode::base64_encode`. 102 | #[inline(always)] 103 | fn decode_signature( 104 | &self, 105 | encoded_signature: &[u8], 106 | ) -> Result, base64::DecodeError> { 107 | Ok(base64::decode(encoded_signature)? 108 | .into_exact_inner()? 109 | .into()) 110 | } 111 | 112 | /// Given a signature, attempt to verify whether or not it is valid 113 | /// for the given `value`. 114 | #[inline(always)] 115 | fn verify_signature( 116 | &self, 117 | value: &[u8], 118 | expected_signature: Signature, 119 | ) -> bool { 120 | let computed_signature = self.get_signature(value); 121 | expected_signature == computed_signature 122 | } 123 | } 124 | 125 | impl Signer 126 | for SignerImpl 127 | where 128 | Algorithm: algorithm::SigningAlgorithm, 129 | DerivedKeySize: ArrayLength, 130 | SignatureEncoder: Base64Sized, 131 | { 132 | fn signature_output_size(&self) -> usize { 133 | SignatureEncoder::OutputSize::USIZE 134 | } 135 | 136 | #[inline(always)] 137 | fn verify_encoded_signature(&self, value: &[u8], encoded_signature: &[u8]) -> bool { 138 | match self.decode_signature(encoded_signature) { 139 | Ok(sig) => self.verify_signature(value, sig), 140 | Err(_) => false, 141 | } 142 | } 143 | 144 | #[inline(always)] 145 | fn separator(&self) -> Separator { 146 | self.separator 147 | } 148 | 149 | #[inline(always)] 150 | fn sign>(&self, value: S) -> String { 151 | let value = value.as_ref(); 152 | // Pre-allocate a string with the correct size (for maximum speeds.) 153 | // This (albeit a bit artisnal approach) is much faster than using `format!(...)`. 154 | let mut output = 155 | String::with_capacity(value.len() + 1 + SignatureEncoder::OutputSize::USIZE); 156 | 157 | output.push_str(value); 158 | output.push(self.separator.0); 159 | self.get_signature(value.as_bytes()) 160 | .base64_encode_str(&mut output); 161 | 162 | output 163 | } 164 | 165 | #[inline(always)] 166 | fn unsign<'a>(&'a self, value: &'a str) -> Result<&'a str, BadSignature<'a>> { 167 | let (value, signature) = self.separator.split(&value)?; 168 | if self.verify_encoded_signature(value.as_bytes(), signature.as_bytes()) { 169 | Ok(value) 170 | } else { 171 | Err(BadSignature::SignatureMismatch { signature, value }) 172 | } 173 | } 174 | } 175 | 176 | impl GetSigner 177 | for SignerImpl 178 | where 179 | Algorithm: algorithm::SigningAlgorithm, 180 | DerivedKeySize: ArrayLength, 181 | { 182 | type OutputSize = Algorithm::OutputSize; 183 | type Signer = Algorithm::Signer; 184 | 185 | /// Gets the signature for a given value. 186 | #[inline(always)] 187 | fn get_signer(&self) -> Self::Signer { 188 | Self::Signer::new(self.derived_key.as_slice()) 189 | } 190 | } 191 | 192 | impl IntoTimestampSigner 193 | for SignerImpl 194 | where 195 | Algorithm: algorithm::SigningAlgorithm, 196 | DerivedKeySize: ArrayLength, 197 | SignatureEncoder: Base64Sized, 198 | { 199 | type TimestampSigner = TimestampSignerImpl; 200 | 201 | fn into_timestamp_signer(self) -> Self::TimestampSigner { 202 | TimestampSignerImpl::with_signer(self) 203 | } 204 | } 205 | 206 | impl AsSigner 207 | for SignerImpl 208 | where 209 | Algorithm: algorithm::SigningAlgorithm, 210 | DerivedKeySize: ArrayLength, 211 | SignatureEncoder: Base64Sized, 212 | { 213 | type Signer = Self; 214 | 215 | fn as_signer(&self) -> &Self::Signer { 216 | &self 217 | } 218 | } 219 | 220 | #[cfg(test)] 221 | mod tests { 222 | use super::*; 223 | use crate::Signer; 224 | 225 | #[test] 226 | fn test_signature_basic() { 227 | let signer = default_builder("hello").build(); 228 | let signature = signer.sign("this is a test"); 229 | // This is a compatibility test against python. 230 | assert_eq!(signature, "this is a test.hgGT0Zoara4L13FX3_xm-xmfa_0"); 231 | assert_eq!( 232 | signer 233 | .unsign("this is a test.hgGT0Zoara4L13FX3_xm-xmfa_0") 234 | .unwrap(), 235 | "this is a test" 236 | ); 237 | } 238 | 239 | #[test] 240 | fn test_non_default_separator() { 241 | let signer = default_builder("hello") 242 | .with_separator(Separator::new('!').unwrap()) 243 | .build(); 244 | let signature = signer.sign("this is a test"); 245 | assert_eq!(signature, "this is a test!hgGT0Zoara4L13FX3_xm-xmfa_0"); 246 | } 247 | 248 | #[test] 249 | fn test_default_separator() { 250 | assert!(!base64::in_alphabet(Separator::default().0)); 251 | } 252 | 253 | #[test] 254 | fn test_separator_rejects_invalid_char() { 255 | assert!(Separator::new('a').is_err()); 256 | } 257 | 258 | #[test] 259 | fn test_unsign_edge_cases() { 260 | let signer = default_builder("hello").build(); 261 | 262 | assert!(signer.unsign("").is_err()); 263 | assert!(signer.unsign("fish").is_err()); 264 | assert!(signer.unsign(".").is_err()); 265 | assert!(signer.unsign("w.").is_err()); 266 | assert!(signer.unsign(".w").is_err()); 267 | } 268 | } 269 | 270 | #[cfg(all(test, feature = "nightly"))] 271 | mod bench { 272 | use super::*; 273 | use crate::Signer; 274 | 275 | extern crate test; 276 | use test::Bencher; 277 | 278 | #[bench] 279 | fn bench_unsign(bench: &mut Bencher) { 280 | let signer = default_builder("hello").build(); 281 | bench.iter(|| signer.unsign("this is a test.hgGT0Zoara4L13FX3_xm-xmfa_0")) 282 | } 283 | 284 | #[bench] 285 | fn bench_sign(bench: &mut Bencher) { 286 | let signer = default_builder("hello").build(); 287 | bench.iter(|| signer.sign("this is a test")) 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/serde_serializer.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::time::{Duration, SystemTime}; 3 | 4 | use serde::{de::DeserializeOwned, Serialize}; 5 | use serde_json; 6 | 7 | use crate::error::{BadSignature, BadTimedSignature, PayloadError, TimestampExpired}; 8 | use crate::serializer_traits::UnsignToString; 9 | use crate::timestamp; 10 | use crate::{ 11 | base64, AsSigner, Encoding, Separator, Serializer, Signer, TimedSerializer, TimestampSigner, 12 | }; 13 | 14 | pub struct NullEncoding; 15 | pub struct URLSafeEncoding; 16 | 17 | pub struct SerializerImpl { 18 | signer: TSigner, 19 | encoding: TEncoding, 20 | } 21 | 22 | pub struct TimedSerializerImpl { 23 | signer: TSigner, 24 | encoding: TEncoding, 25 | } 26 | 27 | impl TimedSerializerImpl { 28 | pub fn signer(&self) -> &TSigner { 29 | &self.signer 30 | } 31 | } 32 | 33 | pub fn serializer_with_signer( 34 | signer: TSigner, 35 | encoding: TEncoding, 36 | ) -> SerializerImpl 37 | where 38 | TSigner: Signer, 39 | TEncoding: Encoding, 40 | { 41 | SerializerImpl { signer, encoding } 42 | } 43 | 44 | pub fn timed_serializer_with_signer( 45 | signer: TSigner, 46 | encoding: TEncoding, 47 | ) -> TimedSerializerImpl 48 | where 49 | TSigner: TimestampSigner, 50 | TEncoding: Encoding, 51 | { 52 | TimedSerializerImpl { signer, encoding } 53 | } 54 | 55 | impl Encoding for NullEncoding { 56 | fn encode<'a>(&self, serialized_input: String) -> String { 57 | serialized_input 58 | } 59 | 60 | fn decode<'a>(&self, encoded_input: String) -> Result { 61 | Ok(encoded_input) 62 | } 63 | } 64 | 65 | impl Encoding for URLSafeEncoding { 66 | fn encode<'a>(&self, serialized_input: String) -> String { 67 | base64::encode(&serialized_input) 68 | } 69 | 70 | fn decode<'a>(&self, encoded_input: String) -> Result { 71 | // TODO: Handle decompression from... you know... python land. 72 | let decoded = base64::decode_str(&encoded_input)?; 73 | Ok(String::from_utf8(decoded).map_err(|e| e.utf8_error())?) 74 | } 75 | } 76 | 77 | #[inline(always)] 78 | fn deserialize<'a, T: DeserializeOwned, Encoding: self::Encoding>( 79 | value: &'a str, 80 | encoding: &Encoding, 81 | ) -> Result> { 82 | let decoded = encoding 83 | .decode(value.to_string()) 84 | .map_err(|e| BadSignature::PayloadInvalid { 85 | value, 86 | error: e.into(), 87 | })?; 88 | serde_json::from_str(&decoded).map_err(|e| BadSignature::PayloadInvalid { 89 | value, 90 | error: e.into(), 91 | }) 92 | } 93 | 94 | impl Serializer for SerializerImpl 95 | where 96 | TSigner: Signer, 97 | TEncoding: Encoding, 98 | { 99 | fn sign(&self, value: &T) -> serde_json::Result { 100 | let serialized = serde_json::to_string(value)?; 101 | let encoded = self.encoding.encode(serialized); 102 | Ok(self.signer.sign(encoded)) 103 | } 104 | 105 | fn unsign<'a, T: DeserializeOwned>(&'a self, value: &'a str) -> Result> { 106 | let value = self.signer.unsign(value)?; 107 | deserialize(value, &self.encoding) 108 | } 109 | } 110 | 111 | impl UnsignToString for SerializerImpl 112 | where 113 | TSigner: Signer, 114 | TEncoding: Encoding, 115 | { 116 | fn unsign_to_string<'a>(&'a self, value: &'a str) -> Result> { 117 | let value = self.signer.unsign(value)?; 118 | self.encoding 119 | .decode(value.to_string()) 120 | .map_err(|e| BadSignature::PayloadInvalid { 121 | value, 122 | error: e.into(), 123 | }) 124 | } 125 | } 126 | 127 | impl AsSigner for SerializerImpl 128 | where 129 | TSigner: Signer, 130 | { 131 | type Signer = TSigner; 132 | 133 | fn as_signer(&self) -> &Self::Signer { 134 | &self.signer 135 | } 136 | } 137 | 138 | impl TimedSerializer for TimedSerializerImpl 139 | where 140 | TSigner: TimestampSigner, 141 | TEncoding: Encoding, 142 | { 143 | fn sign(&self, value: &T) -> serde_json::Result { 144 | self.sign_with_timestamp(value, SystemTime::now()) 145 | } 146 | 147 | fn sign_with_timestamp( 148 | &self, 149 | value: &T, 150 | timestamp: SystemTime, 151 | ) -> serde_json::Result { 152 | let serialized = serde_json::to_string(value)?; 153 | let encoded = self.encoding.encode(serialized); 154 | Ok(self.signer.sign_with_timestamp(encoded, timestamp)) 155 | } 156 | 157 | fn unsign<'a, T: DeserializeOwned>( 158 | &'a self, 159 | value: &'a str, 160 | ) -> Result, BadTimedSignature<'a>> { 161 | let value = self.signer.unsign(value)?; 162 | let timestamp = value.timestamp(); 163 | let value = value.value(); 164 | let deserialized_value = deserialize(value, &self.encoding)?; 165 | 166 | Ok(UnsignedTimedSerializerValue { 167 | value: deserialized_value, 168 | timestamp, 169 | }) 170 | } 171 | } 172 | 173 | /// Represents a value + timestamp that has been successfully unsigned by [`TimedSerializer::unsign`]. 174 | pub struct UnsignedTimedSerializerValue { 175 | value: T, 176 | timestamp: SystemTime, 177 | } 178 | 179 | impl UnsignedTimedSerializerValue { 180 | /// The value that has been [`unsigned`]. This value is safe to use and 181 | /// was part of a payload that has been successfully [`unsigned`]. 182 | /// 183 | /// [`unsigned`]: TimedSerializer::unsign 184 | pub fn value(self) -> T { 185 | self.value 186 | } 187 | 188 | /// The timestamp that the value was signed with. 189 | /// 190 | /// For conveniently unwrapping the value and enforcing a max age, 191 | /// consider using [`value_if_not_expired`]. 192 | /// 193 | /// [`value_if_not_expired`]: UnsignedTimedSerializerValue::value_if_not_expired 194 | pub fn timestamp(&self) -> SystemTime { 195 | self.timestamp 196 | } 197 | 198 | /// Returns the value if the timestamp is not older than `max_age`. 199 | /// In the event that the timestamp is in the future, we'll consider that valid. 200 | /// 201 | /// If the value is expired, returns [`TimestampExpired`]. 202 | pub fn value_if_not_expired(self, max_age: Duration) -> Result> { 203 | match self.timestamp.elapsed() { 204 | Ok(duration) if duration > max_age => Err(TimestampExpired { 205 | timestamp: self.timestamp, 206 | value: self.value, 207 | max_age, 208 | }), 209 | // Timestamp is in the future or hasn't expired yet. 210 | Ok(_) | Err(_) => Ok(self.value), 211 | } 212 | } 213 | } 214 | 215 | impl Deref for UnsignedTimedSerializerValue { 216 | type Target = T; 217 | fn deref(&self) -> &Self::Target { 218 | &self.value 219 | } 220 | } 221 | 222 | /// An [`UnverifiedValue`] is just that. A deserialized value that has not been verified against 223 | /// against a signer. This is useful if you want to deserialize something without verifying 224 | /// the signature, because you might need data in the unsigned value in order to look up the 225 | /// signing key in a database somewhere. 226 | /// 227 | /// # Example 228 | /// ```rust 229 | /// use itsdangerous::*; 230 | /// 231 | /// // One could imagine this looking up a signing key from a databse or something. 232 | /// fn get_signing_key(user_id: u64) -> &'static str { 233 | /// match user_id { 234 | /// 1 => "hello", 235 | /// 2 => "world", 236 | /// _ => panic!("unexpected user {:?}", user_id) 237 | /// } 238 | /// } 239 | /// 240 | /// fn get_serializer(user_id: u64) -> impl Serializer + AsSigner { 241 | /// serializer_with_signer(default_builder(get_signing_key(user_id)).build(), URLSafeEncoding) 242 | /// } 243 | /// 244 | /// // Let's create a token for a user with id 1. 245 | /// let token = get_serializer(1).sign(&1).unwrap(); 246 | /// 247 | /// // Now, let's say we've gotten that token from somewhere. We need to deserialize it, in order 248 | /// // to determine the signing key to use. `from_str` will fail if deserialization fails, not if 249 | /// // the signature is invalid. 250 | /// let unverified_user_id = UnverifiedValue::::from_str(Separator::default(), URLSafeEncoding, &token).unwrap(); 251 | /// let serializer = get_serializer(*unverified_user_id.unverified_value()); 252 | /// // We can now attempt to verify the token with a given serializer. 253 | /// assert_eq!(unverified_user_id.verify(&serializer).unwrap(), 1); 254 | /// ``` 255 | pub struct UnverifiedValue<'a, T> { 256 | unverified_value: T, 257 | unverified_raw_value: &'a str, 258 | unverified_signature: &'a str, 259 | } 260 | 261 | impl<'a, T: DeserializeOwned> UnverifiedValue<'a, T> { 262 | pub fn from_str( 263 | separator: Separator, 264 | encoding: TEncoding, 265 | input: &'a str, 266 | ) -> Result { 267 | let (unverified_raw_value, unverified_signature) = separator.split(input)?; 268 | let unverified_value = deserialize(unverified_raw_value, &encoding)?; 269 | 270 | Ok(UnverifiedValue { 271 | unverified_value, 272 | unverified_raw_value, 273 | unverified_signature, 274 | }) 275 | } 276 | 277 | // XXX: Doc 278 | pub fn unverified_value(&self) -> &T { 279 | &self.unverified_value 280 | } 281 | 282 | pub fn verify(self, signer: &TSigner) -> Result> { 283 | let value = self.unverified_raw_value; 284 | let signature = self.unverified_signature; 285 | 286 | if signer 287 | .as_signer() 288 | .verify_encoded_signature(value.as_bytes(), signature.as_bytes()) 289 | { 290 | Ok(self.unverified_value) 291 | } else { 292 | Err(BadSignature::SignatureMismatch { signature, value }) 293 | } 294 | } 295 | } 296 | 297 | pub struct UnverifiedTimedValue<'a, T> { 298 | unverified_value: T, 299 | unverified_raw_value: &'a str, 300 | unverified_signature: &'a str, 301 | unverified_timestamp: SystemTime, 302 | } 303 | 304 | impl<'a, T: DeserializeOwned> UnverifiedTimedValue<'a, T> { 305 | pub fn from_str( 306 | separator: Separator, 307 | encoding: TEncoding, 308 | input: &'a str, 309 | ) -> Result { 310 | let (unverified_raw_value, unverified_signature) = separator.split(input)?; 311 | let (unverified_raw_serialized_value, unverified_timestamp) = 312 | separator.split(unverified_raw_value)?; 313 | let unverified_timestamp = timestamp::decode(unverified_timestamp)?; 314 | let unverified_value = deserialize(unverified_raw_serialized_value, &encoding)?; 315 | 316 | Ok(UnverifiedTimedValue { 317 | unverified_value, 318 | unverified_raw_value, 319 | unverified_signature, 320 | unverified_timestamp, 321 | }) 322 | } 323 | 324 | pub fn unverified_value(&self) -> &T { 325 | &self.unverified_value 326 | } 327 | 328 | pub fn unverified_timestamp(&self) -> SystemTime { 329 | self.unverified_timestamp 330 | } 331 | 332 | pub fn verify( 333 | self, 334 | timestamp_signer: &TSigner, 335 | ) -> Result, BadTimedSignature<'a>> { 336 | let value = self.unverified_raw_value; 337 | let signature = self.unverified_signature; 338 | 339 | if timestamp_signer 340 | .as_signer() 341 | .verify_encoded_signature(value.as_bytes(), signature.as_bytes()) 342 | { 343 | Ok(UnsignedTimedSerializerValue { 344 | value: self.unverified_value, 345 | timestamp: self.unverified_timestamp, 346 | }) 347 | } else { 348 | Err(BadTimedSignature::SignatureMismatch { signature, value }) 349 | } 350 | } 351 | } 352 | 353 | #[cfg(test)] 354 | mod tests { 355 | use std::time::UNIX_EPOCH; 356 | 357 | use super::*; 358 | use crate::{default_builder, IntoTimestampSigner}; 359 | #[test] 360 | 361 | fn test_null_encoding() { 362 | let s = "hello world".to_owned(); 363 | let encoding = NullEncoding; 364 | assert_eq!(encoding.encode(s.clone()), s); 365 | assert_eq!(encoding.decode(s.clone()).unwrap(), s); 366 | } 367 | 368 | #[test] 369 | fn test_url_safe_encoding() { 370 | let s = "hello world".to_owned(); 371 | let encoded = "aGVsbG8gd29ybGQ".to_owned(); 372 | let encoding = URLSafeEncoding; 373 | assert_eq!(encoding.encode(s.clone()), encoded); 374 | assert_eq!(encoding.decode(encoded).unwrap(), s); 375 | } 376 | 377 | #[test] 378 | fn test_sign_null_encoding() { 379 | let signer = default_builder("hello world").build(); 380 | let serializer = serializer_with_signer(signer, NullEncoding); 381 | let signed = "[1,2,3].bq_ST5hV4J35lKdovyr_ng-ZIxU"; 382 | assert_eq!(serializer.sign(&vec![1, 2, 3]).unwrap(), signed); 383 | assert_eq!(serializer.unsign::>(signed).unwrap(), vec![1, 2, 3]); 384 | } 385 | 386 | #[test] 387 | fn test_unsign_unverified_good_signature() { 388 | let signer = default_builder("hello world").build(); 389 | let signed = "[1,2,3].bq_ST5hV4J35lKdovyr_ng-ZIxU"; 390 | let unverified_value: UnverifiedValue> = 391 | UnverifiedValue::from_str(signer.separator, NullEncoding, signed).unwrap(); 392 | let expected = vec![1, 2, 3]; 393 | assert_eq!(unverified_value.unverified_value(), &expected); 394 | assert_eq!(unverified_value.verify(&signer).unwrap(), expected); 395 | } 396 | 397 | #[test] 398 | fn test_unsign_unverified_bad_signature() { 399 | let signer = default_builder("not the right key lol").build(); 400 | let signed = "[1,2,3].bq_ST5hV4J35lKdovyr_ng-ZIxU"; 401 | let unverified_value: UnverifiedValue> = 402 | UnverifiedValue::from_str(signer.separator, NullEncoding, signed).unwrap(); 403 | let expected = vec![1, 2, 3]; 404 | assert_eq!(unverified_value.unverified_value(), &expected); 405 | assert!(unverified_value.verify(&signer).is_err()); 406 | } 407 | 408 | #[test] 409 | fn test_sign_url_safe_encoding() { 410 | let signer = default_builder("hello world").build(); 411 | let serializer = serializer_with_signer(signer, URLSafeEncoding); 412 | let signed = "WzEsMiwzXQ.ohh92zNcvFVoWHrPf5uumLp6mbQ"; 413 | assert_eq!(serializer.sign(&vec![1, 2, 3]).unwrap(), signed); 414 | assert_eq!(serializer.unsign::>(signed).unwrap(), vec![1, 2, 3]); 415 | } 416 | 417 | #[test] 418 | fn test_timed_sign_null_encoding() { 419 | let signer = default_builder("hello world") 420 | .build() 421 | .into_timestamp_signer(); 422 | let serializer = timed_serializer_with_signer(signer, NullEncoding); 423 | let timestamp = UNIX_EPOCH + Duration::from_secs(1560181622); 424 | let signed = "[1,2,3].XP57dg.azFnnbv1s1cilwCeXmeVlMmbqD4"; 425 | assert_eq!( 426 | serializer 427 | .sign_with_timestamp(&vec![1, 2, 3], timestamp) 428 | .unwrap(), 429 | signed 430 | ); 431 | let unsigned = serializer.unsign::>(signed).unwrap(); 432 | assert_eq!(unsigned.timestamp(), timestamp); 433 | assert_eq!(unsigned.value(), vec![1, 2, 3]); 434 | } 435 | 436 | #[test] 437 | fn test_unverified_timed_good_signature() { 438 | let signer = default_builder("hello world") 439 | .build() 440 | .into_timestamp_signer(); 441 | let timestamp = UNIX_EPOCH + Duration::from_secs(1560181622); 442 | let signed = "[1,2,3].XP57dg.azFnnbv1s1cilwCeXmeVlMmbqD4"; 443 | let unverified_value: UnverifiedTimedValue> = 444 | UnverifiedTimedValue::from_str(signer.separator(), NullEncoding, signed).unwrap(); 445 | let expected = vec![1, 2, 3]; 446 | assert_eq!(unverified_value.unverified_timestamp(), timestamp); 447 | assert_eq!(unverified_value.unverified_value(), &expected); 448 | assert_eq!(unverified_value.verify(&signer).unwrap().value(), expected); 449 | } 450 | 451 | #[test] 452 | fn test_unverified_timed_value_if_not_expired() { 453 | let signer = default_builder("hello world") 454 | .build() 455 | .into_timestamp_signer(); 456 | let serializer = timed_serializer_with_signer(signer, NullEncoding); 457 | let timestamp = SystemTime::now() - Duration::from_secs(30); 458 | let signed = serializer 459 | .sign_with_timestamp(&vec![1, 2, 3], timestamp) 460 | .unwrap(); 461 | 462 | let unsigned = serializer.unsign::>(&signed).unwrap(); 463 | assert!(unsigned 464 | .value_if_not_expired(Duration::from_secs(15)) 465 | .is_err()); 466 | 467 | let unsigned = serializer.unsign::>(&signed).unwrap(); 468 | assert_eq!( 469 | unsigned 470 | .value_if_not_expired(Duration::from_secs(60)) 471 | .unwrap(), 472 | vec![1, 2, 3] 473 | ); 474 | } 475 | 476 | #[test] 477 | fn test_timed_signer_impl_can_be_used_to_verify() { 478 | let signer = default_builder("hello world") 479 | .build() 480 | .into_timestamp_signer(); 481 | let separator = signer.separator().clone(); 482 | let serializer = timed_serializer_with_signer(signer, URLSafeEncoding); 483 | let signed = serializer.sign(&"whatever").unwrap(); 484 | let unverified: UnverifiedTimedValue = 485 | UnverifiedTimedValue::from_str(separator, URLSafeEncoding, &signed).unwrap(); 486 | assert_eq!(unverified.unverified_value(), "whatever"); 487 | let verified = unverified 488 | .verify(serializer.signer()) 489 | .expect("Failed to verify"); 490 | assert_eq!(&verified.value(), "whatever"); 491 | } 492 | } 493 | 494 | #[cfg(all(test, feature = "nightly"))] 495 | mod bench { 496 | use crate::*; 497 | extern crate test; 498 | use test::Bencher; 499 | 500 | #[bench] 501 | fn bench_sign(bench: &mut Bencher) { 502 | let signer = default_builder("hello world").build(); 503 | let serializer = serializer_with_signer(signer, NullEncoding); 504 | 505 | let value = vec![1, 2, 3]; 506 | bench.iter(|| serializer.sign(&value)) 507 | } 508 | 509 | #[bench] 510 | fn bench_unsign(bench: &mut Bencher) { 511 | let signer = default_builder("hello world").build(); 512 | let serializer = serializer_with_signer(signer, NullEncoding); 513 | let signed = "[1,2,3].D-AM9g.nHmuOEE3v5DuwHEW9noSBOvExO0"; 514 | bench.iter(|| serializer.unsign::>(&signed)) 515 | } 516 | } 517 | --------------------------------------------------------------------------------