#json #jwt #jose #jws #jwe

no-way

A library to work with Javascript Object Signing and Encryption(JOSE), including JSON Web Tokens (JWT), JSON Web Signature (JWS) and JSON Web Encryption (JWE)

6 releases (3 breaking)

0.4.1 Oct 9, 2022
0.4.0 Oct 8, 2022
0.3.0 Oct 7, 2022
0.2.0 Oct 3, 2022
0.1.1 Oct 1, 2022

#1596 in Web programming

MIT license

325KB
6K SLoC

No Way, Jose!

A library to work with Javascript Object Signing and Encryption (JOSE), including:

  • JSON Web Tokens (JWT)
  • JSON Web Signature (JWS)
  • JSON Web Encryption (JWE)
  • JSON Web Algorithms (JWA)
  • JSON Web Keys (JWK)

This was inspired by lawliet89/biscuit, which is itself based off Keats/rust-jwt.

Configuration

All cryptographic algorithms are chosen at compile time. This reduces any risk of the infamous 'none' attack.

Supported Features

The crate, does not support all, and probably will never support all of the features described in the various RFCs, including some algorithms and verification.

Quick Demo

JWTs

use no_way::{JWT, jwa, jws, jwk, ClaimsSet, RegisteredClaims};
use serde::{Serialize, Deserialize};

// Define our own private claims
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
struct PrivateClaims {
    company: String,
    department: String,
}

let signing_key = jwk::OctetKey::new("secret".to_string().into_bytes());

let expected_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\
       eyJpc3MiOiJodHRwczovL3d3dy5hY21lLmNvbS8iLCJzdWIiOiJKb2huIERvZSIsImF1ZCI6Imh0dHBzOi8vYWNtZ\
       S1jdXN0b21lci5jb20vIiwibmJmIjoxMjM0LCJjb21wYW55IjoiQUNNRSIsImRlcGFydG1lbnQiOiJUb2lsZXQgQ2\
       xlYW5pbmcifQ.VFCl2un1Kc17odzOe2Ehf4DVrWddu3U4Ux3GFpOZHtc";

let expected_claims = ClaimsSet::<PrivateClaims> {
    registered: RegisteredClaims {
        issuer: Some("https://www.acme.com/".into()),
        subject: Some("John Doe".to_string()),
        audience: Some("https://acme-customer.com/".into()),
        not_before: Some(1234.try_into().unwrap()),
        ..Default::default()
    },
    private: PrivateClaims {
        department: "Toilet Cleaning".to_string(),
        company: "ACME".to_string(),
    },
};

let jwt = JWT::new(expected_claims.clone());

let token = jwt.encode::<jwa::sign::HS256>(&signing_key).unwrap().to_string();
assert_eq!(expected_token, token);
// Now, send `token` to your clients

// ... some time later, we get token back!

let encoded_token: jws::Encoded<ClaimsSet::<PrivateClaims>> = token.parse().unwrap();
let token = JWT::<_>::decode::<jwa::sign::HS256>(encoded_token, &signing_key).unwrap();
assert_eq!(token.payload, expected_claims);

JWEs

use no_way::{ClaimsSet, RegisteredClaims, JWT, JWE};
use no_way::jwk;
use no_way::jwe::Encrypted;
use no_way::jwa::{kma, cea, sign};
use serde::{Serialize, Deserialize};

// Define our own private claims
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
struct PrivateClaims {
    company: String,
    department: String,
}

// Craft our JWS
let expected_claims = ClaimsSet::<PrivateClaims> {
    registered: RegisteredClaims {
        issuer: Some("https://www.acme.com".into()),
        subject: Some("John Doe".into()),
        audience: Some("htts://acme-customer.com".into()),
        not_before: Some(1234.try_into().unwrap()),
        ..Default::default()
    },
    private: PrivateClaims {
        department: "Toilet Cleaning".to_string(),
        company: "ACME".to_string(),
    },
};

let expected_jwt = JWT::new(expected_claims.clone());

let signing_key = jwk::OctetKey::new("secret".to_string().into_bytes());
let jws = expected_jwt.encode::<sign::HS256>(&signing_key).unwrap();

// Encrypt the token

// You would usually have your own AES key for this, but we will use a zeroed key as an example
let key = jwk::OctetKey::new(vec![0; 256 / 8]);

/// We need to create a nonce for AES GCM encryption.
/// You must take care NOT to reuse the nonce.
/// You can simply treat the nonce as a 96 bit
/// counter that is incremented after every use
///
/// In this case, we're using a 64bit counter + a 32bit random prefix tag
fn generate_nonce() -> Vec<u8> {
    # use std::sync::atomic::{AtomicU64, Ordering};
    static NONCE: AtomicU64 = AtomicU64::new(0);
    // use some lazy random generation so each service has a separate tag
    static TAG: u32 = 0xDEADCAFE;

    // fetch and increment the nonce counter
    let nonce = NONCE.fetch_add(1, Ordering::Release);

    // collect the bytes together and return them
    let mut output = vec![0; 96/8];
    output[0..32/8].copy_from_slice(&TAG.to_be_bytes());
    output[32/8..].copy_from_slice(&nonce.to_be_bytes());
    output
}
let nonce = generate_nonce();

// Construct the JWE
let jwe = JWE::new(jws.clone());

// Encrypt
let encrypted_jwe = jwe.encrypt::<
    cea::A256GCM,   // encrypt the contents with AES256 GCM
    kma::A256GCMKW, // perform key wrapping with AES256 GCM
>(&key, nonce).unwrap();

let token = encrypted_jwe.to_string();

// Now, send `token` to your clients

// ... some time later, we get token back!
let token: Encrypted<kma::A256GCMKW> = token.parse().unwrap();

// Decrypt
let decrypted_jwe = token.decrypt::<_, cea::A256GCM>(&key).unwrap();

assert_eq!(jws, decrypted_jwe.payload);

Dependencies

~8.5MB
~171K SLoC