diff --git a/src/core/common/authentication_key.rs b/src/core/common/authentication_key.rs index 086daf5..55606ed 100644 --- a/src/core/common/authentication_key.rs +++ b/src/core/common/authentication_key.rs @@ -1,22 +1,22 @@ -use std::marker::PhantomData; -use std::ops::Deref; - -pub(crate) struct AuthenticationKey { - pub(crate) version: PhantomData, - pub(crate) purpose: PhantomData, - pub(crate) key: Vec, -} - -impl AsRef<[u8]> for AuthenticationKey { - fn as_ref(&self) -> &[u8] { - &self.key - } -} - -impl Deref for AuthenticationKey { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.key - } -} +use std::marker::PhantomData; +use std::ops::Deref; + +pub(crate) struct AuthenticationKey { + pub(crate) version: PhantomData, + pub(crate) purpose: PhantomData, + pub(crate) key: Vec, +} + +impl AsRef<[u8]> for AuthenticationKey { + fn as_ref(&self) -> &[u8] { + &self.key + } +} + +impl Deref for AuthenticationKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.key + } +} diff --git a/src/core/common/authentication_key_impl/mod.rs b/src/core/common/authentication_key_impl/mod.rs index 6a543d5..1b29633 100644 --- a/src/core/common/authentication_key_impl/mod.rs +++ b/src/core/common/authentication_key_impl/mod.rs @@ -1,3 +1,3 @@ -mod v1_local; -mod v3_local; +mod v1_local; +mod v3_local; mod v4_local; \ No newline at end of file diff --git a/src/core/common/authentication_key_impl/v1_local.rs b/src/core/common/authentication_key_impl/v1_local.rs index fbfbb9e..be0d021 100644 --- a/src/core/common/authentication_key_impl/v1_local.rs +++ b/src/core/common/authentication_key_impl/v1_local.rs @@ -1,24 +1,24 @@ -#![cfg(feature = "v1_local")] -use std::marker::PhantomData; -use ring::hkdf; -use crate::core::{Local, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; -use crate::core::common::authentication_key::AuthenticationKey; -use crate::core::common::hkdf_key::HkdfKey; - -impl AuthenticationKey { - pub(crate) fn try_from( - message: &[u8; 24], - key: &PasetoSymmetricKey, - nonce: &PasetoNonce, - ) -> Result { - let info = message.as_ref(); - let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &nonce[..16]); - let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(32))?.try_into()?; - - Ok(Self { - version: PhantomData, - purpose: PhantomData, - key: out, - }) - } -} +#![cfg(feature = "v1_local")] +use std::marker::PhantomData; +use ring::hkdf; +use crate::core::{Local, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; +use crate::core::common::authentication_key::AuthenticationKey; +use crate::core::common::hkdf_key::HkdfKey; + +impl AuthenticationKey { + pub(crate) fn try_from( + message: &[u8; 24], + key: &PasetoSymmetricKey, + nonce: &PasetoNonce, + ) -> Result { + let info = message.as_ref(); + let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &nonce[..16]); + let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(32))?.try_into()?; + + Ok(Self { + version: PhantomData, + purpose: PhantomData, + key: out, + }) + } +} diff --git a/src/core/common/authentication_key_impl/v3_local.rs b/src/core/common/authentication_key_impl/v3_local.rs index 783bdef..f906fcd 100644 --- a/src/core/common/authentication_key_impl/v3_local.rs +++ b/src/core/common/authentication_key_impl/v3_local.rs @@ -1,19 +1,19 @@ -#![cfg(feature = "v3_local")] -use std::marker::PhantomData; -use ring::hkdf; -use crate::core::{Key, Local, PasetoError, PasetoSymmetricKey, V3}; -use crate::core::common::HkdfKey; - -impl crate::core::common::authentication_key::AuthenticationKey { - pub(crate) fn try_from(message: &Key<56>, key: &PasetoSymmetricKey) -> Result { - let info = message.as_ref(); - let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &[]); - let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(48))?.try_into()?; - - Ok(Self { - version: PhantomData, - purpose: PhantomData, - key: out, - }) - } +#![cfg(feature = "v3_local")] +use std::marker::PhantomData; +use ring::hkdf; +use crate::core::{Key, Local, PasetoError, PasetoSymmetricKey, V3}; +use crate::core::common::HkdfKey; + +impl crate::core::common::authentication_key::AuthenticationKey { + pub(crate) fn try_from(message: &Key<56>, key: &PasetoSymmetricKey) -> Result { + let info = message.as_ref(); + let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &[]); + let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(48))?.try_into()?; + + Ok(Self { + version: PhantomData, + purpose: PhantomData, + key: out, + }) + } } \ No newline at end of file diff --git a/src/core/common/authentication_key_impl/v4_local.rs b/src/core/common/authentication_key_impl/v4_local.rs index 13f0c7a..e12491f 100644 --- a/src/core/common/authentication_key_impl/v4_local.rs +++ b/src/core/common/authentication_key_impl/v4_local.rs @@ -1,22 +1,22 @@ -#![cfg(feature = "v4_local")] -use std::marker::PhantomData; -use std::ops::Deref; -use blake2::digest::consts::U32; -use blake2::{Blake2bMac, digest::Update}; -use blake2::digest::FixedOutput; -use digest::KeyInit; -use crate::core::{Key, Local, PasetoSymmetricKey, V4}; - -impl crate::core::common::authentication_key::AuthenticationKey { - pub(crate) fn from(message: &Key<56>, key: &PasetoSymmetricKey) -> Self { - let mut context = Blake2bMac::::new_from_slice(key.as_ref()).unwrap(); - context.update(message.as_ref()); - let binding = context.finalize_fixed(); - let key = binding.to_vec(); - Self { - version: PhantomData, - purpose: PhantomData, - key, - } - } +#![cfg(feature = "v4_local")] +use std::marker::PhantomData; +use std::ops::Deref; +use blake2::digest::consts::U32; +use blake2::{Blake2bMac, digest::Update}; +use blake2::digest::FixedOutput; +use digest::KeyInit; +use crate::core::{Key, Local, PasetoSymmetricKey, V4}; + +impl crate::core::common::authentication_key::AuthenticationKey { + pub(crate) fn from(message: &Key<56>, key: &PasetoSymmetricKey) -> Self { + let mut context = Blake2bMac::::new_from_slice(key.as_ref()).unwrap(); + context.update(message.as_ref()); + let binding = context.finalize_fixed(); + let key = binding.to_vec(); + Self { + version: PhantomData, + purpose: PhantomData, + key, + } + } } \ No newline at end of file diff --git a/src/core/common/authentication_key_separator.rs b/src/core/common/authentication_key_separator.rs index 7487f29..5e23785 100644 --- a/src/core/common/authentication_key_separator.rs +++ b/src/core/common/authentication_key_separator.rs @@ -1,44 +1,44 @@ -use std::fmt; -use std::fmt::Display; -use std::ops::{Add, Deref}; -use crate::core::{Key, Local, PasetoNonce}; - -#[derive(Debug)] -pub (crate) struct AuthenticationKeySeparator(&'static str); - -impl Display for AuthenticationKeySeparator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.0) - } -} - -impl Default for AuthenticationKeySeparator { - fn default() -> Self { - Self("paseto-auth-key-for-aead") - } -} - -impl Deref for AuthenticationKeySeparator { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0.as_bytes() - } -} - -impl AsRef for AuthenticationKeySeparator { - fn as_ref(&self) -> &str { - self.0 - } -} - -impl<'a, Version> Add<&PasetoNonce<'a, Version, Local>> for AuthenticationKeySeparator { - type Output = Key<56>; - - fn add(self, rhs: &PasetoNonce) -> Self::Output { - let mut output = [0u8; 56]; - output[..24].copy_from_slice(self.0.as_bytes()); - output[24..].copy_from_slice(rhs.as_ref()); - Key::<56>::from(output) - } -} +use std::fmt; +use std::fmt::Display; +use std::ops::{Add, Deref}; +use crate::core::{Key, Local, PasetoNonce}; + +#[derive(Debug)] +pub (crate) struct AuthenticationKeySeparator(&'static str); + +impl Display for AuthenticationKeySeparator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl Default for AuthenticationKeySeparator { + fn default() -> Self { + Self("paseto-auth-key-for-aead") + } +} + +impl Deref for AuthenticationKeySeparator { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_bytes() + } +} + +impl AsRef for AuthenticationKeySeparator { + fn as_ref(&self) -> &str { + self.0 + } +} + +impl<'a, Version> Add<&PasetoNonce<'a, Version, Local>> for AuthenticationKeySeparator { + type Output = Key<56>; + + fn add(self, rhs: &PasetoNonce) -> Self::Output { + let mut output = [0u8; 56]; + output[..24].copy_from_slice(self.0.as_bytes()); + output[24..].copy_from_slice(rhs.as_ref()); + Key::<56>::from(output) + } +} diff --git a/src/core/common/cipher_text.rs b/src/core/common/cipher_text.rs index 31e0f7b..7ee9a2e 100644 --- a/src/core/common/cipher_text.rs +++ b/src/core/common/cipher_text.rs @@ -1,20 +1,20 @@ -use std::marker::PhantomData; - -pub(crate) struct CipherText { - pub(crate) ciphertext: Vec, - pub(crate) version: PhantomData, - pub(crate) purpose: PhantomData, -} - -impl AsRef> for CipherText { - fn as_ref(&self) -> &Vec { - &self.ciphertext - } -} - -impl std::ops::Deref for CipherText { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.ciphertext - } +use std::marker::PhantomData; + +pub(crate) struct CipherText { + pub(crate) ciphertext: Vec, + pub(crate) version: PhantomData, + pub(crate) purpose: PhantomData, +} + +impl AsRef> for CipherText { + fn as_ref(&self) -> &Vec { + &self.ciphertext + } +} + +impl std::ops::Deref for CipherText { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.ciphertext + } } \ No newline at end of file diff --git a/src/core/common/cipher_text_impl/mod.rs b/src/core/common/cipher_text_impl/mod.rs index bc3e9b8..fffd6fa 100644 --- a/src/core/common/cipher_text_impl/mod.rs +++ b/src/core/common/cipher_text_impl/mod.rs @@ -1,5 +1,5 @@ -mod v1_public; -mod v1_local; -mod v2_local; -mod v3_local; +mod v1_public; +mod v1_local; +mod v2_local; +mod v3_local; mod v4_local; \ No newline at end of file diff --git a/src/core/common/cipher_text_impl/v1_local.rs b/src/core/common/cipher_text_impl/v1_local.rs index c299fea..2f3b2cb 100644 --- a/src/core/common/cipher_text_impl/v1_local.rs +++ b/src/core/common/cipher_text_impl/v1_local.rs @@ -1,27 +1,27 @@ -#![cfg(feature = "v1_local")] -use std::marker::PhantomData; -use aes::Aes256Ctr; -use aes::cipher::generic_array::GenericArray; -use aes::cipher::{NewCipher, StreamCipher}; -use crate::core::common::cipher_text::CipherText; -use crate::core::{Local, V1}; -use crate::core::common::EncryptionKey; - -impl CipherText { - pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { - let key = GenericArray::from_slice(encryption_key.as_ref()); - let nonce = GenericArray::from_slice(encryption_key.counter_nonce()); - let mut cipher = Aes256Ctr::new(key, nonce); - let mut ciphertext = vec![0u8; payload.as_ref().len()]; - - ciphertext.copy_from_slice(payload); - - cipher.apply_keystream(&mut ciphertext); - - CipherText { - ciphertext, - version: PhantomData, - purpose: PhantomData, - } - } +#![cfg(feature = "v1_local")] +use std::marker::PhantomData; +use aes::Aes256Ctr; +use aes::cipher::generic_array::GenericArray; +use aes::cipher::{NewCipher, StreamCipher}; +use crate::core::common::cipher_text::CipherText; +use crate::core::{Local, V1}; +use crate::core::common::EncryptionKey; + +impl CipherText { + pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { + let key = GenericArray::from_slice(encryption_key.as_ref()); + let nonce = GenericArray::from_slice(encryption_key.counter_nonce()); + let mut cipher = Aes256Ctr::new(key, nonce); + let mut ciphertext = vec![0u8; payload.as_ref().len()]; + + ciphertext.copy_from_slice(payload); + + cipher.apply_keystream(&mut ciphertext); + + CipherText { + ciphertext, + version: PhantomData, + purpose: PhantomData, + } + } } \ No newline at end of file diff --git a/src/core/common/cipher_text_impl/v1_public.rs b/src/core/common/cipher_text_impl/v1_public.rs index 57a2a8d..ef0402f 100644 --- a/src/core/common/cipher_text_impl/v1_public.rs +++ b/src/core/common/cipher_text_impl/v1_public.rs @@ -1,25 +1,25 @@ -#![cfg(feature = "v1_public")] -use std::marker::PhantomData; -use ring::signature::{RSA_PSS_2048_8192_SHA384, UnparsedPublicKey}; -use crate::core::common::{CipherText, PreAuthenticationEncoding}; -use crate::core::{Footer, Header, PasetoError, Public, V1}; - -impl CipherText { - pub(crate) fn try_verify(decoded_payload: &[u8], public_key: &impl AsRef<[u8]>, footer: &Footer) -> Result { - let signature = decoded_payload[(decoded_payload.len() - 256)..].as_ref(); - let public_key = UnparsedPublicKey::new(&RSA_PSS_2048_8192_SHA384, public_key); - let msg = decoded_payload[..(decoded_payload.len() - 256)].as_ref(); - - let pae = PreAuthenticationEncoding::parse(&[&Header::::default(), msg, footer]); - - public_key.verify(&pae, signature)?; - - let ciphertext = Vec::from(msg); - - Ok(CipherText { - ciphertext, - version: PhantomData, - purpose: PhantomData, - }) - } +#![cfg(feature = "v1_public")] +use std::marker::PhantomData; +use ring::signature::{RSA_PSS_2048_8192_SHA384, UnparsedPublicKey}; +use crate::core::common::{CipherText, PreAuthenticationEncoding}; +use crate::core::{Footer, Header, PasetoError, Public, V1}; + +impl CipherText { + pub(crate) fn try_verify(decoded_payload: &[u8], public_key: &impl AsRef<[u8]>, footer: &Footer) -> Result { + let signature = decoded_payload[(decoded_payload.len() - 256)..].as_ref(); + let public_key = UnparsedPublicKey::new(&RSA_PSS_2048_8192_SHA384, public_key); + let msg = decoded_payload[..(decoded_payload.len() - 256)].as_ref(); + + let pae = PreAuthenticationEncoding::parse(&[&Header::::default(), msg, footer]); + + public_key.verify(&pae, signature)?; + + let ciphertext = Vec::from(msg); + + Ok(CipherText { + ciphertext, + version: PhantomData, + purpose: PhantomData, + }) + } } \ No newline at end of file diff --git a/src/core/common/cipher_text_impl/v2_local.rs b/src/core/common/cipher_text_impl/v2_local.rs index 8e7b962..7ced024 100644 --- a/src/core/common/cipher_text_impl/v2_local.rs +++ b/src/core/common/cipher_text_impl/v2_local.rs @@ -1,60 +1,60 @@ -#![cfg(feature = "v2_local")] -use std::marker::PhantomData; -use chacha20poly1305::{KeyInit, XChaCha20Poly1305, XNonce}; -use chacha20poly1305::aead::{Aead, Payload}; -use crate::core::common::{CipherText, PreAuthenticationEncoding}; -use crate::core::{Local, PasetoError, PasetoSymmetricKey, V2}; - -impl CipherText { - pub(crate) fn try_decrypt_from( - key: &PasetoSymmetricKey, - nonce: &XNonce, - payload: &[u8], - pre_auth: &PreAuthenticationEncoding, - ) -> Result { - //let ciphertext = CipherText::try_from(&key, &nonce, &payload, &pae)?; - - let aead = XChaCha20Poly1305::new_from_slice(key.as_ref()).map_err(|_| PasetoError::Cryption)?; - //encrypt cipher_text - let ciphertext = aead - .decrypt( - nonce, - Payload { - msg: payload, - aad: pre_auth.as_ref(), - }, - ) - .map_err(|_| PasetoError::ChaChaCipherError)?; - - Ok(CipherText { - ciphertext, - version: PhantomData, - purpose: PhantomData, - }) - } - - pub(crate) fn try_from( - key: &PasetoSymmetricKey, - nonce: &XNonce, - payload: &[u8], - pre_auth: &PreAuthenticationEncoding, - ) -> Result { - let aead = XChaCha20Poly1305::new_from_slice(key.as_ref()).map_err(|_| PasetoError::Cryption)?; - //encrypt cipher_text - let ciphertext = aead - .encrypt( - nonce, - Payload { - msg: payload, - aad: pre_auth.as_ref(), - }, - ) - .map_err(|_| PasetoError::ChaChaCipherError)?; - - Ok(CipherText { - ciphertext, - version: PhantomData, - purpose: PhantomData, - }) - } +#![cfg(feature = "v2_local")] +use std::marker::PhantomData; +use chacha20poly1305::{KeyInit, XChaCha20Poly1305, XNonce}; +use chacha20poly1305::aead::{Aead, Payload}; +use crate::core::common::{CipherText, PreAuthenticationEncoding}; +use crate::core::{Local, PasetoError, PasetoSymmetricKey, V2}; + +impl CipherText { + pub(crate) fn try_decrypt_from( + key: &PasetoSymmetricKey, + nonce: &XNonce, + payload: &[u8], + pre_auth: &PreAuthenticationEncoding, + ) -> Result { + //let ciphertext = CipherText::try_from(&key, &nonce, &payload, &pae)?; + + let aead = XChaCha20Poly1305::new_from_slice(key.as_ref()).map_err(|_| PasetoError::Cryption)?; + //encrypt cipher_text + let ciphertext = aead + .decrypt( + nonce, + Payload { + msg: payload, + aad: pre_auth.as_ref(), + }, + ) + .map_err(|_| PasetoError::ChaChaCipherError)?; + + Ok(CipherText { + ciphertext, + version: PhantomData, + purpose: PhantomData, + }) + } + + pub(crate) fn try_from( + key: &PasetoSymmetricKey, + nonce: &XNonce, + payload: &[u8], + pre_auth: &PreAuthenticationEncoding, + ) -> Result { + let aead = XChaCha20Poly1305::new_from_slice(key.as_ref()).map_err(|_| PasetoError::Cryption)?; + //encrypt cipher_text + let ciphertext = aead + .encrypt( + nonce, + Payload { + msg: payload, + aad: pre_auth.as_ref(), + }, + ) + .map_err(|_| PasetoError::ChaChaCipherError)?; + + Ok(CipherText { + ciphertext, + version: PhantomData, + purpose: PhantomData, + }) + } } \ No newline at end of file diff --git a/src/core/common/cipher_text_impl/v3_local.rs b/src/core/common/cipher_text_impl/v3_local.rs index ba78e3c..fce2cde 100644 --- a/src/core/common/cipher_text_impl/v3_local.rs +++ b/src/core/common/cipher_text_impl/v3_local.rs @@ -1,26 +1,26 @@ -#![cfg(feature = "v3_local")] -use std::marker::PhantomData; -use aes::Aes256Ctr; -use aes::cipher::generic_array::GenericArray; -use aes::cipher::{NewCipher, StreamCipher}; -use crate::core::common::{CipherText, EncryptionKey}; -use crate::core::{Local, V3}; - -impl CipherText { - pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { - let key = GenericArray::from_slice(encryption_key.as_ref()); - let nonce = GenericArray::from_slice(encryption_key.counter_nonce()); - let mut cipher = Aes256Ctr::new(key, nonce); - let mut ciphertext = vec![0u8; payload.len()]; - - ciphertext.copy_from_slice(payload); - - cipher.apply_keystream(&mut ciphertext); - - CipherText { - ciphertext, - version: PhantomData, - purpose: PhantomData, - } - } +#![cfg(feature = "v3_local")] +use std::marker::PhantomData; +use aes::Aes256Ctr; +use aes::cipher::generic_array::GenericArray; +use aes::cipher::{NewCipher, StreamCipher}; +use crate::core::common::{CipherText, EncryptionKey}; +use crate::core::{Local, V3}; + +impl CipherText { + pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { + let key = GenericArray::from_slice(encryption_key.as_ref()); + let nonce = GenericArray::from_slice(encryption_key.counter_nonce()); + let mut cipher = Aes256Ctr::new(key, nonce); + let mut ciphertext = vec![0u8; payload.len()]; + + ciphertext.copy_from_slice(payload); + + cipher.apply_keystream(&mut ciphertext); + + CipherText { + ciphertext, + version: PhantomData, + purpose: PhantomData, + } + } } \ No newline at end of file diff --git a/src/core/common/cipher_text_impl/v4_local.rs b/src/core/common/cipher_text_impl/v4_local.rs index 726e44a..1307e88 100644 --- a/src/core/common/cipher_text_impl/v4_local.rs +++ b/src/core/common/cipher_text_impl/v4_local.rs @@ -1,22 +1,22 @@ -#![cfg(feature = "v4_local")] -use std::marker::PhantomData; -use chacha20::cipher::{KeyIvInit, StreamCipher}; -use crate::core::common::{CipherText, EncryptionKey}; -use crate::core::{Local, V4}; - -impl CipherText { - pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { - let mut ciphertext = vec![0u8; payload.len()]; - ciphertext.copy_from_slice(payload); - - let n2 = encryption_key.counter_nonce(); - let mut cipher = chacha20::XChaCha20::new(encryption_key.as_ref(), n2); - cipher.apply_keystream(&mut ciphertext); - - CipherText { - ciphertext, - version: PhantomData, - purpose: PhantomData, - } - } -} +#![cfg(feature = "v4_local")] +use std::marker::PhantomData; +use chacha20::cipher::{KeyIvInit, StreamCipher}; +use crate::core::common::{CipherText, EncryptionKey}; +use crate::core::{Local, V4}; + +impl CipherText { + pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { + let mut ciphertext = vec![0u8; payload.len()]; + ciphertext.copy_from_slice(payload); + + let n2 = encryption_key.counter_nonce(); + let mut cipher = chacha20::XChaCha20::new(encryption_key.as_ref(), n2); + cipher.apply_keystream(&mut ciphertext); + + CipherText { + ciphertext, + version: PhantomData, + purpose: PhantomData, + } + } +} diff --git a/src/core/common/encryption_key.rs b/src/core/common/encryption_key.rs index bed68f7..9de63c6 100644 --- a/src/core/common/encryption_key.rs +++ b/src/core/common/encryption_key.rs @@ -1,11 +1,11 @@ -use std::marker::PhantomData; - -#[derive(Default)] -pub(crate) struct EncryptionKey { - pub(crate) version: PhantomData, - pub(crate) purpose: PhantomData, - pub(crate) key: Vec, - #[cfg(any(feature = "v1_local", feature = "v3_local", feature = "v4_local"))] - pub(crate) nonce: Vec, -} - +use std::marker::PhantomData; + +#[derive(Default)] +pub(crate) struct EncryptionKey { + pub(crate) version: PhantomData, + pub(crate) purpose: PhantomData, + pub(crate) key: Vec, + #[cfg(any(feature = "v1_local", feature = "v3_local", feature = "v4_local"))] + pub(crate) nonce: Vec, +} + diff --git a/src/core/common/encryption_key_impl/mod.rs b/src/core/common/encryption_key_impl/mod.rs index a2c29e7..1a51164 100644 --- a/src/core/common/encryption_key_impl/mod.rs +++ b/src/core/common/encryption_key_impl/mod.rs @@ -1,4 +1,4 @@ -mod v1_local; -mod v3_local; -mod v4_local; +mod v1_local; +mod v3_local; +mod v4_local; mod v_local; \ No newline at end of file diff --git a/src/core/common/encryption_key_impl/v1_local.rs b/src/core/common/encryption_key_impl/v1_local.rs index 40ddf77..b9a6372 100644 --- a/src/core/common/encryption_key_impl/v1_local.rs +++ b/src/core/common/encryption_key_impl/v1_local.rs @@ -1,28 +1,28 @@ -#![cfg(feature = "v1_local")] -use std::marker::PhantomData; -use ring::hkdf; -use crate::core::common::EncryptionKey; -use crate::core::{Key, Local, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; -use crate::core::common::hkdf_key::HkdfKey; -impl EncryptionKey { - pub(crate) fn try_from( - message: &Key<21>, - key: &PasetoSymmetricKey, - nonce: &PasetoNonce, - ) -> Result { - let info = message.as_ref(); - let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &nonce[..16]); - let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(32))?.try_into()?; - - Ok(Self { - version: PhantomData, - purpose: PhantomData, - key: out.to_vec(), - nonce: nonce[16..].to_vec(), - }) - } - - pub(crate) fn counter_nonce(&self) -> &Vec { - &self.nonce - } +#![cfg(feature = "v1_local")] +use std::marker::PhantomData; +use ring::hkdf; +use crate::core::common::EncryptionKey; +use crate::core::{Key, Local, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; +use crate::core::common::hkdf_key::HkdfKey; +impl EncryptionKey { + pub(crate) fn try_from( + message: &Key<21>, + key: &PasetoSymmetricKey, + nonce: &PasetoNonce, + ) -> Result { + let info = message.as_ref(); + let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &nonce[..16]); + let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(32))?.try_into()?; + + Ok(Self { + version: PhantomData, + purpose: PhantomData, + key: out.to_vec(), + nonce: nonce[16..].to_vec(), + }) + } + + pub(crate) fn counter_nonce(&self) -> &Vec { + &self.nonce + } } \ No newline at end of file diff --git a/src/core/common/encryption_key_impl/v3_local.rs b/src/core/common/encryption_key_impl/v3_local.rs index bb667fa..f11ce85 100644 --- a/src/core/common/encryption_key_impl/v3_local.rs +++ b/src/core/common/encryption_key_impl/v3_local.rs @@ -1,24 +1,24 @@ -#![cfg(feature = "v3_local")] -use std::marker::PhantomData; -use ring::hkdf; -use crate::core::{Key, Local, PasetoError, PasetoSymmetricKey, V3}; -use crate::core::common::{EncryptionKey, HkdfKey}; -impl EncryptionKey { - pub(crate) fn try_from(message: &Key<53>, key: &PasetoSymmetricKey) -> Result { - let info = message.as_ref(); - let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &[]); - - let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(48))?.try_into()?; - - Ok(Self { - version: PhantomData, - purpose: PhantomData, - key: out[..32].to_vec(), - nonce: out[32..].to_vec(), - }) - } - - pub(crate) fn counter_nonce(&self) -> &Vec { - &self.nonce - } -} +#![cfg(feature = "v3_local")] +use std::marker::PhantomData; +use ring::hkdf; +use crate::core::{Key, Local, PasetoError, PasetoSymmetricKey, V3}; +use crate::core::common::{EncryptionKey, HkdfKey}; +impl EncryptionKey { + pub(crate) fn try_from(message: &Key<53>, key: &PasetoSymmetricKey) -> Result { + let info = message.as_ref(); + let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &[]); + + let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(48))?.try_into()?; + + Ok(Self { + version: PhantomData, + purpose: PhantomData, + key: out[..32].to_vec(), + nonce: out[32..].to_vec(), + }) + } + + pub(crate) fn counter_nonce(&self) -> &Vec { + &self.nonce + } +} diff --git a/src/core/common/encryption_key_impl/v4_local.rs b/src/core/common/encryption_key_impl/v4_local.rs index 31ff248..9896d23 100644 --- a/src/core/common/encryption_key_impl/v4_local.rs +++ b/src/core/common/encryption_key_impl/v4_local.rs @@ -1,48 +1,48 @@ -#![cfg(feature = "v4_local")] -use std::marker::PhantomData; -use std::ops::Deref; -use blake2::digest::consts::U56; -use blake2::{Blake2bMac, digest::Update}; -use blake2::digest::FixedOutput; -use digest::KeyInit; -use chacha20::{XNonce, Key}; -use crate::core::common::EncryptionKey; -use crate::core::{Local, PasetoSymmetricKey, V4}; - -impl EncryptionKey { - pub(crate) fn from(message: &crate::core::Key<53>, key: &PasetoSymmetricKey) -> Self { - let mut context = Blake2bMac::::new_from_slice(key.as_ref()).unwrap(); - context.update(message.as_ref()); - let binding = context.finalize_fixed(); - let context = binding.to_vec(); - let key = context[..32].to_vec(); - let nonce = context[32..56].to_vec(); - - assert_eq!(key.len(), 32); - assert_eq!(nonce.len(), 24); - Self { - key, - nonce, - version: PhantomData, - purpose: PhantomData, - } - - } - pub(crate) fn counter_nonce(&self) -> &XNonce { - XNonce::from_slice(&self.nonce) - } -} - -impl AsRef for EncryptionKey { - fn as_ref(&self) -> &Key { - Key::from_slice(&self.key) - } -} - -impl Deref for EncryptionKey { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - Key::from_slice(&self.key) - } +#![cfg(feature = "v4_local")] +use std::marker::PhantomData; +use std::ops::Deref; +use blake2::digest::consts::U56; +use blake2::{Blake2bMac, digest::Update}; +use blake2::digest::FixedOutput; +use digest::KeyInit; +use chacha20::{XNonce, Key}; +use crate::core::common::EncryptionKey; +use crate::core::{Local, PasetoSymmetricKey, V4}; + +impl EncryptionKey { + pub(crate) fn from(message: &crate::core::Key<53>, key: &PasetoSymmetricKey) -> Self { + let mut context = Blake2bMac::::new_from_slice(key.as_ref()).unwrap(); + context.update(message.as_ref()); + let binding = context.finalize_fixed(); + let context = binding.to_vec(); + let key = context[..32].to_vec(); + let nonce = context[32..56].to_vec(); + + assert_eq!(key.len(), 32); + assert_eq!(nonce.len(), 24); + Self { + key, + nonce, + version: PhantomData, + purpose: PhantomData, + } + + } + pub(crate) fn counter_nonce(&self) -> &XNonce { + XNonce::from_slice(&self.nonce) + } +} + +impl AsRef for EncryptionKey { + fn as_ref(&self) -> &Key { + Key::from_slice(&self.key) + } +} + +impl Deref for EncryptionKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + Key::from_slice(&self.key) + } } \ No newline at end of file diff --git a/src/core/common/encryption_key_impl/v_local.rs b/src/core/common/encryption_key_impl/v_local.rs index 7f3cb78..203d120 100644 --- a/src/core/common/encryption_key_impl/v_local.rs +++ b/src/core/common/encryption_key_impl/v_local.rs @@ -1,23 +1,23 @@ -use std::ops::Deref; -use crate::core::common::EncryptionKey; -use crate::core::{Local, V1orV3}; - -impl AsRef> for EncryptionKey - where - Version: V1orV3, -{ - fn as_ref(&self) -> &Vec { - &self.key - } -} - -impl Deref for EncryptionKey - where - Version: V1orV3, -{ - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.key - } +use std::ops::Deref; +use crate::core::common::EncryptionKey; +use crate::core::{Local, V1orV3}; + +impl AsRef> for EncryptionKey + where + Version: V1orV3, +{ + fn as_ref(&self) -> &Vec { + &self.key + } +} + +impl Deref for EncryptionKey + where + Version: V1orV3, +{ + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.key + } } \ No newline at end of file diff --git a/src/core/common/encryption_key_separator.rs b/src/core/common/encryption_key_separator.rs index f2ef256..ccae5d8 100644 --- a/src/core/common/encryption_key_separator.rs +++ b/src/core/common/encryption_key_separator.rs @@ -1,44 +1,44 @@ -use std::fmt; -use std::fmt::Display; -use std::ops::{Add, Deref}; -use crate::core::{Key, Local, PasetoNonce}; - -#[derive(Debug)] -pub (crate) struct EncryptionKeySeparator(&'static str); - -impl Display for EncryptionKeySeparator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.0) - } -} - -impl Default for EncryptionKeySeparator { - fn default() -> Self { - Self("paseto-encryption-key") - } -} - -impl Deref for EncryptionKeySeparator { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0.as_bytes() - } -} - -impl AsRef for EncryptionKeySeparator { - fn as_ref(&self) -> &str { - self.0 - } -} - -impl<'a, Version> Add<&PasetoNonce<'a, Version, Local>> for EncryptionKeySeparator { - type Output = Key<53>; - - fn add(self, rhs: &PasetoNonce) -> Self::Output { - let mut output = [0u8; 53]; - output[..21].copy_from_slice(self.0.as_bytes()); - output[21..].copy_from_slice(rhs.as_ref()); - Key::<53>::from(output) - } -} +use std::fmt; +use std::fmt::Display; +use std::ops::{Add, Deref}; +use crate::core::{Key, Local, PasetoNonce}; + +#[derive(Debug)] +pub (crate) struct EncryptionKeySeparator(&'static str); + +impl Display for EncryptionKeySeparator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl Default for EncryptionKeySeparator { + fn default() -> Self { + Self("paseto-encryption-key") + } +} + +impl Deref for EncryptionKeySeparator { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_bytes() + } +} + +impl AsRef for EncryptionKeySeparator { + fn as_ref(&self) -> &str { + self.0 + } +} + +impl<'a, Version> Add<&PasetoNonce<'a, Version, Local>> for EncryptionKeySeparator { + type Output = Key<53>; + + fn add(self, rhs: &PasetoNonce) -> Self::Output { + let mut output = [0u8; 53]; + output[..21].copy_from_slice(self.0.as_bytes()); + output[21..].copy_from_slice(rhs.as_ref()); + Key::<53>::from(output) + } +} diff --git a/src/core/common/encryption_nonce.rs b/src/core/common/encryption_nonce.rs index 46ff1ac..5bc0a70 100644 --- a/src/core/common/encryption_nonce.rs +++ b/src/core/common/encryption_nonce.rs @@ -1,12 +1,12 @@ -#[cfg(feature = "chacha20poly1305")] -use chacha20poly1305::XNonce; - -#[cfg(feature = "chacha20poly1305")] -struct EncryptionNonce(XNonce); - -#[cfg(feature = "chacha20poly1305")] -impl AsRef for EncryptionNonce { - fn as_ref(&self) -> &XNonce { - &self.0 - } -} +#[cfg(feature = "chacha20poly1305")] +use chacha20poly1305::XNonce; + +#[cfg(feature = "chacha20poly1305")] +struct EncryptionNonce(XNonce); + +#[cfg(feature = "chacha20poly1305")] +impl AsRef for EncryptionNonce { + fn as_ref(&self) -> &XNonce { + &self.0 + } +} diff --git a/src/core/common/hkdf_key.rs b/src/core/common/hkdf_key.rs index 0eed3da..ad91e0d 100644 --- a/src/core/common/hkdf_key.rs +++ b/src/core/common/hkdf_key.rs @@ -1,21 +1,21 @@ -use ring::hkdf; -use crate::core::PasetoError; - -#[derive(Debug, PartialEq)] -pub(crate) struct HkdfKey(pub T); - -impl hkdf::KeyType for HkdfKey { - fn len(&self) -> usize { - self.0 - } -} - -impl TryFrom>> for HkdfKey> { - type Error = PasetoError; - fn try_from(okm: hkdf::Okm>) -> Result { - let mut r = vec![0u8; okm.len().0]; - okm.fill(&mut r)?; - Ok(Self(r)) - } -} - +use ring::hkdf; +use crate::core::PasetoError; + +#[derive(Debug, PartialEq)] +pub(crate) struct HkdfKey(pub T); + +impl hkdf::KeyType for HkdfKey { + fn len(&self) -> usize { + self.0 + } +} + +impl TryFrom>> for HkdfKey> { + type Error = PasetoError; + fn try_from(okm: hkdf::Okm>) -> Result { + let mut r = vec![0u8; okm.len().0]; + okm.fill(&mut r)?; + Ok(Self(r)) + } +} + diff --git a/src/core/common/mod.rs b/src/core/common/mod.rs index 0037049..9e44189 100644 --- a/src/core/common/mod.rs +++ b/src/core/common/mod.rs @@ -1,26 +1,26 @@ -#![allow(unused)] -pub(crate) mod cipher_text; -mod encryption_key; -mod encryption_nonce; -mod tag; -mod raw_payload; -mod authentication_key; -mod authentication_key_separator; -mod encryption_key_separator; -mod pre_authentication_encoding; -mod hkdf_key; -mod encryption_key_impl; -mod tag_impl; -mod raw_payload_impl; -mod authentication_key_impl; -mod cipher_text_impl; - -pub(crate) use encryption_key::EncryptionKey; -pub(crate) use raw_payload::RawPayload; -pub(crate) use pre_authentication_encoding::PreAuthenticationEncoding; -pub(crate) use cipher_text::CipherText; -pub(crate) use authentication_key::AuthenticationKey; -pub(crate) use authentication_key_separator::AuthenticationKeySeparator; -pub(crate) use encryption_key_separator::EncryptionKeySeparator; -pub(crate) use tag::Tag; -pub(crate) use hkdf_key::HkdfKey; +#![allow(unused)] +pub(crate) mod cipher_text; +mod encryption_key; +mod encryption_nonce; +mod tag; +mod raw_payload; +mod authentication_key; +mod authentication_key_separator; +mod encryption_key_separator; +mod pre_authentication_encoding; +mod hkdf_key; +mod encryption_key_impl; +mod tag_impl; +mod raw_payload_impl; +mod authentication_key_impl; +mod cipher_text_impl; + +pub(crate) use encryption_key::EncryptionKey; +pub(crate) use raw_payload::RawPayload; +pub(crate) use pre_authentication_encoding::PreAuthenticationEncoding; +pub(crate) use cipher_text::CipherText; +pub(crate) use authentication_key::AuthenticationKey; +pub(crate) use authentication_key_separator::AuthenticationKeySeparator; +pub(crate) use encryption_key_separator::EncryptionKeySeparator; +pub(crate) use tag::Tag; +pub(crate) use hkdf_key::HkdfKey; diff --git a/src/core/common/pre_authentication_encoding.rs b/src/core/common/pre_authentication_encoding.rs index f38db5a..cef2257 100644 --- a/src/core/common/pre_authentication_encoding.rs +++ b/src/core/common/pre_authentication_encoding.rs @@ -1,49 +1,49 @@ -use std::ops::Deref; - -pub struct PreAuthenticationEncoding(Vec); - -/// Performs Pre-Authentication Encoding (or PAE) as described in the -/// Paseto Specification v2. -/// -impl PreAuthenticationEncoding { - /// * `pieces` - The Pieces to concatenate, and encode together. - /// Refactored from original code found at - /// - pub fn parse<'a>(pieces: &'a [&'a [u8]]) -> Self { - let the_vec = PreAuthenticationEncoding::le64(pieces.len() as u64); - - Self(pieces.iter().fold(the_vec, |mut acc, piece| { - acc.extend(PreAuthenticationEncoding::le64(piece.len() as u64)); - acc.extend(piece.iter()); - acc - })) - } - /// Encodes a u64-bit unsigned integer into a little-endian binary string. - /// - /// * `to_encode` - The u8 to encode. - /// Copied and gently refactored from - pub(crate) fn le64(mut to_encode: u64) -> Vec { - let mut the_vec = Vec::with_capacity(8); - - for _idx in 0..8 { - the_vec.push((to_encode & 255) as u8); - to_encode >>= 8; - } - - the_vec - } -} - -impl Deref for PreAuthenticationEncoding { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0.deref() - } -} - -impl AsRef> for PreAuthenticationEncoding { - fn as_ref(&self) -> &Vec { - &self.0 - } -} +use std::ops::Deref; + +pub struct PreAuthenticationEncoding(Vec); + +/// Performs Pre-Authentication Encoding (or PAE) as described in the +/// Paseto Specification v2. +/// +impl PreAuthenticationEncoding { + /// * `pieces` - The Pieces to concatenate, and encode together. + /// Refactored from original code found at + /// + pub fn parse<'a>(pieces: &'a [&'a [u8]]) -> Self { + let the_vec = PreAuthenticationEncoding::le64(pieces.len() as u64); + + Self(pieces.iter().fold(the_vec, |mut acc, piece| { + acc.extend(PreAuthenticationEncoding::le64(piece.len() as u64)); + acc.extend(piece.iter()); + acc + })) + } + /// Encodes a u64-bit unsigned integer into a little-endian binary string. + /// + /// * `to_encode` - The u8 to encode. + /// Copied and gently refactored from + pub(crate) fn le64(mut to_encode: u64) -> Vec { + let mut the_vec = Vec::with_capacity(8); + + for _idx in 0..8 { + the_vec.push((to_encode & 255) as u8); + to_encode >>= 8; + } + + the_vec + } +} + +impl Deref for PreAuthenticationEncoding { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl AsRef> for PreAuthenticationEncoding { + fn as_ref(&self) -> &Vec { + &self.0 + } +} diff --git a/src/core/common/raw_payload.rs b/src/core/common/raw_payload.rs index 0982795..84b7fd8 100644 --- a/src/core/common/raw_payload.rs +++ b/src/core/common/raw_payload.rs @@ -1,11 +1,11 @@ -use std::marker::PhantomData; - -pub struct RawPayload { - version: PhantomData, - purpose: PhantomData, -} - - - - - +use std::marker::PhantomData; + +pub struct RawPayload { + version: PhantomData, + purpose: PhantomData, +} + + + + + diff --git a/src/core/common/raw_payload_impl/mod.rs b/src/core/common/raw_payload_impl/mod.rs index 617ebfc..1f77a29 100644 --- a/src/core/common/raw_payload_impl/mod.rs +++ b/src/core/common/raw_payload_impl/mod.rs @@ -1,4 +1,4 @@ -mod nist_local; -mod v2_local; -mod v4_local; +mod nist_local; +mod v2_local; +mod v4_local; mod v_public; \ No newline at end of file diff --git a/src/core/common/raw_payload_impl/nist_local.rs b/src/core/common/raw_payload_impl/nist_local.rs index 6369b5b..1ee1a65 100644 --- a/src/core/common/raw_payload_impl/nist_local.rs +++ b/src/core/common/raw_payload_impl/nist_local.rs @@ -1,29 +1,29 @@ -#![cfg(any(feature = "v1_local", feature = "v3_local"))] -use base64::prelude::*; -use crate::core::common::RawPayload; -use crate::core::{Local, PasetoError, PasetoNonce, V1orV3}; - -impl RawPayload - where - Version: V1orV3, -{ - pub(crate) fn from( - nonce: &PasetoNonce, - ciphertext: &impl AsRef>, - tag: &impl AsRef<[u8]>, - ) -> Result { - let tag_len = tag.as_ref().len(); - let concat_len: usize = match (nonce.len() + tag_len).checked_add(ciphertext.as_ref().len()) { - Some(len) => len, - None => return Err(PasetoError::Signature), - }; - - let mut raw_token = vec![0u8; concat_len]; - raw_token[..nonce.as_ref().len()].copy_from_slice(nonce.as_ref()); - raw_token[nonce.as_ref().len()..nonce.as_ref().len() + ciphertext.as_ref().len()] - .copy_from_slice(ciphertext.as_ref()); - raw_token[concat_len - tag_len..].copy_from_slice(tag.as_ref()); - - Ok(BASE64_URL_SAFE_NO_PAD.encode(&raw_token)) - } -} +#![cfg(any(feature = "v1_local", feature = "v3_local"))] +use base64::prelude::*; +use crate::core::common::RawPayload; +use crate::core::{Local, PasetoError, PasetoNonce, V1orV3}; + +impl RawPayload + where + Version: V1orV3, +{ + pub(crate) fn from( + nonce: &PasetoNonce, + ciphertext: &impl AsRef>, + tag: &impl AsRef<[u8]>, + ) -> Result { + let tag_len = tag.as_ref().len(); + let concat_len: usize = match (nonce.len() + tag_len).checked_add(ciphertext.as_ref().len()) { + Some(len) => len, + None => return Err(PasetoError::Signature), + }; + + let mut raw_token = vec![0u8; concat_len]; + raw_token[..nonce.as_ref().len()].copy_from_slice(nonce.as_ref()); + raw_token[nonce.as_ref().len()..nonce.as_ref().len() + ciphertext.as_ref().len()] + .copy_from_slice(ciphertext.as_ref()); + raw_token[concat_len - tag_len..].copy_from_slice(tag.as_ref()); + + Ok(BASE64_URL_SAFE_NO_PAD.encode(&raw_token)) + } +} diff --git a/src/core/common/raw_payload_impl/v2_local.rs b/src/core/common/raw_payload_impl/v2_local.rs index 70a7c4a..0ee60cf 100644 --- a/src/core/common/raw_payload_impl/v2_local.rs +++ b/src/core/common/raw_payload_impl/v2_local.rs @@ -1,14 +1,14 @@ -#![cfg(feature = "v2_local")] -use base64::prelude::*; -use crate::core::common::RawPayload; -use crate::core::{Local, V2}; - -impl RawPayload { - pub(crate) fn from(blake2_hash: &[u8], ciphertext: &[u8]) -> String { - let mut raw_token = Vec::new(); - raw_token.extend_from_slice(blake2_hash); - raw_token.extend_from_slice(ciphertext); - - BASE64_URL_SAFE_NO_PAD.encode(&raw_token) - } -} +#![cfg(feature = "v2_local")] +use base64::prelude::*; +use crate::core::common::RawPayload; +use crate::core::{Local, V2}; + +impl RawPayload { + pub(crate) fn from(blake2_hash: &[u8], ciphertext: &[u8]) -> String { + let mut raw_token = Vec::new(); + raw_token.extend_from_slice(blake2_hash); + raw_token.extend_from_slice(ciphertext); + + BASE64_URL_SAFE_NO_PAD.encode(&raw_token) + } +} diff --git a/src/core/common/raw_payload_impl/v4_local.rs b/src/core/common/raw_payload_impl/v4_local.rs index 7956652..3fe1d8a 100644 --- a/src/core/common/raw_payload_impl/v4_local.rs +++ b/src/core/common/raw_payload_impl/v4_local.rs @@ -1,26 +1,26 @@ -#![cfg(feature = "v4_local")] -use base64::prelude::*; -use crate::core::common::RawPayload; -use crate::core::{Local, PasetoError, PasetoNonce, V4}; - -impl RawPayload { - pub(crate) fn try_from( - nonce: &PasetoNonce, - ciphertext: &impl AsRef>, - tag: &impl AsRef<[u8]>, - ) -> Result { - let tag_len = tag.as_ref().len(); - let concat_len: usize = match (nonce.len() + tag_len).checked_add(ciphertext.as_ref().len()) { - Some(len) => len, - None => return Err(PasetoError::Cryption), - }; - - let mut raw_token = vec![0u8; concat_len]; - raw_token[..nonce.as_ref().len()].copy_from_slice(nonce.as_ref()); - raw_token[nonce.as_ref().len()..nonce.as_ref().len() + ciphertext.as_ref().len()] - .copy_from_slice(ciphertext.as_ref()); - raw_token[concat_len - tag_len..].copy_from_slice(tag.as_ref()); - - Ok(BASE64_URL_SAFE_NO_PAD.encode(&raw_token)) - } -} +#![cfg(feature = "v4_local")] +use base64::prelude::*; +use crate::core::common::RawPayload; +use crate::core::{Local, PasetoError, PasetoNonce, V4}; + +impl RawPayload { + pub(crate) fn try_from( + nonce: &PasetoNonce, + ciphertext: &impl AsRef>, + tag: &impl AsRef<[u8]>, + ) -> Result { + let tag_len = tag.as_ref().len(); + let concat_len: usize = match (nonce.len() + tag_len).checked_add(ciphertext.as_ref().len()) { + Some(len) => len, + None => return Err(PasetoError::Cryption), + }; + + let mut raw_token = vec![0u8; concat_len]; + raw_token[..nonce.as_ref().len()].copy_from_slice(nonce.as_ref()); + raw_token[nonce.as_ref().len()..nonce.as_ref().len() + ciphertext.as_ref().len()] + .copy_from_slice(ciphertext.as_ref()); + raw_token[concat_len - tag_len..].copy_from_slice(tag.as_ref()); + + Ok(BASE64_URL_SAFE_NO_PAD.encode(&raw_token)) + } +} diff --git a/src/core/common/raw_payload_impl/v_public.rs b/src/core/common/raw_payload_impl/v_public.rs index 171e1d5..4f51504 100644 --- a/src/core/common/raw_payload_impl/v_public.rs +++ b/src/core/common/raw_payload_impl/v_public.rs @@ -1,13 +1,13 @@ -#![cfg(any(feature = "v1_public", feature = "v2_public", feature = "v3_public", feature = "v4_public"))] -use base64::prelude::*; -use crate::core::common::RawPayload; -use crate::core::Public; - -impl RawPayload { - pub(crate) fn from(payload: &[u8], signature: &impl AsRef<[u8]>) -> String { - let mut raw_token = Vec::from(payload); - raw_token.extend_from_slice(signature.as_ref()); - - BASE64_URL_SAFE_NO_PAD.encode(&raw_token) - } +#![cfg(any(feature = "v1_public", feature = "v2_public", feature = "v3_public", feature = "v4_public"))] +use base64::prelude::*; +use crate::core::common::RawPayload; +use crate::core::Public; + +impl RawPayload { + pub(crate) fn from(payload: &[u8], signature: &impl AsRef<[u8]>) -> String { + let mut raw_token = Vec::from(payload); + raw_token.extend_from_slice(signature.as_ref()); + + BASE64_URL_SAFE_NO_PAD.encode(&raw_token) + } } \ No newline at end of file diff --git a/src/core/common/tag.rs b/src/core/common/tag.rs index 0086de8..e20d828 100644 --- a/src/core/common/tag.rs +++ b/src/core/common/tag.rs @@ -1,23 +1,23 @@ -use std::marker::PhantomData; -use std::ops::Deref; - -pub(crate) struct Tag { - pub(crate) version: PhantomData, - pub(crate) purpose: PhantomData, - pub(crate) tag: Vec, -} - - - -impl AsRef<[u8]> for Tag { - fn as_ref(&self) -> &[u8] { - &self.tag - } -} - -impl Deref for Tag { - type Target = [u8]; - fn deref(&self) -> &Self::Target { - &self.tag - } +use std::marker::PhantomData; +use std::ops::Deref; + +pub(crate) struct Tag { + pub(crate) version: PhantomData, + pub(crate) purpose: PhantomData, + pub(crate) tag: Vec, +} + + + +impl AsRef<[u8]> for Tag { + fn as_ref(&self) -> &[u8] { + &self.tag + } +} + +impl Deref for Tag { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.tag + } } \ No newline at end of file diff --git a/src/core/common/tag_impl/mod.rs b/src/core/common/tag_impl/mod.rs index a0b8084..d5a47f3 100644 --- a/src/core/common/tag_impl/mod.rs +++ b/src/core/common/tag_impl/mod.rs @@ -1,2 +1,2 @@ -mod v4_local; +mod v4_local; mod nist_local; \ No newline at end of file diff --git a/src/core/common/tag_impl/nist_local.rs b/src/core/common/tag_impl/nist_local.rs index 0933c7d..76a962e 100644 --- a/src/core/common/tag_impl/nist_local.rs +++ b/src/core/common/tag_impl/nist_local.rs @@ -1,25 +1,25 @@ -#![cfg(any(feature = "v1_local", feature = "v3_local"))] -use std::marker::PhantomData; -use hmac::{Hmac, Mac}; -use crate::core::{Local, V1orV3}; -use crate::core::common::PreAuthenticationEncoding; - -impl crate::core::common::tag::Tag - where - Version: V1orV3, -{ - pub(crate) fn from(authentication_key: impl AsRef<[u8]>, pae: &PreAuthenticationEncoding) -> Self { - type HmacSha384 = Hmac; - - let mut mac = HmacSha384::new_from_slice(authentication_key.as_ref()).expect("HMAC can take key of any size"); - mac.update(pae.as_ref()); - - let out = mac.finalize(); - - Self { - tag: out.into_bytes().to_vec(), - version: PhantomData, - purpose: PhantomData, - } - } +#![cfg(any(feature = "v1_local", feature = "v3_local"))] +use std::marker::PhantomData; +use hmac::{Hmac, Mac}; +use crate::core::{Local, V1orV3}; +use crate::core::common::PreAuthenticationEncoding; + +impl crate::core::common::tag::Tag + where + Version: V1orV3, +{ + pub(crate) fn from(authentication_key: impl AsRef<[u8]>, pae: &PreAuthenticationEncoding) -> Self { + type HmacSha384 = Hmac; + + let mut mac = HmacSha384::new_from_slice(authentication_key.as_ref()).expect("HMAC can take key of any size"); + mac.update(pae.as_ref()); + + let out = mac.finalize(); + + Self { + tag: out.into_bytes().to_vec(), + version: PhantomData, + purpose: PhantomData, + } + } } \ No newline at end of file diff --git a/src/core/common/tag_impl/v4_local.rs b/src/core/common/tag_impl/v4_local.rs index a896d56..3254a01 100644 --- a/src/core/common/tag_impl/v4_local.rs +++ b/src/core/common/tag_impl/v4_local.rs @@ -1,24 +1,24 @@ -#![cfg(feature = "v4_local")] -use std::marker::PhantomData; -use std::ops::Deref; -use blake2::digest::consts::U32; -use blake2::{Blake2bMac, digest::Update}; -use blake2::digest::FixedOutput; -use digest::KeyInit; -use crate::core::common::PreAuthenticationEncoding; -use crate::core::{Local, V4}; - -impl crate::core::common::tag::Tag { - pub(crate) fn from(authentication_key: impl AsRef<[u8]>, pae: &PreAuthenticationEncoding) -> Self { - let mut tag_context = Blake2bMac::::new_from_slice(authentication_key.as_ref()).unwrap(); - tag_context.update(pae.as_ref()); - let binding = tag_context.finalize_fixed(); - let tag = binding.to_vec(); - Self { - tag, - version: PhantomData, - purpose: PhantomData, - } - } -} - +#![cfg(feature = "v4_local")] +use std::marker::PhantomData; +use std::ops::Deref; +use blake2::digest::consts::U32; +use blake2::{Blake2bMac, digest::Update}; +use blake2::digest::FixedOutput; +use digest::KeyInit; +use crate::core::common::PreAuthenticationEncoding; +use crate::core::{Local, V4}; + +impl crate::core::common::tag::Tag { + pub(crate) fn from(authentication_key: impl AsRef<[u8]>, pae: &PreAuthenticationEncoding) -> Self { + let mut tag_context = Blake2bMac::::new_from_slice(authentication_key.as_ref()).unwrap(); + tag_context.update(pae.as_ref()); + let binding = tag_context.finalize_fixed(); + let tag = binding.to_vec(); + Self { + tag, + version: PhantomData, + purpose: PhantomData, + } + } +} + diff --git a/src/core/key/paseto_nonce_impl/mod.rs b/src/core/key/paseto_nonce_impl/mod.rs index d95c81a..703bd14 100644 --- a/src/core/key/paseto_nonce_impl/mod.rs +++ b/src/core/key/paseto_nonce_impl/mod.rs @@ -1,5 +1,5 @@ -mod v1_local; -mod v2_local; -mod v3_local; -mod v4_local; +mod v1_local; +mod v2_local; +mod v3_local; +mod v4_local; mod v2_public; \ No newline at end of file diff --git a/src/core/key/paseto_nonce_impl/v1_local.rs b/src/core/key/paseto_nonce_impl/v1_local.rs index 8c36302..ac2aae5 100644 --- a/src/core/key/paseto_nonce_impl/v1_local.rs +++ b/src/core/key/paseto_nonce_impl/v1_local.rs @@ -1,13 +1,13 @@ -#![cfg(feature = "v1_local")] -use std::marker::PhantomData; -use crate::core::{Key, Local, PasetoNonce, V1}; - -impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V1, Local> { - fn from(key: &'a Key<32>) -> Self { - Self { - version: PhantomData, - purpose: PhantomData, - key: key.as_ref(), - } - } -} +#![cfg(feature = "v1_local")] +use std::marker::PhantomData; +use crate::core::{Key, Local, PasetoNonce, V1}; + +impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V1, Local> { + fn from(key: &'a Key<32>) -> Self { + Self { + version: PhantomData, + purpose: PhantomData, + key: key.as_ref(), + } + } +} diff --git a/src/core/key/paseto_nonce_impl/v2_local.rs b/src/core/key/paseto_nonce_impl/v2_local.rs index edb9f8d..ea4a337 100644 --- a/src/core/key/paseto_nonce_impl/v2_local.rs +++ b/src/core/key/paseto_nonce_impl/v2_local.rs @@ -1,41 +1,41 @@ -#![cfg(feature = "v2_local")] -use std::marker::PhantomData; -use crate::core::{Key, Local, PasetoNonce, V2}; - -impl<'a> From<&'a Key<24>> for PasetoNonce<'a, V2, Local> { - fn from(key: &'a Key<24>) -> Self { - Self { - version: PhantomData, - purpose: PhantomData, - key: key.as_ref(), - } - } -} - -impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V2, Local> { - fn from(key: &'a Key<32>) -> Self { - Self { - version: PhantomData, - purpose: PhantomData, - key: key.as_ref(), - } - } -} - -#[cfg(all(test, feature = "v2_local"))] -mod builders { - use std::convert::From; - - use crate::core::*; - use anyhow::Result; - - use super::PasetoNonce; - - #[test] - fn v2_local_key_test() -> Result<()> { - let key = Key::<32>::from(b"wubbalubbadubdubwubbalubbadubdub"); - let paseto_key = PasetoNonce::::from(&key); - assert_eq!(paseto_key.as_ref().len(), key.as_ref().len()); - Ok(()) - } -} +#![cfg(feature = "v2_local")] +use std::marker::PhantomData; +use crate::core::{Key, Local, PasetoNonce, V2}; + +impl<'a> From<&'a Key<24>> for PasetoNonce<'a, V2, Local> { + fn from(key: &'a Key<24>) -> Self { + Self { + version: PhantomData, + purpose: PhantomData, + key: key.as_ref(), + } + } +} + +impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V2, Local> { + fn from(key: &'a Key<32>) -> Self { + Self { + version: PhantomData, + purpose: PhantomData, + key: key.as_ref(), + } + } +} + +#[cfg(all(test, feature = "v2_local"))] +mod builders { + use std::convert::From; + + use crate::core::*; + use anyhow::Result; + + use super::PasetoNonce; + + #[test] + fn v2_local_key_test() -> Result<()> { + let key = Key::<32>::from(b"wubbalubbadubdubwubbalubbadubdub"); + let paseto_key = PasetoNonce::::from(&key); + assert_eq!(paseto_key.as_ref().len(), key.as_ref().len()); + Ok(()) + } +} diff --git a/src/core/key/paseto_nonce_impl/v2_public.rs b/src/core/key/paseto_nonce_impl/v2_public.rs index 646d018..99c101d 100644 --- a/src/core/key/paseto_nonce_impl/v2_public.rs +++ b/src/core/key/paseto_nonce_impl/v2_public.rs @@ -1,17 +1,17 @@ -#![cfg(feature = "v2_public")] -use std::marker::PhantomData; -use crate::core::{PasetoNonce, Public, V2}; - -impl<'a, T> From<&'a T> for PasetoNonce<'a, V2, Public> - where - T: Into<&'a [u8]>, - &'a [u8]: From<&'a T>, -{ - fn from(key: &'a T) -> Self { - Self { - version: PhantomData, - purpose: PhantomData, - key: key.into(), - } - } -} +#![cfg(feature = "v2_public")] +use std::marker::PhantomData; +use crate::core::{PasetoNonce, Public, V2}; + +impl<'a, T> From<&'a T> for PasetoNonce<'a, V2, Public> + where + T: Into<&'a [u8]>, + &'a [u8]: From<&'a T>, +{ + fn from(key: &'a T) -> Self { + Self { + version: PhantomData, + purpose: PhantomData, + key: key.into(), + } + } +} diff --git a/src/core/key/paseto_nonce_impl/v3_local.rs b/src/core/key/paseto_nonce_impl/v3_local.rs index 602f09a..ae91d7c 100644 --- a/src/core/key/paseto_nonce_impl/v3_local.rs +++ b/src/core/key/paseto_nonce_impl/v3_local.rs @@ -1,13 +1,13 @@ -#![cfg(feature = "v3_local")] -use std::marker::PhantomData; -use crate::core::{Key, Local, PasetoNonce, V3}; - -impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V3, Local> { - fn from(key: &'a Key<32>) -> Self { - Self { - version: PhantomData, - purpose: PhantomData, - key: key.as_ref(), - } - } -} +#![cfg(feature = "v3_local")] +use std::marker::PhantomData; +use crate::core::{Key, Local, PasetoNonce, V3}; + +impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V3, Local> { + fn from(key: &'a Key<32>) -> Self { + Self { + version: PhantomData, + purpose: PhantomData, + key: key.as_ref(), + } + } +} diff --git a/src/core/key/paseto_nonce_impl/v4_local.rs b/src/core/key/paseto_nonce_impl/v4_local.rs index 3e0597b..547d677 100644 --- a/src/core/key/paseto_nonce_impl/v4_local.rs +++ b/src/core/key/paseto_nonce_impl/v4_local.rs @@ -1,13 +1,13 @@ -#![cfg(feature = "v4_local")] -use std::marker::PhantomData; -use crate::core::{Key, Local, PasetoNonce, V4}; - -impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V4, Local> { - fn from(key: &'a Key<32>) -> Self { - Self { - version: PhantomData, - purpose: PhantomData, - key: key.as_ref(), - } - } +#![cfg(feature = "v4_local")] +use std::marker::PhantomData; +use crate::core::{Key, Local, PasetoNonce, V4}; + +impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V4, Local> { + fn from(key: &'a Key<32>) -> Self { + Self { + version: PhantomData, + purpose: PhantomData, + key: key.as_ref(), + } + } } \ No newline at end of file diff --git a/src/core/paseto_impl/mod.rs b/src/core/paseto_impl/mod.rs index 35e7a26..7db7af5 100644 --- a/src/core/paseto_impl/mod.rs +++ b/src/core/paseto_impl/mod.rs @@ -1,8 +1,8 @@ -mod v1_public; -mod v2_public; -mod v1_local; -mod v2_local; -mod v3_local; -mod v4_local; -mod v4_public; +mod v1_public; +mod v2_public; +mod v1_local; +mod v2_local; +mod v3_local; +mod v4_local; +mod v4_public; mod v3_public; \ No newline at end of file diff --git a/src/core/paseto_impl/v1_local.rs b/src/core/paseto_impl/v1_local.rs index 1202b3e..9b87955 100644 --- a/src/core/paseto_impl/v1_local.rs +++ b/src/core/paseto_impl/v1_local.rs @@ -1,119 +1,119 @@ -#![cfg(feature = "v1_local")] -use std::str; -use hmac::{Hmac, Mac}; -use crate::core::{Footer, Header, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; -use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; -use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; -use sha2::Sha384; - -impl<'a> Paseto<'a, V1, Local> { - /// Attempts to decrypt a PASETO token - /// ``` - /// # use serde_json::json; - /// # use rusty_paseto::core::*; - /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); - /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; - /// # let nonce = PasetoNonce::::from(&nonce); - /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); - /// # let payload = payload.as_str(); - /// # let payload = Payload::from(payload); - /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; - /// // decrypt a public v1 token - /// let json = Paseto::::try_decrypt(&token, &key, None)?; - /// # assert_eq!(payload, json); - /// # Ok::<(),anyhow::Error>(()) - /// ``` - pub fn try_decrypt( - token: &'a str, - key: &PasetoSymmetricKey, - footer: (impl Into>> + Copy), - ) -> Result { - let decoded_payload = Self::parse_raw_token(token, footer, &V1::default(), &Local::default())?; - let nonce = Key::from(&decoded_payload[..32]); - let nonce = PasetoNonce::::from(&nonce); - - let aks: &[u8] = &AuthenticationKeySeparator::default(); - let authentication_key = AuthenticationKey::::try_from(&Key::from(aks), key, &nonce)?; - let eks: &[u8] = &EncryptionKeySeparator::default(); - let encryption_key = EncryptionKey::::try_from(&Key::from(eks), key, &nonce)?; - - let ciphertext = &decoded_payload[32..(decoded_payload.len() - 48)]; - - //pack preauth - let pae = PreAuthenticationEncoding::parse(&[ - &Header::::default(), - nonce.as_ref(), - ciphertext, - &footer.into().unwrap_or_default(), - ]); - - //generate tags - let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; - let tag2 = &Tag::::from(authentication_key, &pae); - //compare tags - ConstantTimeEquals(tag, tag2)?; - - //decrypt payload - let ciphertext = CipherText::::from(ciphertext, &encryption_key); - - let decoded_str = str::from_utf8(&ciphertext)?; - - //return decrypted payload - Ok(decoded_str.to_owned()) - } - - /// Attempts to encrypt a PASETO token - /// ``` - /// # use serde_json::json; - /// # use rusty_paseto::core::*; - /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); - /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; - /// # let nonce = PasetoNonce::::from(&nonce); - /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); - /// # let payload = payload.as_str(); - /// # let payload = Payload::from(payload); - /// // encrypt a public v1 token - /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; - /// # let json = Paseto::::try_decrypt(&token, &key, None)?; - /// # assert_eq!(payload, json); - /// # Ok::<(),anyhow::Error>(()) - /// ``` - pub fn try_encrypt( - &mut self, - key: &PasetoSymmetricKey, - nonce: &PasetoNonce, - ) -> Result { - //setup - let footer = self.footer.unwrap_or_default(); - - //calculate nonce - type HmacSha384 = Hmac; - let mut mac = HmacSha384::new_from_slice(nonce.as_ref()).expect("HMAC can take key of any size"); - - mac.update(&self.payload); - let out = mac.finalize(); - let nonce = Key::from(&out.into_bytes()[..32]); - let nonce = PasetoNonce::::from(&nonce); - - //split key - let aks: &[u8] = &AuthenticationKeySeparator::default(); - let authentication_key = AuthenticationKey::::try_from(&Key::from(aks), key, &nonce)?; - let eks: &[u8] = &EncryptionKeySeparator::default(); - let encryption_key = EncryptionKey::::try_from(&Key::from(eks), key, &nonce)?; - - //encrypt payload - let ciphertext = CipherText::::from(&self.payload, &encryption_key); - - //pack preauth - let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer]); - - // //generate tag - let tag = Tag::::from(authentication_key, &pae); - - // //generate appended and base64 encoded payload - let raw_payload = RawPayload::::from(&nonce, &ciphertext, &tag)?; - - //format as paseto with header and optional footer - Ok(self.format_token(&raw_payload)) - } -} +#![cfg(feature = "v1_local")] +use std::str; +use hmac::{Hmac, Mac}; +use crate::core::{Footer, Header, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; +use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; +use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; +use sha2::Sha384; + +impl<'a> Paseto<'a, V1, Local> { + /// Attempts to decrypt a PASETO token + /// ``` + /// # use serde_json::json; + /// # use rusty_paseto::core::*; + /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); + /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; + /// # let nonce = PasetoNonce::::from(&nonce); + /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); + /// # let payload = payload.as_str(); + /// # let payload = Payload::from(payload); + /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; + /// // decrypt a public v1 token + /// let json = Paseto::::try_decrypt(&token, &key, None)?; + /// # assert_eq!(payload, json); + /// # Ok::<(),anyhow::Error>(()) + /// ``` + pub fn try_decrypt( + token: &'a str, + key: &PasetoSymmetricKey, + footer: (impl Into>> + Copy), + ) -> Result { + let decoded_payload = Self::parse_raw_token(token, footer, &V1::default(), &Local::default())?; + let nonce = Key::from(&decoded_payload[..32]); + let nonce = PasetoNonce::::from(&nonce); + + let aks: &[u8] = &AuthenticationKeySeparator::default(); + let authentication_key = AuthenticationKey::::try_from(&Key::from(aks), key, &nonce)?; + let eks: &[u8] = &EncryptionKeySeparator::default(); + let encryption_key = EncryptionKey::::try_from(&Key::from(eks), key, &nonce)?; + + let ciphertext = &decoded_payload[32..(decoded_payload.len() - 48)]; + + //pack preauth + let pae = PreAuthenticationEncoding::parse(&[ + &Header::::default(), + nonce.as_ref(), + ciphertext, + &footer.into().unwrap_or_default(), + ]); + + //generate tags + let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; + let tag2 = &Tag::::from(authentication_key, &pae); + //compare tags + ConstantTimeEquals(tag, tag2)?; + + //decrypt payload + let ciphertext = CipherText::::from(ciphertext, &encryption_key); + + let decoded_str = str::from_utf8(&ciphertext)?; + + //return decrypted payload + Ok(decoded_str.to_owned()) + } + + /// Attempts to encrypt a PASETO token + /// ``` + /// # use serde_json::json; + /// # use rusty_paseto::core::*; + /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); + /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; + /// # let nonce = PasetoNonce::::from(&nonce); + /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); + /// # let payload = payload.as_str(); + /// # let payload = Payload::from(payload); + /// // encrypt a public v1 token + /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; + /// # let json = Paseto::::try_decrypt(&token, &key, None)?; + /// # assert_eq!(payload, json); + /// # Ok::<(),anyhow::Error>(()) + /// ``` + pub fn try_encrypt( + &mut self, + key: &PasetoSymmetricKey, + nonce: &PasetoNonce, + ) -> Result { + //setup + let footer = self.footer.unwrap_or_default(); + + //calculate nonce + type HmacSha384 = Hmac; + let mut mac = HmacSha384::new_from_slice(nonce.as_ref()).expect("HMAC can take key of any size"); + + mac.update(&self.payload); + let out = mac.finalize(); + let nonce = Key::from(&out.into_bytes()[..32]); + let nonce = PasetoNonce::::from(&nonce); + + //split key + let aks: &[u8] = &AuthenticationKeySeparator::default(); + let authentication_key = AuthenticationKey::::try_from(&Key::from(aks), key, &nonce)?; + let eks: &[u8] = &EncryptionKeySeparator::default(); + let encryption_key = EncryptionKey::::try_from(&Key::from(eks), key, &nonce)?; + + //encrypt payload + let ciphertext = CipherText::::from(&self.payload, &encryption_key); + + //pack preauth + let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer]); + + // //generate tag + let tag = Tag::::from(authentication_key, &pae); + + // //generate appended and base64 encoded payload + let raw_payload = RawPayload::::from(&nonce, &ciphertext, &tag)?; + + //format as paseto with header and optional footer + Ok(self.format_token(&raw_payload)) + } +} diff --git a/src/core/paseto_impl/v1_public.rs b/src/core/paseto_impl/v1_public.rs index 40f4416..01f5275 100644 --- a/src/core/paseto_impl/v1_public.rs +++ b/src/core/paseto_impl/v1_public.rs @@ -1,44 +1,44 @@ -#![cfg(feature = "v1_public")] -use ring::rand::SystemRandom; -use ring::signature::{RSA_PSS_SHA384, RsaKeyPair}; -use crate::core::{Footer, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V1}; -use crate::core::common::{CipherText, PreAuthenticationEncoding, RawPayload}; - -impl<'a> Paseto<'a, V1, Public> { - /// Verifies a signed V1 Public Paseto - pub fn try_verify( - signature: &'a str, - public_key: &PasetoAsymmetricPublicKey, - footer: (impl Into>> + Copy), - ) -> Result { - let decoded_payload = Self::parse_raw_token(signature, footer, &V1::default(), &Public::default())?; - - let ciphertext = - CipherText::::try_verify(&decoded_payload, public_key, &footer.into().unwrap_or_default())? - .ciphertext; - - Ok(String::from_utf8(ciphertext)?) - } - - /// Attempts to sign a V1 Public Paseto - /// Fails with a PasetoError if the token is malformed or the private key isn't in a valid pkcs#8 - /// format - pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { - let footer = self.footer.unwrap_or_default(); - - let key_pair = RsaKeyPair::from_pkcs8(key.as_ref())?; - - let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer]); - let random = SystemRandom::new(); - - let mut signature = [0; 256]; - - key_pair - .sign(&RSA_PSS_SHA384, &random, &pae, &mut signature) - .map_err(|_| PasetoError::InvalidSignature)?; - - let raw_payload = RawPayload::::from(&self.payload, &signature); - - Ok(self.format_token(&raw_payload)) - } -} +#![cfg(feature = "v1_public")] +use ring::rand::SystemRandom; +use ring::signature::{RSA_PSS_SHA384, RsaKeyPair}; +use crate::core::{Footer, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V1}; +use crate::core::common::{CipherText, PreAuthenticationEncoding, RawPayload}; + +impl<'a> Paseto<'a, V1, Public> { + /// Verifies a signed V1 Public Paseto + pub fn try_verify( + signature: &'a str, + public_key: &PasetoAsymmetricPublicKey, + footer: (impl Into>> + Copy), + ) -> Result { + let decoded_payload = Self::parse_raw_token(signature, footer, &V1::default(), &Public::default())?; + + let ciphertext = + CipherText::::try_verify(&decoded_payload, public_key, &footer.into().unwrap_or_default())? + .ciphertext; + + Ok(String::from_utf8(ciphertext)?) + } + + /// Attempts to sign a V1 Public Paseto + /// Fails with a PasetoError if the token is malformed or the private key isn't in a valid pkcs#8 + /// format + pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { + let footer = self.footer.unwrap_or_default(); + + let key_pair = RsaKeyPair::from_pkcs8(key.as_ref())?; + + let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer]); + let random = SystemRandom::new(); + + let mut signature = [0; 256]; + + key_pair + .sign(&RSA_PSS_SHA384, &random, &pae, &mut signature) + .map_err(|_| PasetoError::InvalidSignature)?; + + let raw_payload = RawPayload::::from(&self.payload, &signature); + + Ok(self.format_token(&raw_payload)) + } +} diff --git a/src/core/paseto_impl/v2_local.rs b/src/core/paseto_impl/v2_local.rs index 52b8167..79074c6 100644 --- a/src/core/paseto_impl/v2_local.rs +++ b/src/core/paseto_impl/v2_local.rs @@ -1,100 +1,100 @@ -#![cfg(feature = "v2_local")] -use blake2::Blake2bMac; -use blake2::digest::{FixedOutput, Mac}; -use chacha20poly1305::XNonce; -use crate::core::{Footer, Header, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V2}; -use crate::core::common::{CipherText, PreAuthenticationEncoding, RawPayload}; -use std::str; -impl<'a> Paseto<'a, V2, Local> { - /// Attempts to decrypt a PASETO token - /// ``` - /// # use serde_json::json; - /// # use rusty_paseto::core::*; - /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); - /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; - /// # let nonce = PasetoNonce::::from(&nonce); - /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); - /// # let payload = payload.as_str(); - /// # let payload = Payload::from(payload); - /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; - /// // decrypt a public v2 token - /// let json = Paseto::::try_decrypt(&token, &key, None)?; - /// # assert_eq!(payload, json); - /// # Ok::<(),anyhow::Error>(()) - /// ``` - pub fn try_decrypt( - token: &'a str, - key: &PasetoSymmetricKey, - footer: (impl Into>> + Copy), - ) -> Result { - //get footer - - let decoded_payload = Self::parse_raw_token(token, footer, &V2::default(), &Local::default())?; - let (nonce, ciphertext) = decoded_payload.split_at(24); - - //pack preauth - let pae = &PreAuthenticationEncoding::parse(&[ - &Header::::default(), - nonce, - &footer.into().unwrap_or_default(), - ]); - - //create the nonce - let nonce = XNonce::from_slice(nonce); - - //encrypt payload - let ciphertext = CipherText::::try_decrypt_from(key, nonce, ciphertext, pae)?; - - //generate appended and base64 encoded payload - let decoded_str = str::from_utf8(&ciphertext)?; - - //return decrypted payload - Ok(decoded_str.to_owned()) - } - - /// Attempts to encrypt a PASETO token - /// ``` - /// # use serde_json::json; - /// # use rusty_paseto::core::*; - /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); - /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; - /// # let nonce = PasetoNonce::::from(&nonce); - /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); - /// # let payload = payload.as_str(); - /// # let payload = Payload::from(payload); - /// // encrypt a public v2 token - /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; - /// # let json = Paseto::::try_decrypt(&token, &key, None)?; - /// # assert_eq!(payload, json); - /// # Ok::<(),anyhow::Error>(()) - /// ``` - pub fn try_encrypt( - &self, - key: &PasetoSymmetricKey, - nonce: &PasetoNonce, - ) -> Result { - //setup - let footer = self.footer.unwrap_or_default(); - - //create the blake2 context to generate the nonce - let mut blake2 = Blake2bMac::new_from_slice(nonce.as_ref())?; - blake2.update(&self.payload); - let mut context = [0u8; 24]; - blake2.finalize_into((&mut context).into()); - - //create the nonce - let nonce = XNonce::from_slice(&context); - - //pack preauth - let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce, &footer]); - - //encrypt payload - let ciphertext = CipherText::::try_from(key, nonce, &self.payload, &pae)?; - - //generate appended and base64 encoded payload - let raw_payload = RawPayload::::from(&context, &ciphertext); - - //format as paseto with header and optional footer - Ok(self.format_token(&raw_payload)) - } -} +#![cfg(feature = "v2_local")] +use blake2::Blake2bMac; +use blake2::digest::{FixedOutput, Mac}; +use chacha20poly1305::XNonce; +use crate::core::{Footer, Header, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V2}; +use crate::core::common::{CipherText, PreAuthenticationEncoding, RawPayload}; +use std::str; +impl<'a> Paseto<'a, V2, Local> { + /// Attempts to decrypt a PASETO token + /// ``` + /// # use serde_json::json; + /// # use rusty_paseto::core::*; + /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); + /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; + /// # let nonce = PasetoNonce::::from(&nonce); + /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); + /// # let payload = payload.as_str(); + /// # let payload = Payload::from(payload); + /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; + /// // decrypt a public v2 token + /// let json = Paseto::::try_decrypt(&token, &key, None)?; + /// # assert_eq!(payload, json); + /// # Ok::<(),anyhow::Error>(()) + /// ``` + pub fn try_decrypt( + token: &'a str, + key: &PasetoSymmetricKey, + footer: (impl Into>> + Copy), + ) -> Result { + //get footer + + let decoded_payload = Self::parse_raw_token(token, footer, &V2::default(), &Local::default())?; + let (nonce, ciphertext) = decoded_payload.split_at(24); + + //pack preauth + let pae = &PreAuthenticationEncoding::parse(&[ + &Header::::default(), + nonce, + &footer.into().unwrap_or_default(), + ]); + + //create the nonce + let nonce = XNonce::from_slice(nonce); + + //encrypt payload + let ciphertext = CipherText::::try_decrypt_from(key, nonce, ciphertext, pae)?; + + //generate appended and base64 encoded payload + let decoded_str = str::from_utf8(&ciphertext)?; + + //return decrypted payload + Ok(decoded_str.to_owned()) + } + + /// Attempts to encrypt a PASETO token + /// ``` + /// # use serde_json::json; + /// # use rusty_paseto::core::*; + /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); + /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; + /// # let nonce = PasetoNonce::::from(&nonce); + /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); + /// # let payload = payload.as_str(); + /// # let payload = Payload::from(payload); + /// // encrypt a public v2 token + /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; + /// # let json = Paseto::::try_decrypt(&token, &key, None)?; + /// # assert_eq!(payload, json); + /// # Ok::<(),anyhow::Error>(()) + /// ``` + pub fn try_encrypt( + &self, + key: &PasetoSymmetricKey, + nonce: &PasetoNonce, + ) -> Result { + //setup + let footer = self.footer.unwrap_or_default(); + + //create the blake2 context to generate the nonce + let mut blake2 = Blake2bMac::new_from_slice(nonce.as_ref())?; + blake2.update(&self.payload); + let mut context = [0u8; 24]; + blake2.finalize_into((&mut context).into()); + + //create the nonce + let nonce = XNonce::from_slice(&context); + + //pack preauth + let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce, &footer]); + + //encrypt payload + let ciphertext = CipherText::::try_from(key, nonce, &self.payload, &pae)?; + + //generate appended and base64 encoded payload + let raw_payload = RawPayload::::from(&context, &ciphertext); + + //format as paseto with header and optional footer + Ok(self.format_token(&raw_payload)) + } +} diff --git a/src/core/paseto_impl/v2_public.rs b/src/core/paseto_impl/v2_public.rs index 2a2f6ab..c197ff9 100644 --- a/src/core/paseto_impl/v2_public.rs +++ b/src/core/paseto_impl/v2_public.rs @@ -1,54 +1,54 @@ -#![cfg(feature = "v2_public")] -use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; -use crate::core::{Footer, Header, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V2}; -use crate::core::common::{PreAuthenticationEncoding, RawPayload}; - -impl<'a> Paseto<'a, V2, Public> { - /// Attempts to verify a signed V2 Public Paseto - /// Fails with a PasetoError if the token is malformed or the token cannot be verified with the - /// passed public key - pub fn try_verify( - signature: &'a str, - public_key: &PasetoAsymmetricPublicKey, - footer: (impl Into>> + Copy), - ) -> Result { - let decoded_payload = Self::parse_raw_token(signature, footer, &V2::default(), &Public::default())?; - - let verifying_key: VerifyingKey = VerifyingKey::from_bytes(<&[u8; 32]>::try_from(public_key.as_ref())?)?; - - // let public_key = PublicKey::from_bytes(public_key.as_ref()).map_err(|_| PasetoError::InvalidSignature)?; - let msg = decoded_payload[..(decoded_payload.len() - ed25519_dalek::SIGNATURE_LENGTH)].as_ref(); - let sig = decoded_payload[msg.len()..msg.len() + ed25519_dalek::SIGNATURE_LENGTH].as_ref(); - - let signature = Signature::try_from(sig).map_err(|_| PasetoError::InvalidSignature)?; - let pae = PreAuthenticationEncoding::parse(&[ - &Header::::default(), - msg, - &footer.into().unwrap_or_default(), - ]); - - verifying_key.verify(&pae, &signature)?; - // public_key - // .verify(&pae, &signature) - // .map_err(|_| PasetoError::InvalidSignature)?; - - Ok(String::from_utf8(Vec::from(msg))?) - } - - /// Attempts to sign a V2 Public Paseto - /// Fails with a PasetoError if the token is malformed or the private key can't be parsed - pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { - let footer = self.footer.unwrap_or_default(); - - // let keypair = Keypair::from_bytes(key.as_ref())?; - let signing_key = SigningKey::from_keypair_bytes(<&[u8; 64]>::try_from(key.as_ref())?)?; - - let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer]); - - // let signature = keypair.sign(&pae); - let signature = signing_key.sign(&pae); - let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); - - Ok(self.format_token(&raw_payload)) - } -} +#![cfg(feature = "v2_public")] +use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; +use crate::core::{Footer, Header, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V2}; +use crate::core::common::{PreAuthenticationEncoding, RawPayload}; + +impl<'a> Paseto<'a, V2, Public> { + /// Attempts to verify a signed V2 Public Paseto + /// Fails with a PasetoError if the token is malformed or the token cannot be verified with the + /// passed public key + pub fn try_verify( + signature: &'a str, + public_key: &PasetoAsymmetricPublicKey, + footer: (impl Into>> + Copy), + ) -> Result { + let decoded_payload = Self::parse_raw_token(signature, footer, &V2::default(), &Public::default())?; + + let verifying_key: VerifyingKey = VerifyingKey::from_bytes(<&[u8; 32]>::try_from(public_key.as_ref())?)?; + + // let public_key = PublicKey::from_bytes(public_key.as_ref()).map_err(|_| PasetoError::InvalidSignature)?; + let msg = decoded_payload[..(decoded_payload.len() - ed25519_dalek::SIGNATURE_LENGTH)].as_ref(); + let sig = decoded_payload[msg.len()..msg.len() + ed25519_dalek::SIGNATURE_LENGTH].as_ref(); + + let signature = Signature::try_from(sig).map_err(|_| PasetoError::InvalidSignature)?; + let pae = PreAuthenticationEncoding::parse(&[ + &Header::::default(), + msg, + &footer.into().unwrap_or_default(), + ]); + + verifying_key.verify(&pae, &signature)?; + // public_key + // .verify(&pae, &signature) + // .map_err(|_| PasetoError::InvalidSignature)?; + + Ok(String::from_utf8(Vec::from(msg))?) + } + + /// Attempts to sign a V2 Public Paseto + /// Fails with a PasetoError if the token is malformed or the private key can't be parsed + pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { + let footer = self.footer.unwrap_or_default(); + + // let keypair = Keypair::from_bytes(key.as_ref())?; + let signing_key = SigningKey::from_keypair_bytes(<&[u8; 64]>::try_from(key.as_ref())?)?; + + let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer]); + + // let signature = keypair.sign(&pae); + let signature = signing_key.sign(&pae); + let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); + + Ok(self.format_token(&raw_payload)) + } +} diff --git a/src/core/paseto_impl/v3_local.rs b/src/core/paseto_impl/v3_local.rs index 954e4b3..af637fb 100644 --- a/src/core/paseto_impl/v3_local.rs +++ b/src/core/paseto_impl/v3_local.rs @@ -1,115 +1,115 @@ -#![cfg(feature = "v3_local")] - -use std::str; - -use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; - -use crate::core::{Footer, Header, ImplicitAssertion, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V3}; -use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; - -impl<'a> Paseto<'a, V3, Local> { - /// Attempts to decrypt a PASETO token - /// ``` - /// # use serde_json::json; - /// # use rusty_paseto::core::*; - /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); - /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; - /// # let nonce = PasetoNonce::::from(&nonce); - /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); - /// # let payload = payload.as_str(); - /// # let payload = Payload::from(payload); - /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; - /// // decrypt a public v3 token - /// let json = Paseto::::try_decrypt(&token, &key, None, None)?; - /// # assert_eq!(payload, json); - /// # Ok::<(),anyhow::Error>(()) - /// ``` - pub fn try_decrypt( - token: &'a str, - key: &PasetoSymmetricKey, - footer: (impl Into>> + Copy), - implicit_assertion: (impl Into>> + Copy), - ) -> Result { - //get footer - - let decoded_payload = Self::parse_raw_token(token, footer, &V3::default(), &Local::default())?; - let nonce = Key::from(&decoded_payload[..32]); - let nonce = PasetoNonce::::from(&nonce); - - let authentication_key = - AuthenticationKey::::try_from(&(AuthenticationKeySeparator::default() + &nonce), key)?; - let encryption_key = EncryptionKey::::try_from(&(EncryptionKeySeparator::default() + &nonce), key)?; - - let ciphertext = &decoded_payload[32..(decoded_payload.len() - 48)]; - - //pack preauth - let pae = PreAuthenticationEncoding::parse(&[ - &Header::::default(), - nonce.as_ref(), - ciphertext, - &footer.into().unwrap_or_default(), - &implicit_assertion.into().unwrap_or_default(), - ]); - - //generate tags - let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; - let tag2 = &Tag::::from(authentication_key, &pae); - //compare tags - ConstantTimeEquals(tag, tag2)?; - - //decrypt payload - let ciphertext = CipherText::::from(ciphertext, &encryption_key); - - let decoded_str = str::from_utf8(&ciphertext)?; - - //return decrypted payload - Ok(decoded_str.to_owned()) - } - - /// Attempts to encrypt a PASETO token - /// ``` - /// # use serde_json::json; - /// # use rusty_paseto::core::*; - /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); - /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; - /// # let nonce = PasetoNonce::::from(&nonce); - /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); - /// # let payload = payload.as_str(); - /// # let payload = Payload::from(payload); - /// // encrypt a public v3 token - /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; - /// # let json = Paseto::::try_decrypt(&token, &key, None, None)?; - /// # assert_eq!(payload, json); - /// # Ok::<(),anyhow::Error>(()) - /// ``` - pub fn try_encrypt( - &mut self, - key: &PasetoSymmetricKey, - nonce: &PasetoNonce, - ) -> Result { - //setup - let footer = self.footer.unwrap_or_default(); - let implicit_assertion = self.implicit_assertion.unwrap_or_default(); - - //split key - let authentication_key = - AuthenticationKey::::try_from(&(AuthenticationKeySeparator::default() + nonce), key)?; - let encryption_key = EncryptionKey::::try_from(&(EncryptionKeySeparator::default() + nonce), key)?; - - //encrypt payload - let ciphertext = CipherText::::from(&self.payload, &encryption_key); - - //pack preauth - let pae = - PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer, &implicit_assertion]); - - // //generate tag - let tag = Tag::::from(authentication_key, &pae); - - // //generate appended and base64 encoded payload - let raw_payload = RawPayload::::from(nonce, &ciphertext, &tag)?; - - //format as paseto with header and optional footer - Ok(self.format_token(&raw_payload)) - } -} +#![cfg(feature = "v3_local")] + +use std::str; + +use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; + +use crate::core::{Footer, Header, ImplicitAssertion, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V3}; +use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; + +impl<'a> Paseto<'a, V3, Local> { + /// Attempts to decrypt a PASETO token + /// ``` + /// # use serde_json::json; + /// # use rusty_paseto::core::*; + /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); + /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; + /// # let nonce = PasetoNonce::::from(&nonce); + /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); + /// # let payload = payload.as_str(); + /// # let payload = Payload::from(payload); + /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; + /// // decrypt a public v3 token + /// let json = Paseto::::try_decrypt(&token, &key, None, None)?; + /// # assert_eq!(payload, json); + /// # Ok::<(),anyhow::Error>(()) + /// ``` + pub fn try_decrypt( + token: &'a str, + key: &PasetoSymmetricKey, + footer: (impl Into>> + Copy), + implicit_assertion: (impl Into>> + Copy), + ) -> Result { + //get footer + + let decoded_payload = Self::parse_raw_token(token, footer, &V3::default(), &Local::default())?; + let nonce = Key::from(&decoded_payload[..32]); + let nonce = PasetoNonce::::from(&nonce); + + let authentication_key = + AuthenticationKey::::try_from(&(AuthenticationKeySeparator::default() + &nonce), key)?; + let encryption_key = EncryptionKey::::try_from(&(EncryptionKeySeparator::default() + &nonce), key)?; + + let ciphertext = &decoded_payload[32..(decoded_payload.len() - 48)]; + + //pack preauth + let pae = PreAuthenticationEncoding::parse(&[ + &Header::::default(), + nonce.as_ref(), + ciphertext, + &footer.into().unwrap_or_default(), + &implicit_assertion.into().unwrap_or_default(), + ]); + + //generate tags + let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; + let tag2 = &Tag::::from(authentication_key, &pae); + //compare tags + ConstantTimeEquals(tag, tag2)?; + + //decrypt payload + let ciphertext = CipherText::::from(ciphertext, &encryption_key); + + let decoded_str = str::from_utf8(&ciphertext)?; + + //return decrypted payload + Ok(decoded_str.to_owned()) + } + + /// Attempts to encrypt a PASETO token + /// ``` + /// # use serde_json::json; + /// # use rusty_paseto::core::*; + /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); + /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; + /// # let nonce = PasetoNonce::::from(&nonce); + /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); + /// # let payload = payload.as_str(); + /// # let payload = Payload::from(payload); + /// // encrypt a public v3 token + /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; + /// # let json = Paseto::::try_decrypt(&token, &key, None, None)?; + /// # assert_eq!(payload, json); + /// # Ok::<(),anyhow::Error>(()) + /// ``` + pub fn try_encrypt( + &mut self, + key: &PasetoSymmetricKey, + nonce: &PasetoNonce, + ) -> Result { + //setup + let footer = self.footer.unwrap_or_default(); + let implicit_assertion = self.implicit_assertion.unwrap_or_default(); + + //split key + let authentication_key = + AuthenticationKey::::try_from(&(AuthenticationKeySeparator::default() + nonce), key)?; + let encryption_key = EncryptionKey::::try_from(&(EncryptionKeySeparator::default() + nonce), key)?; + + //encrypt payload + let ciphertext = CipherText::::from(&self.payload, &encryption_key); + + //pack preauth + let pae = + PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer, &implicit_assertion]); + + // //generate tag + let tag = Tag::::from(authentication_key, &pae); + + // //generate appended and base64 encoded payload + let raw_payload = RawPayload::::from(nonce, &ciphertext, &tag)?; + + //format as paseto with header and optional footer + Ok(self.format_token(&raw_payload)) + } +} diff --git a/src/core/paseto_impl/v3_public.rs b/src/core/paseto_impl/v3_public.rs index 8a9ed78..01f7999 100644 --- a/src/core/paseto_impl/v3_public.rs +++ b/src/core/paseto_impl/v3_public.rs @@ -1,72 +1,72 @@ -#![cfg(feature = "v3_public")] - -use crate::core::{Footer, Header, ImplicitAssertion, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V3}; -use crate::core::common::{PreAuthenticationEncoding, RawPayload}; -use p384::ecdsa::{ - signature::DigestSigner, signature::DigestVerifier, Signature, SigningKey, VerifyingKey, -}; -use p384::elliptic_curve::sec1::ToEncodedPoint; -use p384::PublicKey; -use sha2::Digest; - -impl<'a> Paseto<'a, V3, Public> { - /// Verifies a signed V3 Public Paseto - pub fn try_verify( - signature: &'a str, - public_key: &PasetoAsymmetricPublicKey, - footer: (impl Into>> + Copy), - implicit_assertion: (impl Into>> + Copy), - ) -> Result { - let decoded_payload = Self::parse_raw_token(signature, footer, &V3::default(), &Public::default())?; - - //compress the key - let compressed_public_key = PublicKey::from_sec1_bytes(public_key.as_ref()) - .map_err(|_| PasetoError::InvalidKey)? - .to_encoded_point(true); - - let verifying_key = - VerifyingKey::from_sec1_bytes(compressed_public_key.as_ref()).map_err(|_| PasetoError::InvalidKey)?; - let msg = decoded_payload[..(decoded_payload.len() - 96)].as_ref(); - let sig = decoded_payload[msg.len()..msg.len() + 96].as_ref(); - - let signature = Signature::try_from(sig).map_err(|_| PasetoError::Signature)?; - let m2 = PreAuthenticationEncoding::parse(&[ - compressed_public_key.as_ref(), - &Header::::default(), - msg, - &footer.into().unwrap_or_default(), - &implicit_assertion.into().unwrap_or_default(), - ]); - let mut msg_digest = sha2::Sha384::default(); - msg_digest.update(&*m2); - verifying_key - .verify_digest(msg_digest, &signature) - .map_err(|_| PasetoError::InvalidSignature)?; - - Ok(String::from_utf8(Vec::from(msg))?) - } - - /// Attempts to sign a V3 Public Paseto - /// Fails with a PasetoError if the token is malformed or the private key isn't in a valid format - pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { - let footer = self.footer.unwrap_or_default(); - - let implicit_assertion = self.implicit_assertion.unwrap_or_default(); - let signing_key = SigningKey::from_bytes(key.as_ref().into()).map_err(|_| PasetoError::InvalidKey)?; - let public_key = VerifyingKey::from(&signing_key).to_encoded_point(true); - - let m2 = PreAuthenticationEncoding::parse(&[ - public_key.as_ref(), - &self.header, - &self.payload, - &footer, - &implicit_assertion, - ]); - let mut msg_digest = sha2::Sha384::new(); - msg_digest.update(&*m2); - let signature: Signature = signing_key - .try_sign_digest(msg_digest)?; - let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); - Ok(self.format_token(&raw_payload)) - } -} +#![cfg(feature = "v3_public")] + +use crate::core::{Footer, Header, ImplicitAssertion, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V3}; +use crate::core::common::{PreAuthenticationEncoding, RawPayload}; +use p384::ecdsa::{ + signature::DigestSigner, signature::DigestVerifier, Signature, SigningKey, VerifyingKey, +}; +use p384::elliptic_curve::sec1::ToEncodedPoint; +use p384::PublicKey; +use sha2::Digest; + +impl<'a> Paseto<'a, V3, Public> { + /// Verifies a signed V3 Public Paseto + pub fn try_verify( + signature: &'a str, + public_key: &PasetoAsymmetricPublicKey, + footer: (impl Into>> + Copy), + implicit_assertion: (impl Into>> + Copy), + ) -> Result { + let decoded_payload = Self::parse_raw_token(signature, footer, &V3::default(), &Public::default())?; + + //compress the key + let compressed_public_key = PublicKey::from_sec1_bytes(public_key.as_ref()) + .map_err(|_| PasetoError::InvalidKey)? + .to_encoded_point(true); + + let verifying_key = + VerifyingKey::from_sec1_bytes(compressed_public_key.as_ref()).map_err(|_| PasetoError::InvalidKey)?; + let msg = decoded_payload[..(decoded_payload.len() - 96)].as_ref(); + let sig = decoded_payload[msg.len()..msg.len() + 96].as_ref(); + + let signature = Signature::try_from(sig).map_err(|_| PasetoError::Signature)?; + let m2 = PreAuthenticationEncoding::parse(&[ + compressed_public_key.as_ref(), + &Header::::default(), + msg, + &footer.into().unwrap_or_default(), + &implicit_assertion.into().unwrap_or_default(), + ]); + let mut msg_digest = sha2::Sha384::default(); + msg_digest.update(&*m2); + verifying_key + .verify_digest(msg_digest, &signature) + .map_err(|_| PasetoError::InvalidSignature)?; + + Ok(String::from_utf8(Vec::from(msg))?) + } + + /// Attempts to sign a V3 Public Paseto + /// Fails with a PasetoError if the token is malformed or the private key isn't in a valid format + pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { + let footer = self.footer.unwrap_or_default(); + + let implicit_assertion = self.implicit_assertion.unwrap_or_default(); + let signing_key = SigningKey::from_bytes(key.as_ref().into()).map_err(|_| PasetoError::InvalidKey)?; + let public_key = VerifyingKey::from(&signing_key).to_encoded_point(true); + + let m2 = PreAuthenticationEncoding::parse(&[ + public_key.as_ref(), + &self.header, + &self.payload, + &footer, + &implicit_assertion, + ]); + let mut msg_digest = sha2::Sha384::new(); + msg_digest.update(&*m2); + let signature: Signature = signing_key + .try_sign_digest(msg_digest)?; + let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); + Ok(self.format_token(&raw_payload)) + } +} diff --git a/src/core/paseto_impl/v4_local.rs b/src/core/paseto_impl/v4_local.rs index d9fdd1c..bc6ca75 100644 --- a/src/core/paseto_impl/v4_local.rs +++ b/src/core/paseto_impl/v4_local.rs @@ -1,117 +1,117 @@ -#![cfg(feature = "v4_local")] - -use std::str; - -use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; - -use crate::core::{Footer, Header, ImplicitAssertion, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V4}; -use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; - -impl<'a> Paseto<'a, V4, Local> { - /// Attempts to decrypt a PASETO token - /// ``` - /// # use serde_json::json; - /// # use rusty_paseto::core::*; - /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); - /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; - /// # let nonce = PasetoNonce::::from(&nonce); - /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); - /// # let payload = payload.as_str(); - /// # let payload = Payload::from(payload); - /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; - /// // decrypt a public v4 token - /// let json = Paseto::::try_decrypt(&token, &key, None, None)?; - /// # assert_eq!(payload, json); - /// # Ok::<(),anyhow::Error>(()) - /// ``` - pub fn try_decrypt( - token: &'a str, - key: &PasetoSymmetricKey, - footer: (impl Into>> + Copy), - implicit_assertion: (impl Into>> + Copy), - ) -> Result { - //get footer - - let decoded_payload = Self::parse_raw_token(token, footer, &V4::default(), &Local::default())?; - let nonce = Key::from(&decoded_payload[..32]); - let nonce = PasetoNonce::::from(&nonce); - - let authentication_key = - AuthenticationKey::::from(&(AuthenticationKeySeparator::default() + &nonce), key); - let encryption_key = EncryptionKey::::from(&(EncryptionKeySeparator::default() + &nonce), key); - - let ciphertext = &decoded_payload[32..(decoded_payload.len() - 32)]; - - //pack preauth - let pae = PreAuthenticationEncoding::parse(&[ - &Header::::default(), - nonce.as_ref(), - ciphertext, - &footer.into().unwrap_or_default(), - &implicit_assertion.into().unwrap_or_default(), - ]); - - //generate tags - let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; - let tag2 = &Tag::::from(authentication_key, &pae); - //compare tags - ConstantTimeEquals(tag, tag2)?; - - //decrypt payload - let ciphertext = CipherText::::from(ciphertext, &encryption_key); - - let decoded_str = str::from_utf8(&ciphertext)?; - - //return decrypted payload - Ok(decoded_str.to_owned()) - } - - /// Attempts to encrypt a PASETO token - /// ``` - /// # use serde_json::json; - /// # use rusty_paseto::core::*; - /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); - /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; - /// # // generate a random nonce with - /// # // let nonce = Key::<32>::try_new_random()?; - /// # let nonce = PasetoNonce::::from(&nonce); - /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); - /// # let payload = payload.as_str(); - /// # let payload = Payload::from(payload); - /// //create a public v4 token - /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; - /// # let json = Paseto::::try_decrypt(&token, &key, None, None)?; - /// # assert_eq!(payload, json); - /// # Ok::<(),anyhow::Error>(()) - /// ``` - pub fn try_encrypt( - &mut self, - key: &PasetoSymmetricKey, - nonce: &PasetoNonce, - ) -> Result { - //setup - let footer = self.footer.unwrap_or_default(); - let implicit_assertion = self.implicit_assertion.unwrap_or_default(); - - //split key - let authentication_key = - AuthenticationKey::::from(&(AuthenticationKeySeparator::default() + nonce), key); - let encryption_key = EncryptionKey::::from(&(EncryptionKeySeparator::default() + nonce), key); - - //encrypt payload - let ciphertext = CipherText::::from(&self.payload, &encryption_key); - - //pack preauth - let pae = - PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer, &implicit_assertion]); - - //generate tag - let tag = Tag::::from(authentication_key, &pae); - - //generate appended and base64 encoded payload - let raw_payload = RawPayload::::try_from(nonce, &ciphertext, &tag)?; - - //format as paseto with header and optional footer - Ok(self.format_token(&raw_payload)) - } -} +#![cfg(feature = "v4_local")] + +use std::str; + +use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; + +use crate::core::{Footer, Header, ImplicitAssertion, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V4}; +use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; + +impl<'a> Paseto<'a, V4, Local> { + /// Attempts to decrypt a PASETO token + /// ``` + /// # use serde_json::json; + /// # use rusty_paseto::core::*; + /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); + /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; + /// # let nonce = PasetoNonce::::from(&nonce); + /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); + /// # let payload = payload.as_str(); + /// # let payload = Payload::from(payload); + /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; + /// // decrypt a public v4 token + /// let json = Paseto::::try_decrypt(&token, &key, None, None)?; + /// # assert_eq!(payload, json); + /// # Ok::<(),anyhow::Error>(()) + /// ``` + pub fn try_decrypt( + token: &'a str, + key: &PasetoSymmetricKey, + footer: (impl Into>> + Copy), + implicit_assertion: (impl Into>> + Copy), + ) -> Result { + //get footer + + let decoded_payload = Self::parse_raw_token(token, footer, &V4::default(), &Local::default())?; + let nonce = Key::from(&decoded_payload[..32]); + let nonce = PasetoNonce::::from(&nonce); + + let authentication_key = + AuthenticationKey::::from(&(AuthenticationKeySeparator::default() + &nonce), key); + let encryption_key = EncryptionKey::::from(&(EncryptionKeySeparator::default() + &nonce), key); + + let ciphertext = &decoded_payload[32..(decoded_payload.len() - 32)]; + + //pack preauth + let pae = PreAuthenticationEncoding::parse(&[ + &Header::::default(), + nonce.as_ref(), + ciphertext, + &footer.into().unwrap_or_default(), + &implicit_assertion.into().unwrap_or_default(), + ]); + + //generate tags + let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; + let tag2 = &Tag::::from(authentication_key, &pae); + //compare tags + ConstantTimeEquals(tag, tag2)?; + + //decrypt payload + let ciphertext = CipherText::::from(ciphertext, &encryption_key); + + let decoded_str = str::from_utf8(&ciphertext)?; + + //return decrypted payload + Ok(decoded_str.to_owned()) + } + + /// Attempts to encrypt a PASETO token + /// ``` + /// # use serde_json::json; + /// # use rusty_paseto::core::*; + /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); + /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; + /// # // generate a random nonce with + /// # // let nonce = Key::<32>::try_new_random()?; + /// # let nonce = PasetoNonce::::from(&nonce); + /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); + /// # let payload = payload.as_str(); + /// # let payload = Payload::from(payload); + /// //create a public v4 token + /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; + /// # let json = Paseto::::try_decrypt(&token, &key, None, None)?; + /// # assert_eq!(payload, json); + /// # Ok::<(),anyhow::Error>(()) + /// ``` + pub fn try_encrypt( + &mut self, + key: &PasetoSymmetricKey, + nonce: &PasetoNonce, + ) -> Result { + //setup + let footer = self.footer.unwrap_or_default(); + let implicit_assertion = self.implicit_assertion.unwrap_or_default(); + + //split key + let authentication_key = + AuthenticationKey::::from(&(AuthenticationKeySeparator::default() + nonce), key); + let encryption_key = EncryptionKey::::from(&(EncryptionKeySeparator::default() + nonce), key); + + //encrypt payload + let ciphertext = CipherText::::from(&self.payload, &encryption_key); + + //pack preauth + let pae = + PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer, &implicit_assertion]); + + //generate tag + let tag = Tag::::from(authentication_key, &pae); + + //generate appended and base64 encoded payload + let raw_payload = RawPayload::::try_from(nonce, &ciphertext, &tag)?; + + //format as paseto with header and optional footer + Ok(self.format_token(&raw_payload)) + } +} diff --git a/src/core/paseto_impl/v4_public.rs b/src/core/paseto_impl/v4_public.rs index a86943b..224ecb1 100644 --- a/src/core/paseto_impl/v4_public.rs +++ b/src/core/paseto_impl/v4_public.rs @@ -1,51 +1,51 @@ -#![cfg(feature = "v4_public")] -use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; -use crate::core::{Footer, Header, ImplicitAssertion, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V4}; -use crate::core::common::{PreAuthenticationEncoding, RawPayload}; - -impl<'a> Paseto<'a, V4, Public> { - pub fn try_verify( - signature: &'a str, - public_key: &PasetoAsymmetricPublicKey, - footer: (impl Into>> + Copy), - implicit_assertion: (impl Into>> + Copy), - ) -> Result { - let decoded_payload = Self::parse_raw_token(signature, footer, &V4::default(), &Public::default())?; - - let verifying_key: VerifyingKey = VerifyingKey::from_bytes(<&[u8; 32]>::try_from(public_key.as_ref())?)?; - - let msg = decoded_payload[..(decoded_payload.len() - ed25519_dalek::SIGNATURE_LENGTH)].as_ref(); - let sig = decoded_payload[msg.len()..msg.len() + ed25519_dalek::SIGNATURE_LENGTH].as_ref(); - - let signature = Signature::try_from(sig)?; - let pae = PreAuthenticationEncoding::parse(&[ - &Header::::default(), - msg, - &footer.into().unwrap_or_default(), - &implicit_assertion.into().unwrap_or_default(), - ]); - - verifying_key.verify(&pae, &signature)?; - // public_key.verify(&pae, &signature)?; - - Ok(String::from_utf8(Vec::from(msg))?) - } - - pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { - let footer = self.footer.unwrap_or_default(); - let assertion = self.implicit_assertion.unwrap_or_default(); - // let secret_key : SecretKey = SecretKey::try_from(key.as_ref())?; - let signing_key = SigningKey::from_keypair_bytes(<&[u8; 64]>::try_from(key.as_ref())?)?; - - // let keypair = Keypair::from_bytes(key.as_ref())?; - - let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer, &assertion]); - - - let signature = signing_key.sign(&pae); - - let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); - - Ok(self.format_token(&raw_payload)) - } -} +#![cfg(feature = "v4_public")] +use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; +use crate::core::{Footer, Header, ImplicitAssertion, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V4}; +use crate::core::common::{PreAuthenticationEncoding, RawPayload}; + +impl<'a> Paseto<'a, V4, Public> { + pub fn try_verify( + signature: &'a str, + public_key: &PasetoAsymmetricPublicKey, + footer: (impl Into>> + Copy), + implicit_assertion: (impl Into>> + Copy), + ) -> Result { + let decoded_payload = Self::parse_raw_token(signature, footer, &V4::default(), &Public::default())?; + + let verifying_key: VerifyingKey = VerifyingKey::from_bytes(<&[u8; 32]>::try_from(public_key.as_ref())?)?; + + let msg = decoded_payload[..(decoded_payload.len() - ed25519_dalek::SIGNATURE_LENGTH)].as_ref(); + let sig = decoded_payload[msg.len()..msg.len() + ed25519_dalek::SIGNATURE_LENGTH].as_ref(); + + let signature = Signature::try_from(sig)?; + let pae = PreAuthenticationEncoding::parse(&[ + &Header::::default(), + msg, + &footer.into().unwrap_or_default(), + &implicit_assertion.into().unwrap_or_default(), + ]); + + verifying_key.verify(&pae, &signature)?; + // public_key.verify(&pae, &signature)?; + + Ok(String::from_utf8(Vec::from(msg))?) + } + + pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { + let footer = self.footer.unwrap_or_default(); + let assertion = self.implicit_assertion.unwrap_or_default(); + // let secret_key : SecretKey = SecretKey::try_from(key.as_ref())?; + let signing_key = SigningKey::from_keypair_bytes(<&[u8; 64]>::try_from(key.as_ref())?)?; + + // let keypair = Keypair::from_bytes(key.as_ref())?; + + let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer, &assertion]); + + + let signature = signing_key.sign(&pae); + + let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); + + Ok(self.format_token(&raw_payload)) + } +} diff --git a/tests/build_payload_from_claims_prop_test.rs b/tests/build_payload_from_claims_prop_test.rs index 3b49d5b..75db126 100644 --- a/tests/build_payload_from_claims_prop_test.rs +++ b/tests/build_payload_from_claims_prop_test.rs @@ -1,223 +1,223 @@ -/*! - * Property Tests for build_payload_from_claims Function - * - * This file contains property tests designed to validate the correctness and robustness - * of the `build_payload_from_claims` function. The `build_payload_from_claims` function - * is responsible for constructing JSON payloads from claims, ensuring they are serialized - * and wrapped correctly. - * - * The primary goals of these tests are: - * 1. **Validation**: Ensure the `build_payload_from_claims` function correctly handles different types of claims. - * 2. **Robustness**: Identify and address potential edge cases that may not be covered by unit tests. - * 3. **Consistency**: Verify that the function maintains the expected structure and behavior for all inputs. - * - * ## Test Strategy - * - * The property tests leverage the `proptest` crate to generate a wide range of claims, - * including nested structures. The generated claims are then passed to the `build_payload_from_claims` - * function, and the resulting payloads are compared against the expected outcomes. - * - * ## Key Test Scenarios - * - * - **Null Values**: Ensure null values remain null and are not wrapped unnecessarily. - * - **Empty Objects**: Verify that empty maps are wrapped as empty JSON objects. - * - **Primitive Values**: Confirm that primitive values (e.g., strings, numbers) remain unchanged. - * - **Arrays**: Ensure arrays, including empty arrays, are wrapped correctly and consistently. - * - **Nested Structures**: Validate the recursive wrapping and serialization of nested JSON objects and arrays. - * - * ## Findings - * - * - The `build_payload_from_claims` function correctly handles most input values and passes the associated unit tests. - * - A specific floating-point corner case was identified during the testing process. This case involves minor - * discrepancies in floating-point precision, which is a common issue in many systems. The identified corner - * case has been documented and is not critical for most practical use cases. - * - * ## Conclusion - * - * The property tests demonstrate that the `build_payload_from_claims` function is robust and reliable for most practical - * use cases. While a specific floating-point corner case remains, the function's behavior is consistent with the expected - * outcomes for a wide range of input values. - * - * To run these tests, use the following command: - * - * ```sh - * cargo test -- --ignored - * ``` - * - * This approach ensures comprehensive validation of the `build_payload_from_claims` function, contributing to the overall - * stability and reliability of the system. - */ - -use std::collections::HashMap; - -use proptest::prelude::*; -use erased_serde::Serialize; -use serde_json::{Map, Number, Value}; - -// Define a strategy to generate arbitrary JSON values -fn arb_json() -> impl Strategy { - let leaf = prop_oneof![ - Just(Json::Null), - any::().prop_map(Json::Bool), - any::().prop_map(Json::Number), - "[a-zA-Z0-9_]+".prop_map(Json::String), - ]; - leaf.prop_recursive( - 3, // 3 levels deep - 64, // Shoot for maximum size of 64 nodes - 10, // We put up to 10 items per collection - |inner| prop_oneof![ - prop::collection::vec(inner.clone(), 0..10).prop_map(Json::Array), - prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", inner, 0..10).prop_map(Json::Map), - ], - ) -} - -#[derive(Clone, Debug)] -enum Json { - Null, - Bool(bool), - Number(f64), - String(String), - Array(Vec), - Map(HashMap), -} - -// Convert our custom Json enum to serde_json::Value -impl From for Value { - fn from(json: Json) -> Self { - match json { - Json::Null => Value::Null, - Json::Bool(b) => Value::Bool(b), - Json::Number(n) => Value::Number(Number::from_f64(n).unwrap()), - Json::String(s) => Value::String(s), - Json::Array(arr) => Value::Array(arr.into_iter().map(Value::from).collect()), - Json::Map(map) => Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect()), - } - } -} - - -// Wrap claims in an outer JSON object to ensure proper nesting -fn wrap_claims(claims: HashMap) -> Value { - let wrapped: HashMap = claims - .into_iter() - .map(|(k, v)| (k, wrap_value(v))) - .collect(); - Value::Object(Map::from_iter(wrapped)) -} - -// Recursively wrap values to ensure all values are valid JSON objects -fn wrap_value(value: Value) -> Value { - match value { - Value::Object(map) => { - if map.is_empty() { - Value::Object(Map::new()) // Ensure empty map is wrapped as an empty object - } else { - Value::Object(map.into_iter().map(|(k, v)| (k, wrap_value(v))).collect()) - } - } - Value::Array(arr) => Value::Array(arr.into_iter().map(wrap_value).collect()), - Value::Null => Value::Null, // Do not wrap null values - other => Value::Object(Map::from_iter(vec![("value".to_string(), other)])), // Wrap primitive values - } -} - -// Define a strategy to generate arbitrary claims with valid JSON string keys -fn claim_strategy() -> impl Strategy> { - prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", arb_json().prop_map(Value::from), 1..10) -} - -// Simulated GenericBuilder structure -struct SimulatedGenericBuilder { - claims: HashMap>, -} - -impl SimulatedGenericBuilder { - pub fn new() -> Self { - Self { - claims: HashMap::new(), - } - } - - pub fn extend_claims(&mut self, claims: HashMap>) { - self.claims.extend(claims); - } - - pub fn build_payload_from_claims(&mut self) -> Result { - let claims = std::mem::take(&mut self.claims); - let serialized_claims: HashMap = claims - .into_iter() - .map(|(k, v)| (k, serde_json::to_value(v).unwrap_or(Value::Null))) - .collect(); - let wrapped_claims = wrap_claims(serialized_claims); - serde_json::to_string(&wrapped_claims) - } -} - -// Custom function to compare JSON values with tolerance for floating-point numbers -fn compare_json_values(a: &Value, b: &Value) -> bool { - match (a, b) { - (Value::Number(a_num), Value::Number(b_num)) => { - let a_f64 = a_num.as_f64().unwrap(); - let b_f64 = b_num.as_f64().unwrap(); - (a_f64 - b_f64).abs() < 1e-10 // Tolerance for floating-point comparison - } - (Value::Object(a_map), Value::Object(b_map)) => { - if a_map.len() != b_map.len() { - return false; - } - for (key, a_value) in a_map { - if let Some(b_value) = b_map.get(key) { - if !compare_json_values(a_value, b_value) { - return false; - } - } else { - return false; - } - } - true - } - (Value::Array(a_arr), Value::Array(b_arr)) => { - if a_arr.len() != b_arr.len() { - return false; - } - for (a_value, b_value) in a_arr.iter().zip(b_arr.iter()) { - if !compare_json_values(a_value, b_value) { - return false; - } - } - true - } - _ => a == b, - } -} - -proptest! { - #[test] - #[ignore] - fn test_build_payload_from_claims(claims in claim_strategy()) { - // Debug print to check the generated claims - println!("Generated claims: {:?}", claims); - let wrapped_claims = wrap_claims(claims.clone()); - println!("Wrapped claims: {:?}", wrapped_claims); - - let mut builder = SimulatedGenericBuilder::new(); - builder.extend_claims(claims.clone().into_iter().map(|(k, v)| (k, Box::new(v) as Box)).collect()); - - let payload_result = builder.build_payload_from_claims(); - // Check if payload is built successfully - prop_assert!(payload_result.is_ok(), "Failed to build payload: {:?}", payload_result); - - let payload = payload_result.unwrap(); - println!("Generated payload: {}", payload); - let payload_value: Value = serde_json::from_str(&payload).expect("Payload should be valid JSON"); - - // Check if all claims are present in the payload - for (key, _) in claims { - let expected_value = wrapped_claims.get(&key).unwrap(); - let actual_value = payload_value.get(&key).unwrap(); - prop_assert!(compare_json_values(expected_value, actual_value), "Key '{}' not found or value mismatch: expected {:?}, got {:?}", key, expected_value, actual_value); - } - } -} +/*! + * Property Tests for build_payload_from_claims Function + * + * This file contains property tests designed to validate the correctness and robustness + * of the `build_payload_from_claims` function. The `build_payload_from_claims` function + * is responsible for constructing JSON payloads from claims, ensuring they are serialized + * and wrapped correctly. + * + * The primary goals of these tests are: + * 1. **Validation**: Ensure the `build_payload_from_claims` function correctly handles different types of claims. + * 2. **Robustness**: Identify and address potential edge cases that may not be covered by unit tests. + * 3. **Consistency**: Verify that the function maintains the expected structure and behavior for all inputs. + * + * ## Test Strategy + * + * The property tests leverage the `proptest` crate to generate a wide range of claims, + * including nested structures. The generated claims are then passed to the `build_payload_from_claims` + * function, and the resulting payloads are compared against the expected outcomes. + * + * ## Key Test Scenarios + * + * - **Null Values**: Ensure null values remain null and are not wrapped unnecessarily. + * - **Empty Objects**: Verify that empty maps are wrapped as empty JSON objects. + * - **Primitive Values**: Confirm that primitive values (e.g., strings, numbers) remain unchanged. + * - **Arrays**: Ensure arrays, including empty arrays, are wrapped correctly and consistently. + * - **Nested Structures**: Validate the recursive wrapping and serialization of nested JSON objects and arrays. + * + * ## Findings + * + * - The `build_payload_from_claims` function correctly handles most input values and passes the associated unit tests. + * - A specific floating-point corner case was identified during the testing process. This case involves minor + * discrepancies in floating-point precision, which is a common issue in many systems. The identified corner + * case has been documented and is not critical for most practical use cases. + * + * ## Conclusion + * + * The property tests demonstrate that the `build_payload_from_claims` function is robust and reliable for most practical + * use cases. While a specific floating-point corner case remains, the function's behavior is consistent with the expected + * outcomes for a wide range of input values. + * + * To run these tests, use the following command: + * + * ```sh + * cargo test -- --ignored + * ``` + * + * This approach ensures comprehensive validation of the `build_payload_from_claims` function, contributing to the overall + * stability and reliability of the system. + */ + +use std::collections::HashMap; + +use proptest::prelude::*; +use erased_serde::Serialize; +use serde_json::{Map, Number, Value}; + +// Define a strategy to generate arbitrary JSON values +fn arb_json() -> impl Strategy { + let leaf = prop_oneof![ + Just(Json::Null), + any::().prop_map(Json::Bool), + any::().prop_map(Json::Number), + "[a-zA-Z0-9_]+".prop_map(Json::String), + ]; + leaf.prop_recursive( + 3, // 3 levels deep + 64, // Shoot for maximum size of 64 nodes + 10, // We put up to 10 items per collection + |inner| prop_oneof![ + prop::collection::vec(inner.clone(), 0..10).prop_map(Json::Array), + prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", inner, 0..10).prop_map(Json::Map), + ], + ) +} + +#[derive(Clone, Debug)] +enum Json { + Null, + Bool(bool), + Number(f64), + String(String), + Array(Vec), + Map(HashMap), +} + +// Convert our custom Json enum to serde_json::Value +impl From for Value { + fn from(json: Json) -> Self { + match json { + Json::Null => Value::Null, + Json::Bool(b) => Value::Bool(b), + Json::Number(n) => Value::Number(Number::from_f64(n).unwrap()), + Json::String(s) => Value::String(s), + Json::Array(arr) => Value::Array(arr.into_iter().map(Value::from).collect()), + Json::Map(map) => Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect()), + } + } +} + + +// Wrap claims in an outer JSON object to ensure proper nesting +fn wrap_claims(claims: HashMap) -> Value { + let wrapped: HashMap = claims + .into_iter() + .map(|(k, v)| (k, wrap_value(v))) + .collect(); + Value::Object(Map::from_iter(wrapped)) +} + +// Recursively wrap values to ensure all values are valid JSON objects +fn wrap_value(value: Value) -> Value { + match value { + Value::Object(map) => { + if map.is_empty() { + Value::Object(Map::new()) // Ensure empty map is wrapped as an empty object + } else { + Value::Object(map.into_iter().map(|(k, v)| (k, wrap_value(v))).collect()) + } + } + Value::Array(arr) => Value::Array(arr.into_iter().map(wrap_value).collect()), + Value::Null => Value::Null, // Do not wrap null values + other => Value::Object(Map::from_iter(vec![("value".to_string(), other)])), // Wrap primitive values + } +} + +// Define a strategy to generate arbitrary claims with valid JSON string keys +fn claim_strategy() -> impl Strategy> { + prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", arb_json().prop_map(Value::from), 1..10) +} + +// Simulated GenericBuilder structure +struct SimulatedGenericBuilder { + claims: HashMap>, +} + +impl SimulatedGenericBuilder { + pub fn new() -> Self { + Self { + claims: HashMap::new(), + } + } + + pub fn extend_claims(&mut self, claims: HashMap>) { + self.claims.extend(claims); + } + + pub fn build_payload_from_claims(&mut self) -> Result { + let claims = std::mem::take(&mut self.claims); + let serialized_claims: HashMap = claims + .into_iter() + .map(|(k, v)| (k, serde_json::to_value(v).unwrap_or(Value::Null))) + .collect(); + let wrapped_claims = wrap_claims(serialized_claims); + serde_json::to_string(&wrapped_claims) + } +} + +// Custom function to compare JSON values with tolerance for floating-point numbers +fn compare_json_values(a: &Value, b: &Value) -> bool { + match (a, b) { + (Value::Number(a_num), Value::Number(b_num)) => { + let a_f64 = a_num.as_f64().unwrap(); + let b_f64 = b_num.as_f64().unwrap(); + (a_f64 - b_f64).abs() < 1e-10 // Tolerance for floating-point comparison + } + (Value::Object(a_map), Value::Object(b_map)) => { + if a_map.len() != b_map.len() { + return false; + } + for (key, a_value) in a_map { + if let Some(b_value) = b_map.get(key) { + if !compare_json_values(a_value, b_value) { + return false; + } + } else { + return false; + } + } + true + } + (Value::Array(a_arr), Value::Array(b_arr)) => { + if a_arr.len() != b_arr.len() { + return false; + } + for (a_value, b_value) in a_arr.iter().zip(b_arr.iter()) { + if !compare_json_values(a_value, b_value) { + return false; + } + } + true + } + _ => a == b, + } +} + +proptest! { + #[test] + #[ignore] + fn test_build_payload_from_claims(claims in claim_strategy()) { + // Debug print to check the generated claims + println!("Generated claims: {:?}", claims); + let wrapped_claims = wrap_claims(claims.clone()); + println!("Wrapped claims: {:?}", wrapped_claims); + + let mut builder = SimulatedGenericBuilder::new(); + builder.extend_claims(claims.clone().into_iter().map(|(k, v)| (k, Box::new(v) as Box)).collect()); + + let payload_result = builder.build_payload_from_claims(); + // Check if payload is built successfully + prop_assert!(payload_result.is_ok(), "Failed to build payload: {:?}", payload_result); + + let payload = payload_result.unwrap(); + println!("Generated payload: {}", payload); + let payload_value: Value = serde_json::from_str(&payload).expect("Payload should be valid JSON"); + + // Check if all claims are present in the payload + for (key, _) in claims { + let expected_value = wrapped_claims.get(&key).unwrap(); + let actual_value = payload_value.get(&key).unwrap(); + prop_assert!(compare_json_values(expected_value, actual_value), "Key '{}' not found or value mismatch: expected {:?}, got {:?}", key, expected_value, actual_value); + } + } +} diff --git a/tests/generic_claims_wrap_value_prop_test.rs b/tests/generic_claims_wrap_value_prop_test.rs index 052c5f9..7faaece 100644 --- a/tests/generic_claims_wrap_value_prop_test.rs +++ b/tests/generic_claims_wrap_value_prop_test.rs @@ -1,139 +1,139 @@ -/*! - * Property Tests for wrap_value Function - * - * This file contains property tests designed to validate the correctness and robustness - * of the `wrap_value` function. The `wrap_value` function ensures that all values are - * recursively wrapped to maintain valid JSON objects, handling various edge cases - * including empty objects, arrays, and null values. - * - * The primary goals of these tests are: - * 1. **Validation**: Ensure the `wrap_value` function correctly handles different types of JSON values. - * 2. **Robustness**: Identify and address potential edge cases that may not be covered by unit tests. - * 3. **Consistency**: Verify that the function maintains the expected structure and behavior for all inputs. - * - * ## Test Strategy - * - * The property tests leverage the `proptest` crate to generate a wide range of JSON values, - * including nested structures. The generated values are then passed to the `wrap_value` - * function, and the resulting wrapped values are compared against the expected outcomes. - * - * ## Key Test Scenarios - * - * - **Null Values**: Ensure null values remain null and are not wrapped unnecessarily. - * - **Empty Objects**: Verify that empty maps are wrapped as empty JSON objects. - * - **Primitive Values**: Confirm that primitive values (e.g., strings, numbers) remain unchanged. - * - **Arrays**: Ensure arrays, including empty arrays, are wrapped correctly and consistently. - * - **Nested Structures**: Validate the recursive wrapping of nested JSON objects and arrays. - * - * ## Findings - * - * - The `wrap_value` function correctly handles a wide range of input values, passing all property tests. - * - No significant corner cases were identified during the testing process, indicating that the function - * is robust and reliable for most practical use cases. - * - * ## Conclusion - * - * The property tests demonstrate that the `wrap_value` function is robust and reliable for most practical - * use cases. The function's behavior is consistent with the expected outcomes for a wide range of input values. - * - * This approach ensures comprehensive validation of the `wrap_value` function, contributing to the overall - * stability and reliability of the system. - */ - -use std::collections::HashMap; - -use proptest::prelude::*; -use serde_json::{Map, Value}; - -// Define a strategy to generate arbitrary JSON values -fn arb_json() -> impl Strategy { - let leaf = prop_oneof![ - Just(Json::Null), - any::().prop_map(Json::Bool), - any::().prop_map(Json::Number), - "[a-zA-Z0-9_]+".prop_map(Json::String), - ]; - leaf.prop_recursive( - 3, // 3 levels deep - 64, // Shoot for maximum size of 64 nodes - 10, // We put up to 10 items per collection - |inner| prop_oneof![ - prop::collection::vec(inner.clone(), 0..10).prop_map(Json::Array), - prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", inner, 0..10).prop_map(Json::Map), - ], - ) -} - -#[derive(Clone, Debug)] -enum Json { - Null, - Bool(bool), - Number(f64), - String(String), - Array(Vec), - Map(HashMap), -} - -// Convert our custom Json enum to serde_json::Value -impl From for Value { - fn from(json: Json) -> Self { - match json { - Json::Null => Value::Null, - Json::Bool(b) => Value::Bool(b), - Json::Number(n) => Value::Number(serde_json::Number::from_f64(n).unwrap()), - Json::String(s) => Value::String(s), - Json::Array(arr) => Value::Array(arr.into_iter().map(Value::from).collect()), - Json::Map(map) => Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect()), - } - } -} - -fn wrap_value(value: Value) -> Value { - match value { - Value::Object(map) => { - if map.is_empty() { - Value::Object(Map::new()) // Ensure empty map is wrapped as an empty object - } else { - Value::Object(map.into_iter().map(|(k, v)| (k, wrap_value(v))).collect()) - } - } - Value::Array(arr) => Value::Array(arr.into_iter().map(wrap_value).collect()), - Value::Null => Value::Null, // Do not wrap null values - other => other, // Do not wrap primitive values - } -} -proptest! { - #[test] - fn test_wrap_value(input in arb_json()) { - let value: Value = input.into(); - let wrapped_value = wrap_value(value.clone()); - - // Ensure null values remain null - if let Value::Null = value { - prop_assert_eq!(wrapped_value, Value::Null); - } else if let Value::Object(map) = &value { - if map.is_empty() { - prop_assert_eq!(wrapped_value, Value::Object(Map::new())); - } else { - // For non-empty maps, ensure they are wrapped correctly - for (k, v) in map { - let wrapped_sub_value = wrapped_value.get(k).expect("Key should exist in wrapped map"); - let expected_sub_value = wrap_value(v.clone()); - prop_assert_eq!(wrapped_sub_value, &expected_sub_value, "Key '{}' not wrapped correctly", k); - } - } - } else if let Value::Array(arr) = &value { - // Ensure arrays are wrapped correctly, including empty arrays - for (original, wrapped) in arr.iter().zip(wrapped_value.as_array().expect("Wrapped value should be an array")) { - let expected_sub_value = wrap_value(original.clone()); - prop_assert_eq!(wrapped, &expected_sub_value, "Array element not wrapped correctly"); - } - if arr.is_empty() { - prop_assert_eq!(wrapped_value, Value::Array(vec![])); - } - } else { - // For other values, ensure they remain unchanged - prop_assert_eq!(wrapped_value, value); - } - } +/*! + * Property Tests for wrap_value Function + * + * This file contains property tests designed to validate the correctness and robustness + * of the `wrap_value` function. The `wrap_value` function ensures that all values are + * recursively wrapped to maintain valid JSON objects, handling various edge cases + * including empty objects, arrays, and null values. + * + * The primary goals of these tests are: + * 1. **Validation**: Ensure the `wrap_value` function correctly handles different types of JSON values. + * 2. **Robustness**: Identify and address potential edge cases that may not be covered by unit tests. + * 3. **Consistency**: Verify that the function maintains the expected structure and behavior for all inputs. + * + * ## Test Strategy + * + * The property tests leverage the `proptest` crate to generate a wide range of JSON values, + * including nested structures. The generated values are then passed to the `wrap_value` + * function, and the resulting wrapped values are compared against the expected outcomes. + * + * ## Key Test Scenarios + * + * - **Null Values**: Ensure null values remain null and are not wrapped unnecessarily. + * - **Empty Objects**: Verify that empty maps are wrapped as empty JSON objects. + * - **Primitive Values**: Confirm that primitive values (e.g., strings, numbers) remain unchanged. + * - **Arrays**: Ensure arrays, including empty arrays, are wrapped correctly and consistently. + * - **Nested Structures**: Validate the recursive wrapping of nested JSON objects and arrays. + * + * ## Findings + * + * - The `wrap_value` function correctly handles a wide range of input values, passing all property tests. + * - No significant corner cases were identified during the testing process, indicating that the function + * is robust and reliable for most practical use cases. + * + * ## Conclusion + * + * The property tests demonstrate that the `wrap_value` function is robust and reliable for most practical + * use cases. The function's behavior is consistent with the expected outcomes for a wide range of input values. + * + * This approach ensures comprehensive validation of the `wrap_value` function, contributing to the overall + * stability and reliability of the system. + */ + +use std::collections::HashMap; + +use proptest::prelude::*; +use serde_json::{Map, Value}; + +// Define a strategy to generate arbitrary JSON values +fn arb_json() -> impl Strategy { + let leaf = prop_oneof![ + Just(Json::Null), + any::().prop_map(Json::Bool), + any::().prop_map(Json::Number), + "[a-zA-Z0-9_]+".prop_map(Json::String), + ]; + leaf.prop_recursive( + 3, // 3 levels deep + 64, // Shoot for maximum size of 64 nodes + 10, // We put up to 10 items per collection + |inner| prop_oneof![ + prop::collection::vec(inner.clone(), 0..10).prop_map(Json::Array), + prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", inner, 0..10).prop_map(Json::Map), + ], + ) +} + +#[derive(Clone, Debug)] +enum Json { + Null, + Bool(bool), + Number(f64), + String(String), + Array(Vec), + Map(HashMap), +} + +// Convert our custom Json enum to serde_json::Value +impl From for Value { + fn from(json: Json) -> Self { + match json { + Json::Null => Value::Null, + Json::Bool(b) => Value::Bool(b), + Json::Number(n) => Value::Number(serde_json::Number::from_f64(n).unwrap()), + Json::String(s) => Value::String(s), + Json::Array(arr) => Value::Array(arr.into_iter().map(Value::from).collect()), + Json::Map(map) => Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect()), + } + } +} + +fn wrap_value(value: Value) -> Value { + match value { + Value::Object(map) => { + if map.is_empty() { + Value::Object(Map::new()) // Ensure empty map is wrapped as an empty object + } else { + Value::Object(map.into_iter().map(|(k, v)| (k, wrap_value(v))).collect()) + } + } + Value::Array(arr) => Value::Array(arr.into_iter().map(wrap_value).collect()), + Value::Null => Value::Null, // Do not wrap null values + other => other, // Do not wrap primitive values + } +} +proptest! { + #[test] + fn test_wrap_value(input in arb_json()) { + let value: Value = input.into(); + let wrapped_value = wrap_value(value.clone()); + + // Ensure null values remain null + if let Value::Null = value { + prop_assert_eq!(wrapped_value, Value::Null); + } else if let Value::Object(map) = &value { + if map.is_empty() { + prop_assert_eq!(wrapped_value, Value::Object(Map::new())); + } else { + // For non-empty maps, ensure they are wrapped correctly + for (k, v) in map { + let wrapped_sub_value = wrapped_value.get(k).expect("Key should exist in wrapped map"); + let expected_sub_value = wrap_value(v.clone()); + prop_assert_eq!(wrapped_sub_value, &expected_sub_value, "Key '{}' not wrapped correctly", k); + } + } + } else if let Value::Array(arr) = &value { + // Ensure arrays are wrapped correctly, including empty arrays + for (original, wrapped) in arr.iter().zip(wrapped_value.as_array().expect("Wrapped value should be an array")) { + let expected_sub_value = wrap_value(original.clone()); + prop_assert_eq!(wrapped, &expected_sub_value, "Array element not wrapped correctly"); + } + if arr.is_empty() { + prop_assert_eq!(wrapped_value, Value::Array(vec![])); + } + } else { + // For other values, ensure they remain unchanged + prop_assert_eq!(wrapped_value, value); + } + } } \ No newline at end of file