summaryrefslogtreecommitdiffstats
path: root/vendor/pasetors/src/token.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/pasetors/src/token.rs')
-rw-r--r--vendor/pasetors/src/token.rs656
1 files changed, 656 insertions, 0 deletions
diff --git a/vendor/pasetors/src/token.rs b/vendor/pasetors/src/token.rs
new file mode 100644
index 000000000..ae3368036
--- /dev/null
+++ b/vendor/pasetors/src/token.rs
@@ -0,0 +1,656 @@
+use crate::alloc::string::ToString;
+#[cfg(feature = "std")]
+use crate::claims::Claims;
+use crate::common;
+use crate::errors::Error;
+#[cfg(feature = "std")]
+use crate::footer::Footer;
+use crate::token::private::Purpose;
+use crate::version::private::Version;
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+use core::marker::PhantomData;
+
+pub(crate) mod private {
+ use super::Error;
+ use crate::version::private::Version;
+
+ // Inside private module to prevent users from implementing this themself.
+
+ /// Purpose (`local`/`public`) of a token, given a version `V`.
+ pub trait Purpose<V: Version> {
+ /// Validate the header for a given version and purpose for some token.
+ fn validate_header(token: &str) -> Result<(), Error>;
+ /// Validate the tokens raw (decoded base64)
+ /// message length for a given version and purpose for some token.
+ fn validate_token_message_len(message: &[u8]) -> Result<(), Error>;
+ /// Parse the raw payload of a token. Either the ciphertext or the message that was signed.
+ /// The length **MUST** have been verified beforehand.
+ fn parse_raw_payload(message: &[u8]) -> &[u8];
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+/// A public token.
+pub struct Public;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+/// A local token.
+pub struct Local;
+
+impl<V: Version> Purpose<V> for Public {
+ fn validate_header(token: &str) -> Result<(), Error> {
+ if token.is_empty() || !token.starts_with(V::PUBLIC_HEADER) {
+ return Err(Error::TokenFormat);
+ }
+
+ Ok(())
+ }
+
+ fn validate_token_message_len(message: &[u8]) -> Result<(), Error> {
+ if message.len() <= V::PUBLIC_SIG {
+ // Empty payload encrypted. Disallowed by PASETO
+ return Err(Error::TokenFormat);
+ }
+
+ Ok(())
+ }
+
+ fn parse_raw_payload(message: &[u8]) -> &[u8] {
+ debug_assert!(message.len() > V::PUBLIC_SIG);
+ &message[..message.len() - V::PUBLIC_SIG]
+ }
+}
+
+impl<V: Version> Purpose<V> for Local {
+ fn validate_header(token: &str) -> Result<(), Error> {
+ if token.is_empty() || !token.starts_with(V::LOCAL_HEADER) {
+ return Err(Error::TokenFormat);
+ }
+
+ Ok(())
+ }
+
+ fn validate_token_message_len(message: &[u8]) -> Result<(), Error> {
+ if message.len() <= V::LOCAL_NONCE + V::LOCAL_TAG {
+ // Empty payload encrypted. Disallowed by PASETO
+ return Err(Error::TokenFormat);
+ }
+
+ Ok(())
+ }
+
+ fn parse_raw_payload(message: &[u8]) -> &[u8] {
+ debug_assert!(message.len() > V::LOCAL_TAG + V::LOCAL_NONCE);
+ &message[V::LOCAL_NONCE..message.len() - V::LOCAL_TAG]
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+/// A [`TrustedToken`] is returned by either a `verify()` or `decrypt()` operation and represents
+/// a validated token.
+///
+/// It represents a authenticated and non-tampered token. It **does not** validate additional things,
+/// such as claims that may be within the token payload itself. These must still be validated separately.
+///
+/// However, using the [`crate::public`] and [`crate::local`] API will automatically handle claims
+/// validation. Any validated claims may be retrieved with [`TrustedToken::payload_claims()`].
+pub struct TrustedToken {
+ header: String,
+ // PASETO requires the payload to be valid JSON in UTF-8, so we say String for UTF-8.
+ payload: String,
+ #[cfg(feature = "std")]
+ // If std is available, we also keep claims as JSON.
+ payload_claims: Option<Claims>,
+ footer: Vec<u8>,
+ implicit_assert: Vec<u8>,
+}
+
+impl TrustedToken {
+ pub(crate) fn _new(
+ header: &str,
+ payload: &[u8],
+ footer: &[u8],
+ implicit_assert: &[u8],
+ ) -> Result<Self, Error> {
+ Ok(Self {
+ header: header.to_string(),
+ payload: String::from_utf8(payload.to_vec()).map_err(|_| Error::PayloadInvalidUtf8)?,
+ #[cfg(feature = "std")]
+ payload_claims: None,
+ footer: footer.to_vec(),
+ implicit_assert: implicit_assert.to_vec(),
+ })
+ }
+
+ /// Get the header that is used for this token.
+ pub fn header(&self) -> &str {
+ &self.header
+ }
+
+ /// Get the payload that is used for this token.
+ pub fn payload(&self) -> &str {
+ &self.payload
+ }
+
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+ /// Return the optional and validated [`Claims`] parsed from the tokens payload.
+ ///
+ /// - `None`: If no [`Claims`] have been parsed or validated.
+ /// - `Some`: If some [`Claims`] have been parsed **AND** validated.
+ ///
+ /// [`Claims`]: crate::claims::Claims
+ pub fn payload_claims(&self) -> Option<&Claims> {
+ debug_assert!(self.payload_claims.is_some());
+ match &self.payload_claims {
+ Some(claims) => Some(claims),
+ None => None,
+ }
+ }
+
+ #[cfg(feature = "std")]
+ /// Set the payload claims **AFTER HAVING VALIDATED THEM**.
+ pub(crate) fn set_payload_claims(&mut self, claims: Claims) {
+ self.payload_claims = Some(claims);
+ }
+
+ /// Get the footer used to create the token.
+ ///
+ /// Empty if `None` was used during creation.
+ pub fn footer(&self) -> &[u8] {
+ &self.footer
+ }
+
+ /// Get the implicit assertion used to create the token.
+ ///
+ /// Empty if `None` was used during creation.
+ /// If token was created using `V2`, then it will always be empty.
+ pub fn implicit_assert(&self) -> &[u8] {
+ &self.implicit_assert
+ }
+}
+
+#[cfg(feature = "std")]
+impl TryFrom<&TrustedToken> for Footer {
+ type Error = Error;
+
+ fn try_from(value: &TrustedToken) -> Result<Self, Self::Error> {
+ if value.footer.is_empty() {
+ return Err(Error::FooterParsing);
+ }
+
+ let mut footer = Footer::new();
+ footer.parse_bytes(value.footer())?;
+
+ Ok(footer)
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+/// [`UntrustedToken`] can parse PASETO tokens in order to extract individual parts of it.
+///
+/// A use-case for this would be parsing the tokens footer, if this is not known before receiving it. Then,
+/// the footer can be used during verification/decryption of the token itself.
+///
+/// This type should only be used in order to verify the validity of a token.
+///
+/// __WARNING__: Anything returned by this type should be treated as **UNTRUSTED** until the token
+/// has been verified.
+pub struct UntrustedToken<T, V> {
+ message: Vec<u8>,
+ footer: Vec<u8>,
+ phantom_t: PhantomData<T>,
+ phantom_v: PhantomData<V>,
+}
+
+impl<T: Purpose<V>, V: Version> TryFrom<&str> for UntrustedToken<T, V> {
+ type Error = Error;
+
+ /// This fails if `value` is not a PASETO token or it has invalid base64 encoding.
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ T::validate_header(value)?;
+
+ let parts_split = value.split('.').collect::<Vec<&str>>();
+ if parts_split.len() < 3 || parts_split.len() > 4 {
+ return Err(Error::TokenFormat);
+ }
+ if parts_split[2].is_empty() {
+ // Empty payload entirely
+ return Err(Error::TokenFormat);
+ }
+
+ let m_raw = common::decode_b64(parts_split[2])?;
+ T::validate_token_message_len(&m_raw)?;
+ let is_footer_present = parts_split.len() == 4;
+
+ Ok(Self {
+ message: m_raw,
+ footer: {
+ if is_footer_present {
+ common::decode_b64(parts_split[3])?
+ } else {
+ Vec::<u8>::new()
+ }
+ },
+ phantom_t: PhantomData,
+ phantom_v: PhantomData,
+ })
+ }
+}
+
+impl<T: Purpose<V>, V: Version> TryFrom<&String> for UntrustedToken<T, V> {
+ type Error = Error;
+
+ /// This fails if `value` is not a PASETO token or it has invalid base64 encoding.
+ fn try_from(value: &String) -> Result<Self, Self::Error> {
+ Self::try_from(value.as_str())
+ }
+}
+
+impl<T: Purpose<V>, V: Version> UntrustedToken<T, V> {
+ /// Return untrusted message of this [`UntrustedToken`].
+ /// If it is a [`Local`] token, this is the encrypted message with nonce and tag.
+ /// If it is a [`Public`] token, the signature is included.
+ pub fn untrusted_message(&self) -> &[u8] {
+ &self.message
+ }
+
+ /// Return untrusted payload only of this [`UntrustedToken`]'s message body.
+ /// If it is a [`Local`] token, this is the encrypted message sans nonce and tag.
+ /// If it is a [`Public`] token, the signature is not included.
+ pub fn untrusted_payload(&self) -> &[u8] {
+ T::parse_raw_payload(self.untrusted_message())
+ }
+
+ /// Return untrusted footer of this [`UntrustedToken`].
+ /// Empty if there was no footer in the token.
+ pub fn untrusted_footer(&self) -> &[u8] {
+ &self.footer
+ }
+}
+
+#[cfg(test)]
+#[cfg(all(feature = "v2", feature = "v3", feature = "v4"))]
+mod tests_untrusted {
+ use super::*;
+ use crate::common::encode_b64;
+ use crate::errors::Error;
+ use crate::version::private::Version;
+ use crate::{version2::V2, version3::V3, version4::V4};
+
+ const V2_PUBLIC_TOKEN: &str = "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
+ const V2_LOCAL_TOKEN: &str = "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
+ const V3_PUBLIC_TOKEN: &str = "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9ZWrbGZ6L0MDK72skosUaS0Dz7wJ_2bMcM6tOxFuCasO9GhwHrvvchqgXQNLQQyWzGC2wkr-VKII71AvkLpC8tJOrzJV1cap9NRwoFzbcXjzMZyxQ0wkshxZxx8ImmNWP.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9";
+ const V4_PUBLIC_TOKEN: &str = "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9v3Jt8mx_TdM2ceTGoqwrh4yDFn0XsHvvV_D0DtwQxVrJEBMl0F2caAdgnpKlt4p7xBnx1HcO-SPo8FPp214HDw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
+ const V4_LOCAL_TOKEN: &str = "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t4x-RMNXtQNbz7FvFZ_G-lFpk5RG3EOrwDL6CgDqcerSQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
+
+ const TOKEN_LIST: [&str; 5] = [
+ V2_PUBLIC_TOKEN,
+ V2_LOCAL_TOKEN,
+ V3_PUBLIC_TOKEN,
+ V4_LOCAL_TOKEN,
+ V4_PUBLIC_TOKEN,
+ ];
+
+ fn test_untrusted_parse_fails(invalid: &str, expected_err: Error) {
+ if invalid.starts_with(V2::LOCAL_HEADER) {
+ assert_eq!(
+ UntrustedToken::<Local, V2>::try_from(invalid).unwrap_err(),
+ expected_err
+ );
+ }
+ if invalid.starts_with(V2::PUBLIC_HEADER) {
+ assert_eq!(
+ UntrustedToken::<Public, V2>::try_from(invalid).unwrap_err(),
+ expected_err
+ );
+ }
+ if invalid.starts_with(V3::LOCAL_HEADER) {
+ assert_eq!(
+ UntrustedToken::<Local, V3>::try_from(invalid).unwrap_err(),
+ expected_err
+ );
+ }
+ if invalid.starts_with(V3::PUBLIC_HEADER) {
+ assert_eq!(
+ UntrustedToken::<Public, V3>::try_from(invalid).unwrap_err(),
+ expected_err
+ );
+ }
+ if invalid.starts_with(V4::LOCAL_HEADER) {
+ assert_eq!(
+ UntrustedToken::<Local, V4>::try_from(invalid).unwrap_err(),
+ expected_err
+ );
+ }
+ if invalid.starts_with(V4::PUBLIC_HEADER) {
+ assert_eq!(
+ UntrustedToken::<Public, V4>::try_from(invalid).unwrap_err(),
+ expected_err
+ );
+ }
+ }
+
+ #[test]
+ fn empty_string() {
+ assert_eq!(
+ UntrustedToken::<Local, V2>::try_from("").unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Public, V2>::try_from("").unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Local, V3>::try_from("").unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Public, V3>::try_from("").unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Local, V4>::try_from("").unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Public, V4>::try_from("").unwrap_err(),
+ Error::TokenFormat
+ );
+ }
+
+ #[test]
+ fn no_separators() {
+ for token in TOKEN_LIST {
+ let split = token.split('.').collect::<Vec<&str>>();
+ let invalid: String = split.iter().copied().collect();
+
+ test_untrusted_parse_fails(&invalid, Error::TokenFormat);
+ }
+ }
+
+ #[test]
+ // NOTE: See https://github.com/paseto-standard/paseto-spec/issues/17
+ fn missing_payload() {
+ for token in TOKEN_LIST {
+ let split = token.split('.').collect::<Vec<&str>>();
+ let invalid: String = format!("{}.{}..{}", split[0], split[1], split[3]);
+
+ test_untrusted_parse_fails(&invalid, Error::TokenFormat);
+ }
+ }
+
+ #[test]
+ fn payload_too_short() {
+ for token in TOKEN_LIST {
+ let split = token.split('.').collect::<Vec<&str>>();
+ let invalid: String = format!(
+ "{}.{}.{}.{}",
+ split[0],
+ split[1],
+ encode_b64(split[0].as_bytes()).unwrap(),
+ split[3]
+ );
+
+ test_untrusted_parse_fails(&invalid, Error::TokenFormat);
+ }
+ }
+
+ #[test]
+ fn extra_after_footer() {
+ for token in TOKEN_LIST {
+ let mut invalid = token.to_string();
+ invalid.push_str(".shouldNotBeHere");
+
+ test_untrusted_parse_fails(&invalid, Error::TokenFormat);
+ }
+ }
+
+ #[test]
+ fn invalid_header() {
+ // Invalid version
+ assert_eq!(
+ UntrustedToken::<Public, V2>::try_from(&V2_PUBLIC_TOKEN.replace("v2", "v4"))
+ .unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Local, V2>::try_from(&V2_LOCAL_TOKEN.replace("v2", "v4")).unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Public, V3>::try_from(&V3_PUBLIC_TOKEN.replace("v3", "v2"))
+ .unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Local, V4>::try_from(&V4_LOCAL_TOKEN.replace("v4", "v2")).unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Public, V4>::try_from(&V4_PUBLIC_TOKEN.replace("v4", "v2"))
+ .unwrap_err(),
+ Error::TokenFormat
+ );
+
+ // Invalid purpose
+ assert_eq!(
+ UntrustedToken::<Public, V2>::try_from(&V2_PUBLIC_TOKEN.replace("public", "local"))
+ .unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Local, V2>::try_from(&V2_LOCAL_TOKEN.replace("local", "public"))
+ .unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Public, V3>::try_from(&V3_PUBLIC_TOKEN.replace("public", "local"))
+ .unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Local, V4>::try_from(&V4_LOCAL_TOKEN.replace("local", "public"))
+ .unwrap_err(),
+ Error::TokenFormat
+ );
+ assert_eq!(
+ UntrustedToken::<Public, V4>::try_from(&V4_PUBLIC_TOKEN.replace("public", "local"))
+ .unwrap_err(),
+ Error::TokenFormat
+ );
+ }
+
+ #[test]
+ fn invalid_base64() {
+ for token in TOKEN_LIST {
+ let split = token.split('.').collect::<Vec<&str>>();
+
+ let invalid: String = format!("{}.{}.{}!.{}", split[0], split[1], split[2], split[3]);
+ test_untrusted_parse_fails(&invalid, Error::Base64);
+
+ let invalid: String = format!("{}.{}.{}.{}!", split[0], split[1], split[2], split[3]);
+ test_untrusted_parse_fails(&invalid, Error::Base64);
+ }
+ }
+
+ #[cfg(feature = "v2")]
+ #[test]
+ fn valid_v2_local() {
+ // "2-E-1"
+ let valid_no_footer = "v2.local.97TTOvgwIxNGvV80XKiGZg_kD3tsXM_-qB4dZGHOeN1cTkgQ4PnW8888l802W8d9AvEGnoNBY3BnqHORy8a5cC8aKpbA0En8XELw2yDk2f1sVODyfnDbi6rEGMY3pSfCbLWMM2oHJxvlEl2XbQ";
+ // "2-E-5"
+ let valid_with_footer = "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
+
+ let untrusted_no_footer = UntrustedToken::<Local, V2>::try_from(valid_no_footer).unwrap();
+ let untrusted_with_footer =
+ UntrustedToken::<Local, V2>::try_from(valid_with_footer).unwrap();
+
+ // Note: We don't test for untrusted message, since it is encrypted.
+ assert_eq!(untrusted_no_footer.untrusted_footer(), &[0u8; 0]);
+ assert_eq!(
+ untrusted_with_footer.untrusted_footer(),
+ "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}".as_bytes()
+ );
+ }
+
+ #[cfg(feature = "v2")]
+ #[test]
+ fn valid_v2_public() {
+ // "2-S-1"
+ let valid_no_footer = "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9HQr8URrGntTu7Dz9J2IF23d1M7-9lH9xiqdGyJNvzp4angPW5Esc7C5huy_M8I8_DjJK2ZXC2SUYuOFM-Q_5Cw";
+ // "2-S-2"
+ let valid_with_footer = "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
+
+ let untrusted_no_footer = UntrustedToken::<Public, V2>::try_from(valid_no_footer).unwrap();
+ let untrusted_with_footer =
+ UntrustedToken::<Public, V2>::try_from(valid_with_footer).unwrap();
+
+ assert_eq!(
+ untrusted_no_footer.untrusted_payload(),
+ "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}"
+ .as_bytes()
+ );
+ assert_eq!(untrusted_no_footer.untrusted_footer(), &[0u8; 0]);
+
+ assert_eq!(
+ untrusted_with_footer.untrusted_payload(),
+ "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}"
+ .as_bytes()
+ );
+ assert_eq!(
+ untrusted_with_footer.untrusted_footer(),
+ "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}".as_bytes()
+ );
+ }
+
+ #[cfg(feature = "v3")]
+ #[test]
+ fn valid_v3_public() {
+ // "3-S-1"
+ let valid_no_footer = "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9qqEwwrKHKi5lJ7b9MBKc0G4MGZy0ptUiMv3lAUAaz-JY_zjoqBSIxMxhfAoeNYiSyvfUErj76KOPWm1OeNnBPkTSespeSXDGaDfxeIrl3bRrPEIy7tLwLAIsRzsXkfph";
+ // "3-S-2"
+ let valid_with_footer = "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9ZWrbGZ6L0MDK72skosUaS0Dz7wJ_2bMcM6tOxFuCasO9GhwHrvvchqgXQNLQQyWzGC2wkr-VKII71AvkLpC8tJOrzJV1cap9NRwoFzbcXjzMZyxQ0wkshxZxx8ImmNWP.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9";
+
+ let untrusted_no_footer = UntrustedToken::<Public, V3>::try_from(valid_no_footer).unwrap();
+ let untrusted_with_footer =
+ UntrustedToken::<Public, V3>::try_from(valid_with_footer).unwrap();
+
+ assert_eq!(
+ untrusted_no_footer.untrusted_payload(),
+ "{\"data\":\"this is a signed message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}"
+ .as_bytes()
+ );
+ assert_eq!(untrusted_no_footer.untrusted_footer(), &[0u8; 0]);
+
+ assert_eq!(
+ untrusted_with_footer.untrusted_payload(),
+ "{\"data\":\"this is a signed message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}"
+ .as_bytes()
+ );
+ assert_eq!(
+ untrusted_with_footer.untrusted_footer(),
+ "{\"kid\":\"dYkISylxQeecEcHELfzF88UZrwbLolNiCdpzUHGw9Uqn\"}".as_bytes()
+ );
+ }
+
+ #[cfg(feature = "v4")]
+ #[test]
+ fn valid_v4_public() {
+ // "4-S-1"
+ let valid_no_footer = "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9bg_XBBzds8lTZShVlwwKSgeKpLT3yukTw6JUz3W4h_ExsQV-P0V54zemZDcAxFaSeef1QlXEFtkqxT1ciiQEDA";
+ // "4-S-2"
+ let valid_with_footer = "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9v3Jt8mx_TdM2ceTGoqwrh4yDFn0XsHvvV_D0DtwQxVrJEBMl0F2caAdgnpKlt4p7xBnx1HcO-SPo8FPp214HDw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
+
+ let untrusted_no_footer = UntrustedToken::<Public, V4>::try_from(valid_no_footer).unwrap();
+ let untrusted_with_footer =
+ UntrustedToken::<Public, V4>::try_from(valid_with_footer).unwrap();
+
+ assert_eq!(
+ untrusted_no_footer.untrusted_payload(),
+ "{\"data\":\"this is a signed message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}"
+ .as_bytes()
+ );
+ assert_eq!(untrusted_no_footer.untrusted_footer(), &[0u8; 0]);
+
+ assert_eq!(
+ untrusted_with_footer.untrusted_payload(),
+ "{\"data\":\"this is a signed message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}"
+ .as_bytes()
+ );
+ assert_eq!(
+ untrusted_with_footer.untrusted_footer(),
+ "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}".as_bytes()
+ );
+ }
+
+ #[cfg(feature = "v4")]
+ #[test]
+ fn valid_v4_local() {
+ // "4-E-1"
+ let valid_no_footer = "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvSwscFlAl1pk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XJ5hOb_4v9RmDkneN0S92dx0OW4pgy7omxgf3S8c3LlQg";
+ // "4-E-5"
+ let valid_with_footer = "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t4x-RMNXtQNbz7FvFZ_G-lFpk5RG3EOrwDL6CgDqcerSQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9";
+
+ let untrusted_no_footer = UntrustedToken::<Local, V4>::try_from(valid_no_footer).unwrap();
+ let untrusted_with_footer =
+ UntrustedToken::<Local, V4>::try_from(valid_with_footer).unwrap();
+
+ // Note: We don't test for untrusted message, since it is encrypted.
+ assert_eq!(untrusted_no_footer.untrusted_footer(), &[0u8; 0]);
+ assert_eq!(
+ untrusted_with_footer.untrusted_footer(),
+ "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}".as_bytes()
+ );
+ }
+
+ #[test]
+ fn local_token_nonce_tag_no_payload_v4() {
+ assert!(UntrustedToken::<Local, V4>::try_from(
+ "v4.local.444444bbbbb444444444bbb444444bbb44444444444444888888888888888cJJbbb44444444",
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn local_token_nonce_tag_no_payload_v3() {
+ assert!(UntrustedToken::<Public, V3>::try_from(
+ "v3.local.oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
+ ).is_err());
+ }
+
+ #[test]
+ fn test_get_footer_from_trusted() {
+ let mut footer = Footer::default();
+ footer.add_additional("t", "v").unwrap();
+ let mut tt = TrustedToken::_new(
+ "v3.local.",
+ b"test msg",
+ footer.to_string().unwrap().as_bytes(),
+ b"",
+ )
+ .unwrap();
+ assert!(Footer::try_from(&tt).is_ok());
+ tt.footer = Vec::<u8>::new();
+ assert!(Footer::try_from(&tt).is_err());
+ }
+
+ #[test]
+ fn test_trusted_claims() {
+ let mut footer = Footer::default();
+ footer.add_additional("t", "v").unwrap();
+ let mut tt = TrustedToken::_new(
+ "v3.local.",
+ b"test msg",
+ footer.to_string().unwrap().as_bytes(),
+ b"",
+ )
+ .unwrap();
+ let claims = Claims::new().unwrap();
+ tt.set_payload_claims(claims.clone());
+
+ assert_eq!(tt.payload_claims.unwrap(), claims);
+ }
+}