Skip to content

Commit

Permalink
Update documenation
Browse files Browse the repository at this point in the history
  • Loading branch information
rrrodzilla committed Dec 10, 2021
1 parent 671f1ad commit 160377a
Show file tree
Hide file tree
Showing 14 changed files with 218 additions and 73 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
name = "rusty_paseto"
version = "0.2.3"
edition = "2021"
readme = "readme.md"
authors = ["Roland Rodriguez <rolandrodriguez@gmail.com"]
description = "A type-driven, ergonomic implementation of the PASETO protocol for secure stateless tokens."
license = "MIT OR Apache-2.0"
keywords = ["crypto", "cryptography", "authentication", "paseto", "token", "security", "api", "web"]
categories = ["cryptography", "authentication", "encoding", "network-programming", "web-programming"]

[lib]
#doctest = false
Expand Down
27 changes: 27 additions & 0 deletions src/core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,76 @@ use thiserror::Error;
/// Potential errors from attempting to build a token claim
#[derive(Debug, Error)]
pub enum PasetoError {
///A general, unspecified (for security reasons) cipher error
#[error("A cipher error occurred")]
PasetoCipherError(Box<PasetoError>),
///A general, unspecified (for security reasons) cipher error
#[error("An unspecified cryption error occured")]
Cryption,
///A problem generating a signature
#[error("Could not assemble final signature.")]
Signature,
/// Occurs when a private RSA key is not in pkcs#8 format
#[error("A private RSA key was not in the correct format")]
KeyRejected {
///Surfaces key rejection errors from ring
#[from]
source: ring::error::KeyRejected,
},
///A general, unspecified (for security reasons) cipher error
#[error("An unspecified cipher error occurred")]
Cipher {
///Surfaces unspecified errors from ring
#[from]
source: ring::error::Unspecified,
},
///An RSA cipher error
#[error("An unspecified cipher error occurred")]
RsaCipher {
///An RSA cipher error
#[from]
source: ed25519_dalek::ed25519::Error,
},
///Occurs when a signature fails verification
#[error("The token signature could not be verified")]
InvalidSignature,
///Occurs when an untrusted token string is unable to be parsed into its constituent parts
#[error("This string has an incorrect number of parts and cannot be parsed into a token")]
IncorrectSize,
///Occurs when an incorrect header is provided on an untrusted token string
#[error("The token header is invalid")]
WrongHeader,
///Occurs when an incorrect footer was passed in an attempt to parse an untrusted token string
#[error("The provided footer is invalid")]
FooterInvalid,
///Occurs when a base64 encoded payload cannot be decoded
#[error("A base64 decode error occurred")]
PayloadBase64Decode {
///Surfaced from the base64 crate
#[from]
source: base64::DecodeError,
},
///Occurs when a string fails parsing as Utf8
#[error("A Utf8 parsing error occurred")]
Utf8Error {
///Surfaced from std::str::Utf8
#[from]
source: std::str::Utf8Error,
},
///A cipher error from the ChaCha algorithm
#[error("An unspecified cipher error occurred")]
ChaChaCipherError,
///An infallible error
#[error("A Utf8 parsing error occurred")]
Infallibale {
///An infallible error
#[from]
source: std::convert::Infallible,
},
///Occurs when a string fails conversion from Utf8
#[error("A Utf8 parsing error occurred")]
FromUtf8Error {
///Surfaced from std::string::FromUtf8Error
#[from]
source: std::string::FromUtf8Error,
},
Expand Down
18 changes: 18 additions & 0 deletions src/core/footer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@ use super::*;
use std::fmt;
use std::ops::Deref;

/// Unencrypted text, potentially JSON or some other structured format, typically used for key rotation schemes, packed into the
/// payload as part of the cipher scheme.
///
/// # Usage
/// ```
/// # use rusty_paseto::prelude::*;
/// # let key = PasetoSymmetricKey::<V2, Local>::from(Key::<32>::from(b"wubbalubbadubdubwubbalubbadubdub"));
/// let token = PasetoBuilder::<V2, Local>::default()
/// // note how we set the footer here
/// .set_footer(Footer::from("Sometimes science is more art than science"))
/// .build(&key)?;
///
/// // the footer same footer should be used to parse the token
/// let json_value = PasetoParser::<V2, Local>::default()
/// .set_footer(Footer::from("Sometimes science is more art than science"))
/// .parse(&token, &key)?;
/// # Ok::<(),anyhow::Error>(())
/// ```
#[derive(Default, Debug, Clone, Copy)]
pub struct Footer<'a>(&'a str);

Expand Down
19 changes: 19 additions & 0 deletions src/core/implicit_assertion.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
use std::fmt;
use std::ops::Deref;

/// Unencrypted but authenticated data (like the optional footer), but is NOT stored in the PASETO token (thus, implicit) and MUST be asserted when verifying a token.
/// The main purpose for Implicit Assertions is to bind the token to some value that, due to business reasons, shouldn't ever be revealed publicly (i.e., a primary key or foreign key from a relational database table).
/// Implicit Assertions allow you to build systems that are impervious to Confused Deputy attacks without ever having to disclose these internal values.
///
/// # Usage
/// ```
/// # use rusty_paseto::prelude::*;
/// # let key = PasetoSymmetricKey::<V4, Local>::from(Key::<32>::from(b"wubbalubbadubdubwubbalubbadubdub"));
/// let token = PasetoBuilder::<V4, Local>::default()
/// // note how we set the footer here
/// .set_implicit_assertion(ImplicitAssertion::from("Sometimes science is more art than science"))
/// .build(&key)?;
///
/// // the footer same footer should be used to parse the token
/// let json_value = PasetoParser::<V4, Local>::default()
/// .set_implicit_assertion(ImplicitAssertion::from("Sometimes science is more art than science"))
/// .parse(&token, &key)?;
/// # Ok::<(),anyhow::Error>(())
/// ```
#[derive(Default, Debug, Copy, Clone)]
pub struct ImplicitAssertion<'a>(&'a str);

Expand Down
17 changes: 15 additions & 2 deletions src/core/paseto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,15 @@ where
Purpose: PurposeTrait,
Version: ImplicitAssertionCapable,
{
/// Note: Only for V3, V4 tokens
pub fn set_implicit_assertion(&mut self, implicit_assertion: ImplicitAssertion<'a>) -> &mut Self {
self.implicit_assertion = Some(implicit_assertion);
self
}
}

impl<'a> Paseto<'a, V1, Public> {
/// Verifies a signed V1 Public Paseto
pub fn try_verify(
signature: &'a str,
public_key: &PasetoAsymmetricPublicKey<V1, Public>,
Expand All @@ -135,10 +137,13 @@ impl<'a> Paseto<'a, V1, Public> {
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<V1, Public>) -> Result<String, PasetoError> {
let footer = self.footer.unwrap_or_default();

let key_pair = RsaKeyPair::from_pkcs8(key.as_ref()).expect("Bad Private Key pkcs!");
let key_pair = RsaKeyPair::from_pkcs8(key.as_ref())?;

let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer]);
let random = SystemRandom::new();
Expand All @@ -156,6 +161,9 @@ impl<'a> Paseto<'a, V1, Public> {
}

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<V2, Public>,
Expand All @@ -181,6 +189,8 @@ impl<'a> Paseto<'a, V2, Public> {
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<V2, Public>) -> Result<String, PasetoError> {
let footer = self.footer.unwrap_or_default();

Expand All @@ -197,7 +207,8 @@ impl<'a> Paseto<'a, V2, Public> {
}

impl<'a> Paseto<'a, V1, Local> {
/// Parse an untrusted token string to validate and decrypt into a plaintext payload
/// Attempt to parse an untrusted token string to validate and decrypt into a plaintext payload
/// Fails with a PasetoError if the token is malformed or can't be decrypted
pub fn try_decrypt(
token: &'a str,
key: &PasetoSymmetricKey<V1, Local>,
Expand Down Expand Up @@ -237,6 +248,8 @@ impl<'a> Paseto<'a, V1, Local> {
Ok(decoded_str.to_owned())
}

/// Attempt to encrypt a V1, Local Paseto token
/// Fails with a PasetoError if the token is malformed or can't be encrypted
pub fn try_encrypt(
&mut self,
key: &PasetoSymmetricKey<V1, Local>,
Expand Down
10 changes: 5 additions & 5 deletions src/generic/builders/generic_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ impl<'a, Version, Purpose> GenericBuilder<'a, Version, Purpose> {
self
}

pub fn set_footer(&mut self, footer: &'a Footer) -> &mut Self {
self.footer = Some(*footer);
pub fn set_footer(&mut self, footer: Footer<'a>) -> &mut Self {
self.footer = Some(footer);
self
}

Expand Down Expand Up @@ -68,8 +68,8 @@ impl<'a, Version, Purpose> GenericBuilder<'a, Version, Purpose>
where
Version: ImplicitAssertionCapable,
{
pub fn set_implicit_assertion(&mut self, implicit_assertion: &'a ImplicitAssertion) -> &mut Self {
self.implicit_assertion = Some(*implicit_assertion);
pub fn set_implicit_assertion(&mut self, implicit_assertion: ImplicitAssertion<'a>) -> &mut Self {
self.implicit_assertion = Some(implicit_assertion);
self
}
}
Expand Down Expand Up @@ -214,7 +214,7 @@ mod builders {
.set_claim(CustomClaim::try_from(("data", "this is a secret message"))?)
.set_claim(CustomClaim::try_from(("seats", 4))?)
.set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?)
.set_footer(&footer)
.set_footer(footer)
.try_encrypt(&key)?;

//now let's decrypt the token and verify the values
Expand Down
12 changes: 10 additions & 2 deletions src/generic/claims/error.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
use thiserror::Error;

/// Errors from validating claims in a parsed token
#[derive(Debug, Error)]
pub enum PasetoClaimError {
/// Occurs during an attempt to parse an expired token
#[error("This token is expired")]
Expired,
/// Occurs during an attempt to parse a token before its Not Before claim time
#[error("The token cannot be used before {0}")]
UseBeforeAvailable(String),
/// Occurs if a date time is passed that is not a valid RFC3339 date - "2019-01-01T00:00:00+00:00"
#[error("The value {0} is a malformed RFC3339 date")]
RFC3339Date(String),
/// Occurs if a claim was expected but wasn't found in the payload
#[error("The expected claim '{0}' was not found in the payload")]
Missing(String),
/// Occurs during claim validation if a claim value was unable to be converted to its expected type
#[error("Could not convert claim '{0}' to the expected data type")]
Unexpected(String),
/// Occurs when a custom claim fails validation
#[error("The claim '{0}' failed custom validation")]
CustomValidation(String),
/// Occurs when a claim fails validation
#[error("The claim '{0}' failed validation. Expected '{1}' but received '{2}'")]
Invalid(String, String, String),
/// Occurs when a user attempts to create a custom claim using a reserved claim key
#[error("The key {0} is a reserved for use within PASETO. To set a reserved claim, use the strong type: e.g - ExpirationClaimClaim")]
Reserved(String),
#[error("{0} is an invalid iso8601 (email) string")]
BadEmailAddress(String),
/// Occurs when a user attempts to use a top level claim more than once in the payload
#[error("The claim '{0}' appears more than once in the top level payload json")]
DuplicateTopLevelPayloadClaim(String),
}
4 changes: 2 additions & 2 deletions src/generic/claims/expiration_claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl TryFrom<String> for ExpirationClaim {
fn try_from(value: String) -> Result<Self, Self::Error> {
match iso8601::datetime(&value) {
Ok(_) => Ok(Self(("exp".to_string(), value))),
Err(_) => Err(PasetoClaimError::BadEmailAddress(value.to_string())),
Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())),
}
}
}
Expand All @@ -32,7 +32,7 @@ impl TryFrom<&str> for ExpirationClaim {
fn try_from(value: &str) -> Result<Self, Self::Error> {
match iso8601::datetime(value) {
Ok(_) => Ok(Self(("exp".to_string(), value.to_string()))),
Err(_) => Err(PasetoClaimError::BadEmailAddress(value.to_string())),
Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/generic/claims/issued_at_claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl TryFrom<&str> for IssuedAtClaim {
fn try_from(value: &str) -> Result<Self, Self::Error> {
match iso8601::datetime(value) {
Ok(_) => Ok(Self(("iat".to_string(), value.to_string()))),
Err(_) => Err(PasetoClaimError::BadEmailAddress(value.to_string())),
Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())),
}
}
}
Expand All @@ -39,7 +39,7 @@ impl TryFrom<String> for IssuedAtClaim {
fn try_from(value: String) -> Result<Self, Self::Error> {
match iso8601::datetime(&value) {
Ok(_) => Ok(Self(("iat".to_string(), value))),
Err(_) => Err(PasetoClaimError::BadEmailAddress(value.to_string())),
Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/generic/claims/not_before_claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl TryFrom<String> for NotBeforeClaim {
fn try_from(value: String) -> Result<Self, Self::Error> {
match iso8601::datetime(&value) {
Ok(_) => Ok(Self(("nbf".to_string(), value))),
Err(_) => Err(PasetoClaimError::BadEmailAddress(value.to_string())),
Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())),
}
}
}
Expand All @@ -32,7 +32,7 @@ impl TryFrom<&str> for NotBeforeClaim {
fn try_from(value: &str) -> Result<Self, Self::Error> {
match iso8601::datetime(value) {
Ok(_) => Ok(Self(("nbf".to_string(), value.to_string()))),
Err(_) => Err(PasetoClaimError::BadEmailAddress(value.to_string())),
Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/generic/parsers/generic_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ mod parsers {
.set_claim(CustomClaim::try_from(("data", "this is a secret message"))?)
.set_claim(CustomClaim::try_from(("seats", 4))?)
.set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?)
.set_footer(&footer)
.set_footer(footer)
.try_sign(&private_key)?;

//now let's decrypt the token and verify the values
Expand Down Expand Up @@ -315,7 +315,7 @@ mod parsers {
.set_claim(CustomClaim::try_from(("data", "this is a secret message"))?)
.set_claim(CustomClaim::try_from(("seats", 4))?)
.set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?)
.set_footer(&footer)
.set_footer(footer)
.try_encrypt(&key)?;

//now let's decrypt the token and verify the values
Expand Down
Loading

0 comments on commit 160377a

Please sign in to comment.