├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── crypto │ ├── algorithm.rs │ ├── ecdsa.rs │ ├── hmac.rs │ ├── mod.rs │ └── rsa.rs ├── encode.rs ├── error.rs ├── lib.rs ├── pem │ ├── decoder.rs │ └── mod.rs ├── raw.rs └── verifier.rs └── tests ├── common └── mod.rs ├── ecdsa ├── README.md ├── mod.rs ├── private_ecdsa_key_jwtio_p256_pkcs8.pem ├── private_ecdsa_key_jwtio_p256_sec1.pem ├── private_ecdsa_key_jwtio_p384_pkcs8.pem ├── private_ecdsa_key_jwtio_p384_sec1.pem ├── public_ecdsa_key_jwtio_p256_pkcs8.pem └── public_ecdsa_key_jwtio_p384_pkcs8.pem ├── hmac.rs ├── lib.rs ├── none.rs ├── rsa ├── certificate_rsa_pkcs1.crt ├── certificate_rsa_pkcs1.csr ├── mod.rs ├── private_rsa_key_jwtio_pkcs1.pem ├── private_rsa_key_pkcs1.pem ├── private_rsa_key_pkcs8.pem ├── public_rsa_key_jwtio_pkcs1.pem ├── public_rsa_key_pkcs1.pem └── public_rsa_key_pkcs8.pem ├── time.rs └── verifier.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | 13 | - package-ecosystem: "cargo" 14 | directory: "/" 15 | schedule: 16 | interval: "daily" 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | # Check code formatting. 7 | fmt: 8 | name: Rustfmt 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout sources 12 | uses: actions/checkout@v3 13 | - name: Install rust 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | components: rustfmt 18 | profile: minimal 19 | override: true 20 | - name: Run rustfmt 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: fmt 24 | args: --all -- --check 25 | 26 | # Static analyzer. 27 | clippy: 28 | name: Clippy 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout sources 32 | uses: actions/checkout@v3 33 | - name: Install rust 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: stable 37 | components: clippy 38 | profile: minimal 39 | override: true 40 | - name: Run clippy 41 | uses: actions-rs/clippy-check@v1 42 | with: 43 | token: ${{ secrets.GITHUB_TOKEN }} 44 | args: --all --tests --all-features -- -D warnings 45 | 46 | # Security audit. 47 | audit: 48 | name: Security audit 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v3 52 | - uses: actions-rs/audit-check@v1 53 | with: 54 | token: ${{ secrets.GITHUB_TOKEN }} 55 | 56 | # Tests. 57 | test: 58 | name: ${{ matrix.build }} 59 | runs-on: ${{ matrix.os }} 60 | strategy: 61 | matrix: 62 | include: 63 | - build: Linux 64 | os: ubuntu-latest 65 | steps: 66 | - name: Checkout sources 67 | uses: actions/checkout@v3 68 | - name: Install rust 69 | uses: actions-rs/toolchain@v1 70 | with: 71 | toolchain: stable 72 | profile: minimal 73 | override: true 74 | - name: Build 75 | uses: actions-rs/cargo@v1 76 | with: 77 | command: build 78 | args: --examples --all 79 | - name: Test default features 80 | uses: actions-rs/cargo@v1 81 | with: 82 | command: test 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | jobs: 7 | allow_failures: 8 | - rust: nightly 9 | fast_finish: true 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | 11 | ## [1.2.0] 2022-12-20 12 | 13 | ### Added 14 | 15 | - Allow PEM certificates to be read as public keys ([#19](https://github.com/rib/jsonwebtokens/pull/19)) 16 | 17 | ### Changed 18 | 19 | - A verifier with no associated `kid` will allow any token `kid` (like a wildcard) instead of requiring there to be no `kid`. This is a fix that's consistent with the original API documentation ([#18](https://github.com/rib/jsonwebtokens/pull/18)) 20 | 21 | 22 | ## [1.1.0] 2021-08-15 23 | 24 | ### Added 25 | 26 | - Allow string _or_ array `aud` claims (previously just allowed string `aud`) ([#13](https://github.com/rib/jsonwebtokens/issues/13)) 27 | 28 | ### Removed 29 | 30 | - Spurious assertion that any empty claim key `""` must have a string value -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonwebtokens" 3 | version = "1.2.0" 4 | authors = ["Robert Bragg "] 5 | license = "MIT" 6 | description = "A Json Web Token implementation for Rust" 7 | homepage = "https://github.com/rib/jsonwebtokens" 8 | repository = "https://github.com/rib/jsonwebtokens" 9 | documentation = "https://docs.rs/jsonwebtokens" 10 | readme = "README.md" 11 | keywords = ["jwt", "json", "web", "token", "api"] 12 | edition = "2021" 13 | 14 | [features] 15 | default = [ "matching" ] 16 | matching = [ "regex" ] 17 | 18 | [dependencies] 19 | base64 = "0.21" 20 | serde = { version="1", features=["derive"] } 21 | serde_json = "1" 22 | regex = { version = "1", optional = true } 23 | pem = "1.1" 24 | simple_asn1 = "0.6" 25 | ring = { version = "0.16", features = ["std"] } 26 | 27 | [dev-dependencies] 28 | tokio-test = "0.4" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Robert Bragg 4 | Copyright (c) 2015 Vincent Prouillet 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![jsonwebtokens](https://img.shields.io/crates/v/jsonwebtokens?style=flat-square)](https://crates.io/crates/jsonwebtokens) 2 | ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) 3 | [![Build Status](https://travis-ci.org/rib/jsonwebtokens.svg)](https://travis-ci.org/rib/jsonwebtokens) 4 | 5 | 6 | A Rust implementation of [Json Web Tokens](https://tools.ietf.org/html/rfc7519) 7 | 8 | # Installation 9 | 10 | ``` 11 | jsonwebtokens = "1" 12 | serde_json = "1" 13 | ``` 14 | 15 | Then, in your code: 16 | ```rust 17 | use serde_json::json; 18 | use serde_json::value::Value; 19 | 20 | use jsonwebtokens as jwt; 21 | use jwt::{Algorithm, AlgorithmID, Verifier}; 22 | ``` 23 | 24 | # Usage 25 | 26 | The main two types are `Algorithm` and `Verifier`. An `Algorithm` encapsulates 27 | a cryptographic function for signing or verifying tokens, and a `Verifier` 28 | handles checking the signature and claims of a token, given an `Algorithm`. 29 | 30 | Creating an `Algorithm` separately ensures any parsing of secrets or keys only 31 | needs to happen once. 32 | 33 | The builder pattern used for describing a `Verifier` keeps code ergonimic no 34 | matter if you have simple or elaborate verification requirements. 35 | 36 | There is also a low-level [(`::raw`)](#Low-level-Usage) API available in 37 | case you need more control over splitting, decoding, deserializing and 38 | verifying tokens. 39 | 40 | ## Signing a token 41 | 42 | with a symmetric secret: 43 | ```rust 44 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret")?; 45 | let header = json!({ "alg": alg.name() }); 46 | let claims = json!({ "foo": "bar" }); 47 | let token = encode(&header, &claims, &alg)?; 48 | ``` 49 | or if your secret is base64 encoded: 50 | ```rust 51 | let alg = Algorithm::new_hmac_b64(AlgorithmID::HS256, secret_data)?; 52 | ``` 53 | 54 | with an RSA private key: 55 | ```rust 56 | let alg = Algorithm::new_rsa_pem_signer(AlgorithmID::RS256, pem_data)?; 57 | let header = json!({ "alg": alg.name() }); 58 | let claims = json!({ "foo": "bar" }); 59 | let token = encode(&header, &claims, &alg)?; 60 | ``` 61 | 62 | ## Verifying tokens 63 | 64 | with a symmetric secret: 65 | ```rust 66 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret")?; 67 | let verifier = Verifier::create() 68 | .issuer("http://some-auth-service.com") 69 | .audience("application_id") 70 | .build()?; 71 | let claims: Value = verifier.verify(&token_str, &alg)?; 72 | ``` 73 | 74 | with an RSA private key: 75 | ```rust 76 | let alg = Algorithm::new_rsa_pem_verifier(AlgorithmID::RS256, pem_data)?; 77 | let verifier = Verifier::create() 78 | .issuer("http://some-auth-service.com") 79 | .audience("application_id") 80 | .build()?; 81 | let claims: Value = verifier.verify(&token_str, &alg)?; 82 | ``` 83 | 84 | ## Verifying standard claims 85 | ```rust 86 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret")?; 87 | let verifier = Verifier::create() 88 | .issuer("http://some-auth-service.com") 89 | .audience("application_id") 90 | .subject("subject") 91 | .nonce("9837459873945093845") 92 | .leeway(5) // give this much leeway (in seconds) when validating exp, nbf and iat claims 93 | .build()?; 94 | let claims: Value = verifier.verify(&token_str, &alg)?; 95 | ``` 96 | 97 | ## Verifying custom claims 98 | ```rust 99 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret")?; 100 | let verifier = Verifier::create() 101 | .string_equals("my_claim0", "value") 102 | .string_matches("my_claim1", Regex::new("value[0-9]").unwrap()) 103 | .string_equals_one_of("my_claim2", &["value0", "value1"]) 104 | .string_matches_one_of("my_claim3", &[regex0, regex1]) 105 | .claim_callback("my_claim4", |v| v.is_u64() && v.as_u64().unwrap() == 1234) 106 | .build()?; 107 | let claims: Value = verifier.verify(&token_str, &alg)?; 108 | ``` 109 | 110 | ## Verifying timestamps (or not) 111 | ```rust 112 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret")?; 113 | let verifier = Verifier::create() 114 | .leeway(5) // give this much leeway when validating exp, nbf and iat claims 115 | .ignore_exp() // ignore expiry 116 | .ignore_nbf() // ignore 'not before time' 117 | .ignore_iat() // ignore issue time 118 | .build()?; 119 | let claims: Value = verifier.verify(&token_str, &alg)?; 120 | ``` 121 | 122 | # Low-level Usage 123 | 124 | In case you need even more fine-grained control than is possible with the 125 | above APIs, many of the lower-level details are exposed through the `::raw` 126 | module to allow you to manually split, decode and verify a JWT token. 127 | 128 | 129 | ## Just split a token into component parts 130 | ```rust 131 | let TokenSlices {message, signature, header, claims } = raw::split_token(token)?; 132 | ``` 133 | 134 | ## Just parse the header 135 | ```rust 136 | use serde_json::value::Value; 137 | let header: Value = raw::decode_header_only(token); 138 | ``` 139 | 140 | ## Base64 decode header or claims and deserialize JSON 141 | Equivalent to `raw::decode_header_only()`: 142 | ```rust 143 | let TokenSlices {header, .. } = raw::split_token(token)?; 144 | let header = raw::decode_json_token_slice(header)?; 145 | ``` 146 | 147 | Or, decode and deserialize just the claims: 148 | ```rust 149 | let TokenSlices {claims, .. } = raw::split_token(token)?; 150 | let claims = raw::decode_json_token_slice(claims)?; 151 | ``` 152 | 153 | ## Manually split, decode and verify a token 154 | ```rust 155 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret")?; 156 | let verifier = Verifier::create() 157 | // snip 158 | .build()?; 159 | 160 | let TokenSlices {message, signature, header, claims } = raw::split_token(token)?; 161 | let header = raw::decode_json_token_slice(header)?; 162 | raw::verify_signature_only(&header, message, signature, &alg)?; 163 | let claims = raw::decode_json_token_slice(claims)?; 164 | verifier.verify_claims_only(&claims, time_now)?; 165 | ``` 166 | 167 | # Algorithms Supported 168 | 169 | Array of supported algorithms. The following algorithms are currently supported. 170 | 171 | alg Parameter Value | Digital Signature or MAC Algorithm 172 | ----------------|---------------------------- 173 | HS256 | HMAC using SHA-256 hash algorithm 174 | HS384 | HMAC using SHA-384 hash algorithm 175 | HS512 | HMAC using SHA-512 hash algorithm 176 | RS256 | RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm 177 | RS384 | RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm 178 | RS512 | RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm 179 | PS256 | RSASSA-PSS using SHA-256 hash algorithm 180 | PS384 | RSASSA-PSS using SHA-384 hash algorithm 181 | PS512 | RSASSA-PSS using SHA-512 hash algorithm 182 | ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm (only PKCS#8 format PEM) 183 | ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm (only PKCS#8 format PEM) 184 | none | No digital signature or MAC value included 185 | 186 | # Based on 187 | 188 | Originally this project started as a few small changes to 189 | [jsonwebtoken](https://crates.io/crates/jsonwebtoken) (without an 's'), to 190 | meet the needs I had while building 191 | [jsonwebtokens-cognito](https://crates.io/crates/jsonwebtokens-cognito) but 192 | eventually the design and implementation became substantially different with 193 | the creation of the `Algorithm` API and the customizable `Verifier` 194 | API. 195 | 196 | The project borrows design ideas from a variety of pre-existing Json Web 197 | Token libraries. In particular it shamelessly steals ideas from 198 | [node-jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) and 199 | [java-jwt](https://github.com/auth0/java-jwt). 200 | -------------------------------------------------------------------------------- /src/crypto/algorithm.rs: -------------------------------------------------------------------------------- 1 | use ring::signature; 2 | use serde::{Deserialize, Serialize}; 3 | use simple_asn1::BigUint; 4 | use std::fmt; 5 | use std::str::FromStr; 6 | 7 | use crate::crypto::*; 8 | use crate::error::{Error, ErrorDetails}; 9 | use crate::pem::decoder::PemEncodedKey; 10 | use crate::raw::*; 11 | 12 | impl From for &dyn signature::VerificationAlgorithm { 13 | fn from(alg: AlgorithmID) -> Self { 14 | match alg { 15 | AlgorithmID::ES256 => &signature::ECDSA_P256_SHA256_FIXED, 16 | AlgorithmID::ES384 => &signature::ECDSA_P384_SHA384_FIXED, 17 | 18 | AlgorithmID::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256, 19 | AlgorithmID::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384, 20 | AlgorithmID::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512, 21 | AlgorithmID::PS256 => &signature::RSA_PSS_2048_8192_SHA256, 22 | AlgorithmID::PS384 => &signature::RSA_PSS_2048_8192_SHA384, 23 | AlgorithmID::PS512 => &signature::RSA_PSS_2048_8192_SHA512, 24 | 25 | _ => unreachable!("algorithm doesn't map to a ring signature verification algorithm"), 26 | } 27 | } 28 | } 29 | 30 | /// Uniquely identifies a specific cryptographic algorithm for signing or verifying tokens 31 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] 32 | #[non_exhaustive] 33 | pub enum AlgorithmID { 34 | /// Unsecured JWT 35 | NONE, 36 | 37 | /// HMAC using SHA-256 38 | HS256, 39 | /// HMAC using SHA-384 40 | HS384, 41 | /// HMAC using SHA-512 42 | HS512, 43 | 44 | /// ECDSA using SHA-256 45 | ES256, 46 | /// ECDSA using SHA-384 47 | ES384, 48 | 49 | /// RSASSA-PKCS1-v1_5 using SHA-256 50 | RS256, 51 | /// RSASSA-PKCS1-v1_5 using SHA-384 52 | RS384, 53 | /// RSASSA-PKCS1-v1_5 using SHA-512 54 | RS512, 55 | 56 | /// RSASSA-PSS using SHA-256 57 | PS256, 58 | /// RSASSA-PSS using SHA-384 59 | PS384, 60 | /// RSASSA-PSS using SHA-512 61 | PS512, 62 | } 63 | 64 | impl From for &'static str { 65 | fn from(id: AlgorithmID) -> Self { 66 | match id { 67 | AlgorithmID::NONE => "none", 68 | 69 | AlgorithmID::HS256 => "HS256", 70 | AlgorithmID::HS384 => "HS384", 71 | AlgorithmID::HS512 => "HS512", 72 | 73 | AlgorithmID::ES256 => "ES256", 74 | AlgorithmID::ES384 => "ES384", 75 | 76 | AlgorithmID::RS256 => "RS256", 77 | AlgorithmID::RS384 => "RS384", 78 | AlgorithmID::RS512 => "RS512", 79 | 80 | AlgorithmID::PS256 => "PS256", 81 | AlgorithmID::PS384 => "PS384", 82 | AlgorithmID::PS512 => "PS512", 83 | } 84 | } 85 | } 86 | 87 | impl std::fmt::Display for AlgorithmID { 88 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 89 | let alg = *self; 90 | let s: &'static str = alg.into(); 91 | write!(f, "{s}") 92 | } 93 | } 94 | 95 | impl FromStr for AlgorithmID { 96 | type Err = Error; 97 | fn from_str(s: &str) -> Result { 98 | match s { 99 | "none" => Ok(AlgorithmID::NONE), 100 | 101 | "HS256" => Ok(AlgorithmID::HS256), 102 | "HS384" => Ok(AlgorithmID::HS384), 103 | "HS512" => Ok(AlgorithmID::HS512), 104 | 105 | "ES256" => Ok(AlgorithmID::ES256), 106 | "ES384" => Ok(AlgorithmID::ES384), 107 | 108 | "RS256" => Ok(AlgorithmID::RS256), 109 | "RS384" => Ok(AlgorithmID::RS384), 110 | "RS512" => Ok(AlgorithmID::RS512), 111 | 112 | "PS256" => Ok(AlgorithmID::PS256), 113 | "PS384" => Ok(AlgorithmID::PS384), 114 | "PS512" => Ok(AlgorithmID::PS512), 115 | 116 | _ => Err(Error::InvalidInput(ErrorDetails::new(format!( 117 | "Unknown algorithm name {s}" 118 | )))), 119 | } 120 | } 121 | } 122 | 123 | impl From for &'static str { 124 | fn from(alg: Algorithm) -> Self { 125 | alg.name() 126 | } 127 | } 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | use super::*; 132 | 133 | #[test] 134 | fn generate_algorithm_enum_from_str() { 135 | assert!(AlgorithmID::from_str("none").is_ok()); 136 | 137 | assert!(AlgorithmID::from_str("HS256").is_ok()); 138 | assert!(AlgorithmID::from_str("HS384").is_ok()); 139 | assert!(AlgorithmID::from_str("HS512").is_ok()); 140 | 141 | assert!(AlgorithmID::from_str("ES256").is_ok()); 142 | assert!(AlgorithmID::from_str("ES384").is_ok()); 143 | 144 | assert!(AlgorithmID::from_str("RS256").is_ok()); 145 | assert!(AlgorithmID::from_str("RS384").is_ok()); 146 | assert!(AlgorithmID::from_str("RS512").is_ok()); 147 | 148 | assert!(AlgorithmID::from_str("PS256").is_ok()); 149 | assert!(AlgorithmID::from_str("PS384").is_ok()); 150 | assert!(AlgorithmID::from_str("PS512").is_ok()); 151 | 152 | assert!(AlgorithmID::from_str("").is_err()); 153 | } 154 | } 155 | 156 | fn ensure_hmac_id(id: AlgorithmID) -> Result<(), Error> { 157 | match id { 158 | AlgorithmID::HS256 => Ok(()), 159 | AlgorithmID::HS384 => Ok(()), 160 | AlgorithmID::HS512 => Ok(()), 161 | _ => Err(Error::AlgorithmMismatch()), 162 | } 163 | } 164 | 165 | fn ensure_ecdsa_id(id: AlgorithmID) -> Result<(), Error> { 166 | match id { 167 | AlgorithmID::ES256 => Ok(()), 168 | AlgorithmID::ES384 => Ok(()), 169 | _ => Err(Error::AlgorithmMismatch()), 170 | } 171 | } 172 | 173 | fn ensure_rsa_id(id: AlgorithmID) -> Result<(), Error> { 174 | match id { 175 | AlgorithmID::RS256 => Ok(()), 176 | AlgorithmID::RS384 => Ok(()), 177 | AlgorithmID::RS512 => Ok(()), 178 | 179 | AlgorithmID::PS256 => Ok(()), 180 | AlgorithmID::PS384 => Ok(()), 181 | AlgorithmID::PS512 => Ok(()), 182 | _ => Err(Error::AlgorithmMismatch()), 183 | } 184 | } 185 | 186 | /// A cryptographic function for signing or verifying a token signature 187 | /// 188 | /// An Algorithm encapsulates one function for signing or verifying tokens. A key 189 | /// or secret only needs to be decoded once so it can be reused cheaply while 190 | /// signing or verifying tokens. The decoded key or secret and `AlgorithmID` are 191 | /// immutable after construction to avoid the chance of being coerced into using 192 | /// the wrong algorithm to sign or verify a token at runtime. 193 | /// 194 | /// Optionally a `kid` Key ID can be assigned to an `Algorithm` to add a strict 195 | /// check that a token's header must include the same `kid` value. This is useful 196 | /// when using an `Algorithm` to represent a single key within a JWKS key set, 197 | /// for example. 198 | /// 199 | #[derive(Debug)] 200 | pub struct Algorithm { 201 | id: AlgorithmID, 202 | kid: Option, 203 | 204 | secret_or_key: SecretOrKey, 205 | } 206 | 207 | impl Algorithm { 208 | /// Returns the `AlgorithmID` that was used to construct the `Algorithm` 209 | pub fn id(&self) -> AlgorithmID { 210 | self.id 211 | } 212 | 213 | /// Returns the algorithm name as standardized in [RFC 7518](https://tools.ietf.org/html/rfc7518) 214 | pub fn name(&self) -> &'static str { 215 | self.id.into() 216 | } 217 | 218 | /// Optionally if a `kid` is associated with an algorithm there will be an extra 219 | /// verification that a token's kid matches the one associated with the `Algorithm` 220 | pub fn set_kid(&mut self, kid: impl Into) { 221 | self.kid = Some(kid.into()); 222 | } 223 | 224 | /// Returns a reference to any associated `kid` set via `set_kid()` 225 | pub fn kid(&self) -> Option<&str> { 226 | match &self.kid { 227 | Some(string) => Some(string.as_ref()), 228 | None => None, 229 | } 230 | } 231 | 232 | /// Constructs a NOP algorithm for use with unsecured (unsigned) tokens 233 | pub fn new_unsecured() -> Result { 234 | Ok(Algorithm { 235 | id: AlgorithmID::NONE, 236 | kid: None, 237 | secret_or_key: SecretOrKey::None, 238 | }) 239 | } 240 | 241 | /// Constructs a symmetric HMAC algorithm based on a given secret 242 | /// 243 | /// This algorithm may be used for signing and/or verifying signatures 244 | pub fn new_hmac(id: AlgorithmID, secret: impl Into>) -> Result { 245 | ensure_hmac_id(id)?; 246 | 247 | Ok(Algorithm { 248 | id, 249 | kid: None, 250 | secret_or_key: SecretOrKey::Secret(secret.into()), 251 | }) 252 | } 253 | 254 | /// Constructs a symmetric HMAC algorithm based on a given base64 secret 255 | /// 256 | /// This is a convenience api in case the secret you're using is base64 encoded 257 | /// 258 | /// This algorithm may be used for signing and/or verifying signatures 259 | pub fn new_hmac_b64(id: AlgorithmID, secret: impl AsRef) -> Result { 260 | ensure_hmac_id(id)?; 261 | 262 | Ok(Algorithm { 263 | id, 264 | kid: None, 265 | secret_or_key: SecretOrKey::Secret(b64_decode(secret.as_ref())?), 266 | }) 267 | } 268 | 269 | /// Constructs an ECDSA algorithm based on a PEM format private key 270 | /// 271 | /// This algorithm may only be used for signing tokens 272 | pub fn new_ecdsa_pem_signer(id: AlgorithmID, key: &[u8]) -> Result { 273 | ensure_ecdsa_id(id)?; 274 | 275 | let ring_alg = id.into(); 276 | let pem_key = PemEncodedKey::new(key)?; 277 | let signing_key = 278 | signature::EcdsaKeyPair::from_pkcs8(ring_alg, pem_key.as_ec_private_key()?).map_err( 279 | |e| { 280 | Error::InvalidInput(ErrorDetails::map( 281 | "Failed to create ECDSA key pair for signing", 282 | Box::new(e), 283 | )) 284 | }, 285 | )?; 286 | 287 | Ok(Algorithm { 288 | id, 289 | kid: None, 290 | secret_or_key: SecretOrKey::EcdsaKeyPair(Box::from(signing_key)), 291 | }) 292 | } 293 | 294 | /// Constructs an ECDSA algorithm based on a PEM format public key 295 | /// 296 | /// This algorithm may only be used for verifying tokens 297 | pub fn new_ecdsa_pem_verifier(id: AlgorithmID, key: &[u8]) -> Result { 298 | ensure_ecdsa_id(id)?; 299 | 300 | let pem_key = PemEncodedKey::new(key)?; 301 | let ec_pub_key = pem_key.as_ec_public_key()?; 302 | 303 | Ok(Algorithm { 304 | id, 305 | kid: None, 306 | secret_or_key: SecretOrKey::EcdsaUnparsedKey(ec_pub_key.to_vec()), 307 | }) 308 | } 309 | 310 | /// Constructs an RSA algorithm based on a PEM format private key 311 | /// 312 | /// This algorithm may only be used for signing tokens 313 | pub fn new_rsa_pem_signer(id: AlgorithmID, key: &[u8]) -> Result { 314 | ensure_rsa_id(id)?; 315 | 316 | let pem_key = PemEncodedKey::new(key)?; 317 | let key_pair = 318 | signature::RsaKeyPair::from_der(pem_key.as_rsa_private_key()?).map_err(|e| { 319 | Error::InvalidInput(ErrorDetails::map( 320 | "Failed to create RSA key for signing", 321 | Box::new(e), 322 | )) 323 | })?; 324 | 325 | Ok(Algorithm { 326 | id, 327 | kid: None, 328 | secret_or_key: SecretOrKey::RsaKeyPair(Box::from(key_pair)), 329 | }) 330 | } 331 | 332 | /// Constructs an RSA algorithm based on a PEM format public key 333 | /// 334 | /// This algorithm may only be used for verifying tokens 335 | pub fn new_rsa_pem_verifier(id: AlgorithmID, key: &[u8]) -> Result { 336 | ensure_rsa_id(id)?; 337 | 338 | let pem_key = PemEncodedKey::new(key)?; 339 | let rsa_pub_key = pem_key.as_rsa_public_key()?; 340 | 341 | Ok(Algorithm { 342 | id, 343 | kid: None, 344 | secret_or_key: SecretOrKey::RsaUnparsedKey(rsa_pub_key.to_vec()), 345 | }) 346 | } 347 | 348 | /// Constructs an RSA algorithm based on modulus (n) and exponent (e) components 349 | /// 350 | /// In some situations (such as JWKS key sets), a public RSA key may be 351 | /// described in terms of (base64 encoded) modulus and exponent values. 352 | /// 353 | /// This algorithm may only be used for verifying tokens 354 | pub fn new_rsa_n_e_b64_verifier( 355 | id: AlgorithmID, 356 | n_b64: &str, 357 | e_b64: &str, 358 | ) -> Result { 359 | ensure_rsa_id(id)?; 360 | 361 | let n = BigUint::from_bytes_be(&b64_decode(n_b64)?).to_bytes_be(); 362 | let e = BigUint::from_bytes_be(&b64_decode(e_b64)?).to_bytes_be(); 363 | 364 | Ok(Algorithm { 365 | id, 366 | kid: None, 367 | secret_or_key: SecretOrKey::RsaParameters(n, e), 368 | }) 369 | } 370 | 371 | /// Lower-level api that can be used to verify a signature for a given message 372 | pub fn verify( 373 | &self, 374 | kid: Option<&str>, 375 | message: impl AsRef, 376 | signature: impl AsRef, 377 | ) -> Result<(), Error> { 378 | // We need an Option(&str) instead of Option(String) 379 | let kid_matches = match &self.kid { 380 | Some(string) => kid == Some(string.as_ref()), 381 | None => true, 382 | }; 383 | if !kid_matches { 384 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 385 | "'kid' ({:?}) didn't match ID ({:?}) associated with Algorithm", 386 | kid, self.kid 387 | )))); 388 | } 389 | 390 | match self.id { 391 | AlgorithmID::NONE => { 392 | if signature.as_ref() == "" { 393 | Ok(()) 394 | } else { 395 | Err(Error::InvalidSignature()) 396 | } 397 | } 398 | AlgorithmID::HS256 | AlgorithmID::HS384 | AlgorithmID::HS512 => hmac::verify( 399 | self.id, 400 | &self.secret_or_key, 401 | message.as_ref(), 402 | signature.as_ref(), 403 | ), 404 | AlgorithmID::ES256 | AlgorithmID::ES384 => ecdsa::verify( 405 | self.id, 406 | &self.secret_or_key, 407 | message.as_ref(), 408 | signature.as_ref(), 409 | ), 410 | AlgorithmID::RS256 411 | | AlgorithmID::RS384 412 | | AlgorithmID::RS512 413 | | AlgorithmID::PS256 414 | | AlgorithmID::PS384 415 | | AlgorithmID::PS512 => rsa::verify( 416 | self.id, 417 | &self.secret_or_key, 418 | message.as_ref(), 419 | signature.as_ref(), 420 | ), 421 | } 422 | } 423 | 424 | /// Lower-level api that can be used to calculate a signature for a message 425 | pub fn sign(&self, message: &str) -> Result { 426 | match self.id { 427 | AlgorithmID::NONE => Ok("".to_owned()), 428 | AlgorithmID::HS256 | AlgorithmID::HS384 | AlgorithmID::HS512 => { 429 | hmac::sign(self.id, &self.secret_or_key, message) 430 | } 431 | AlgorithmID::ES256 | AlgorithmID::ES384 => { 432 | ecdsa::sign(self.id, &self.secret_or_key, message) 433 | } 434 | AlgorithmID::RS256 435 | | AlgorithmID::RS384 436 | | AlgorithmID::RS512 437 | | AlgorithmID::PS256 438 | | AlgorithmID::PS384 439 | | AlgorithmID::PS512 => rsa::sign(self.id, &self.secret_or_key, message), 440 | } 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/crypto/ecdsa.rs: -------------------------------------------------------------------------------- 1 | use ring::{rand, signature}; 2 | 3 | use crate::crypto::algorithm::AlgorithmID; 4 | use crate::crypto::SecretOrKey; 5 | use crate::error::{Error, ErrorDetails}; 6 | use crate::raw::*; 7 | 8 | impl From for &signature::EcdsaSigningAlgorithm { 9 | fn from(alg: AlgorithmID) -> Self { 10 | match alg { 11 | AlgorithmID::ES256 => &signature::ECDSA_P256_SHA256_FIXED_SIGNING, 12 | AlgorithmID::ES384 => &signature::ECDSA_P384_SHA384_FIXED_SIGNING, 13 | _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), 14 | } 15 | } 16 | } 17 | 18 | pub fn sign( 19 | _algorithm: AlgorithmID, 20 | secret_or_key: &SecretOrKey, 21 | message: &str, 22 | ) -> Result { 23 | match secret_or_key { 24 | SecretOrKey::EcdsaKeyPair(signing_key) => { 25 | let rng = rand::SystemRandom::new(); 26 | let out = signing_key.sign(&rng, message.as_bytes()).map_err(|e| { 27 | Error::InvalidInput(ErrorDetails::map( 28 | "Failed to sign JWT with ECDSA", 29 | Box::new(e), 30 | )) 31 | })?; 32 | Ok(b64_encode(out.as_ref())) 33 | } 34 | _ => Err(Error::InvalidInput(ErrorDetails::new( 35 | "Missing ECDSA private key for signing", 36 | ))), 37 | } 38 | } 39 | 40 | pub fn verify( 41 | algorithm: AlgorithmID, 42 | secret_or_key: &SecretOrKey, 43 | message: &str, 44 | signature: &str, 45 | ) -> Result<(), Error> { 46 | let ring_alg = algorithm.into(); 47 | match secret_or_key { 48 | SecretOrKey::EcdsaUnparsedKey(key) => { 49 | let public_key = signature::UnparsedPublicKey::new(ring_alg, key); 50 | let signature_bytes = b64_decode(signature)?; 51 | public_key 52 | .verify(message.as_bytes(), &signature_bytes) 53 | .map_err(|_| Error::InvalidSignature()) 54 | } 55 | _ => Err(Error::InvalidInput(ErrorDetails::new( 56 | "Missing ECDSA public key for signing", 57 | ))), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/crypto/hmac.rs: -------------------------------------------------------------------------------- 1 | use crate::crypto::algorithm::AlgorithmID; 2 | use crate::crypto::SecretOrKey; 3 | use crate::error::{Error, ErrorDetails}; 4 | use crate::raw::*; 5 | use ring::constant_time::verify_slices_are_equal; 6 | use ring::hmac; 7 | 8 | impl From for hmac::Algorithm { 9 | fn from(alg: AlgorithmID) -> Self { 10 | match alg { 11 | AlgorithmID::HS256 => ring::hmac::HMAC_SHA256, 12 | AlgorithmID::HS384 => ring::hmac::HMAC_SHA384, 13 | AlgorithmID::HS512 => ring::hmac::HMAC_SHA512, 14 | _ => unreachable!("Tried to map HMAC type for a non-HMAC algorithm"), 15 | } 16 | } 17 | } 18 | 19 | pub(crate) fn sign( 20 | alg: AlgorithmID, 21 | secret_or_key: &SecretOrKey, 22 | message: &str, 23 | ) -> Result { 24 | match secret_or_key { 25 | SecretOrKey::Secret(key) => { 26 | let ring_alg = alg.into(); 27 | let digest = hmac::sign(&hmac::Key::new(ring_alg, key), message.as_bytes()); 28 | Ok(b64_encode(digest.as_ref())) 29 | } 30 | _ => Err(Error::InvalidInput(ErrorDetails::new( 31 | "Missing secret for HMAC signing", 32 | ))), 33 | } 34 | } 35 | 36 | pub fn verify( 37 | algorithm: AlgorithmID, 38 | secret_or_key: &SecretOrKey, 39 | message: &str, 40 | signature: &str, 41 | ) -> Result<(), Error> { 42 | // we just re-sign the message with the key and compare if they are equal 43 | let signed = sign(algorithm, secret_or_key, message)?; 44 | verify_slices_are_equal(signature.as_bytes(), signed.as_ref()) 45 | .map_err(|_| Error::InvalidSignature()) 46 | } 47 | -------------------------------------------------------------------------------- /src/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | use ring::signature; 2 | 3 | pub(crate) mod algorithm; 4 | pub(crate) mod ecdsa; 5 | pub(crate) mod hmac; 6 | pub(crate) mod rsa; 7 | 8 | #[derive(Debug)] 9 | pub enum SecretOrKey { 10 | // Unsecured 11 | None, 12 | 13 | // HMAC 14 | Secret(Vec), 15 | 16 | // ECDSA 17 | EcdsaKeyPair(Box), 18 | EcdsaUnparsedKey(Vec), 19 | 20 | // RSA 21 | RsaKeyPair(Box), 22 | RsaUnparsedKey(Vec), 23 | RsaParameters(Vec, Vec), // (n, e) 24 | } 25 | -------------------------------------------------------------------------------- /src/crypto/rsa.rs: -------------------------------------------------------------------------------- 1 | use ring::{rand, signature}; 2 | 3 | use crate::crypto::algorithm::AlgorithmID; 4 | use crate::crypto::SecretOrKey; 5 | use crate::error::{Error, ErrorDetails}; 6 | use crate::raw::*; 7 | 8 | impl From for &signature::RsaParameters { 9 | fn from(alg: AlgorithmID) -> Self { 10 | match alg { 11 | AlgorithmID::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256, 12 | AlgorithmID::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384, 13 | AlgorithmID::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512, 14 | AlgorithmID::PS256 => &signature::RSA_PSS_2048_8192_SHA256, 15 | AlgorithmID::PS384 => &signature::RSA_PSS_2048_8192_SHA384, 16 | AlgorithmID::PS512 => &signature::RSA_PSS_2048_8192_SHA512, 17 | _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), 18 | } 19 | } 20 | } 21 | impl From for &dyn signature::RsaEncoding { 22 | fn from(alg: AlgorithmID) -> Self { 23 | match alg { 24 | AlgorithmID::RS256 => &signature::RSA_PKCS1_SHA256, 25 | AlgorithmID::RS384 => &signature::RSA_PKCS1_SHA384, 26 | AlgorithmID::RS512 => &signature::RSA_PKCS1_SHA512, 27 | AlgorithmID::PS256 => &signature::RSA_PSS_SHA256, 28 | AlgorithmID::PS384 => &signature::RSA_PSS_SHA384, 29 | AlgorithmID::PS512 => &signature::RSA_PSS_SHA512, 30 | _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), 31 | } 32 | } 33 | } 34 | 35 | pub fn sign( 36 | algorithm: AlgorithmID, 37 | secret_or_key: &SecretOrKey, 38 | message: &str, 39 | ) -> Result { 40 | let ring_alg = algorithm.into(); 41 | 42 | match secret_or_key { 43 | SecretOrKey::RsaKeyPair(key_pair) => { 44 | let mut signature = vec![0; key_pair.public_modulus_len()]; 45 | let rng = rand::SystemRandom::new(); 46 | key_pair 47 | .sign(ring_alg, &rng, message.as_bytes(), &mut signature) 48 | .map_err(|e| { 49 | Error::InvalidInput(ErrorDetails::map( 50 | "Failed to sign JWT with RSA", 51 | Box::new(e), 52 | )) 53 | })?; 54 | 55 | Ok(b64_encode(&signature)) 56 | } 57 | _ => Err(Error::InvalidInput(ErrorDetails::new( 58 | "Missing RSA private key for signing", 59 | ))), 60 | } 61 | } 62 | 63 | pub fn verify( 64 | algorithm: AlgorithmID, 65 | secret_or_key: &SecretOrKey, 66 | message: &str, 67 | signature: &str, 68 | ) -> Result<(), Error> { 69 | match secret_or_key { 70 | SecretOrKey::RsaUnparsedKey(key) => { 71 | let ring_alg = algorithm.into(); 72 | let public_key = signature::UnparsedPublicKey::new(ring_alg, key); 73 | let signature_bytes = b64_decode(signature)?; 74 | public_key 75 | .verify(message.as_bytes(), &signature_bytes) 76 | .map_err(|_| Error::InvalidSignature()) 77 | } 78 | SecretOrKey::RsaParameters(n, e) => { 79 | let rsa_params = algorithm.into(); 80 | let pubkey = signature::RsaPublicKeyComponents { n, e }; 81 | let signature_bytes = b64_decode(signature)?; 82 | pubkey 83 | .verify(rsa_params, message.as_ref(), &signature_bytes) 84 | .map_err(|_| Error::InvalidSignature()) 85 | } 86 | _ => Err(Error::InvalidInput(ErrorDetails::new( 87 | "Missing RSA public key for verifying", 88 | ))), 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/encode.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::Serialize; 2 | 3 | use crate::crypto::algorithm::Algorithm; 4 | use crate::error::Error; 5 | use crate::raw::*; 6 | 7 | /// Encodes a Json Web Token 8 | /// 9 | /// For example, to encode and sign a token with a symmetric secret: 10 | /// ```rust 11 | /// # use serde_json::json; 12 | /// # use serde_json::value::Value; 13 | /// # use jsonwebtokens as jwt; 14 | /// # use jwt::{Algorithm, AlgorithmID, Verifier}; 15 | /// # fn main() -> Result<(), Box> { 16 | /// let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret")?; 17 | /// let header = json!({ "alg": alg.name() }); 18 | /// let claims = json!({ "foo": "bar" }); 19 | /// let token = jwt::encode(&header, &claims, &alg)?; 20 | /// # Ok(()) 21 | /// # } 22 | /// ``` 23 | /// 24 | /// Or to encode and sign a token with an RSA private key: 25 | /// ```rust 26 | /// # use serde_json::json; 27 | /// # use serde_json::value::Value; 28 | /// # use jsonwebtokens as jwt; 29 | /// # use jwt::{Algorithm, AlgorithmID, Verifier}; 30 | /// # fn main() -> Result<(), Box> { 31 | /// let pem_data = include_bytes!("../tests/rsa/private_rsa_key_pkcs1.pem"); 32 | /// let alg = Algorithm::new_rsa_pem_signer(AlgorithmID::RS256, pem_data)?; 33 | /// let header = json!({ "alg": alg.name() }); 34 | /// let claims = json!({ "foo": "bar" }); 35 | /// let token = jwt::encode(&header, &claims, &alg)?; 36 | /// # Ok(()) 37 | /// # } 38 | /// ``` 39 | pub fn encode( 40 | header: &H, 41 | claims: &C, 42 | algorithm: &Algorithm, 43 | ) -> Result { 44 | let encoded_header = b64_encode_part(&header)?; 45 | let encoded_claims = b64_encode_part(&claims)?; 46 | let message = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); 47 | let signature = algorithm.sign(&message)?; 48 | Ok([message, signature].join(".")) 49 | } 50 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | 4 | #[derive(Debug)] 5 | #[non_exhaustive] 6 | pub struct ErrorDetails { 7 | desc: String, 8 | src: Option>, 9 | } 10 | 11 | impl ErrorDetails { 12 | pub fn new(desc: impl Into) -> ErrorDetails { 13 | ErrorDetails { 14 | desc: desc.into(), 15 | src: None, 16 | } 17 | } 18 | 19 | pub fn map( 20 | desc: impl Into, 21 | src: Box, 22 | ) -> ErrorDetails { 23 | ErrorDetails { 24 | desc: desc.into(), 25 | src: Some(src), 26 | } 27 | } 28 | } 29 | 30 | impl From for ErrorDetails { 31 | fn from(s: String) -> Self { 32 | ErrorDetails { desc: s, src: None } 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | #[non_exhaustive] 38 | pub enum Error { 39 | /// Any of: invalid key data, malformed data for encoding, base864/utf8 decode/encode errors 40 | InvalidInput(ErrorDetails), 41 | 42 | /// The alg found in the token header didn't match the given algorithm 43 | AlgorithmMismatch(), 44 | 45 | /// The token's signature was not validated 46 | InvalidSignature(), 47 | 48 | /// The token expired at this time (unix epoch timestamp) 49 | TokenExpiredAt(u64), 50 | 51 | /// Any of: header.payload.signature split error, json parser error, header or claim validation error 52 | MalformedToken(ErrorDetails), 53 | } 54 | 55 | impl StdError for Error { 56 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 57 | match self { 58 | Error::InvalidInput(ErrorDetails { src: Some(s), .. }) => Some(s.as_ref()), 59 | Error::AlgorithmMismatch() => None, 60 | Error::InvalidSignature() => None, 61 | Error::TokenExpiredAt(_) => None, 62 | Error::MalformedToken(ErrorDetails { src: Some(s), .. }) => Some(s.as_ref()), 63 | _ => None, 64 | } 65 | } 66 | } 67 | 68 | impl std::fmt::Display for Error { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | match self { 71 | Error::InvalidInput(details) => write!(f, "Invalid Input: {}", details.desc), 72 | Error::AlgorithmMismatch() => write!(f, "JWT Algorithm Mismatch"), 73 | Error::InvalidSignature() => write!(f, "JWT Signature Invalid"), 74 | Error::TokenExpiredAt(when) => write!(f, "JWT token expired at {when}"), 75 | Error::MalformedToken(details) => write!(f, "JWT claims invalid: {}", details.desc), 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | 3 | mod verifier; 4 | pub use verifier::*; 5 | 6 | mod crypto; 7 | pub use crypto::algorithm::{Algorithm, AlgorithmID}; 8 | 9 | mod pem; 10 | 11 | pub mod raw; 12 | 13 | mod encode; 14 | pub use encode::encode; 15 | 16 | /// For lower-level APIs to return decoded header and claim values 17 | pub struct TokenData { 18 | pub header: serde_json::value::Value, 19 | pub claims: serde_json::value::Value, 20 | 21 | #[doc(hidden)] 22 | pub _extensible: (), 23 | } 24 | -------------------------------------------------------------------------------- /src/pem/decoder.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, ErrorDetails}; 2 | 3 | /// Supported PEM files for EC and RSA Public and Private Keys 4 | #[derive(Debug, PartialEq)] 5 | enum PemType { 6 | EcPublic, 7 | EcPrivate, 8 | RsaPublic, 9 | RsaPrivate, 10 | } 11 | 12 | #[derive(Debug, PartialEq)] 13 | enum Standard { 14 | // Only for RSA 15 | Pkcs1, 16 | // Only for EC 17 | Sec1, 18 | // RSA/EC 19 | Pkcs8, 20 | } 21 | 22 | #[derive(Debug, PartialEq)] 23 | enum Classification { 24 | Ec, 25 | Rsa, 26 | } 27 | 28 | /// The return type of a successful PEM encoded key with `decode_pem` 29 | /// 30 | /// This struct gives a way to parse a string to a key for our use. 31 | /// A struct is necessary as it provides the lifetime of the key 32 | /// 33 | /// PEM public private keys are encoded PKCS#1 or PKCS#8 34 | /// You will find that with PKCS#8 RSA keys that the PKCS#1 content 35 | /// is embedded inside. This is what is provided to ring via `Key::Der` 36 | /// For EC keys, they are always PKCS#8 on the outside but like RSA keys 37 | /// EC keys contain a section within that ultimately has the configuration 38 | /// that ring uses. 39 | /// Documentation about these formats is at 40 | /// PKCS#1: https://tools.ietf.org/html/rfc8017 41 | /// PKCS#8: https://tools.ietf.org/html/rfc5958 42 | #[derive(Debug)] 43 | pub(crate) struct PemEncodedKey { 44 | content: Vec, 45 | asn1: Vec, 46 | pem_type: PemType, 47 | standard: Standard, 48 | } 49 | 50 | impl PemEncodedKey { 51 | /// Read the PEM file for later key use 52 | pub fn new(input: &[u8]) -> Result { 53 | match pem::parse(input) { 54 | Ok(content) => { 55 | let pem_contents = content.contents; 56 | let asn1_content = match simple_asn1::from_der(pem_contents.as_slice()) { 57 | Ok(asn1) => asn1, 58 | Err(e) => { 59 | return Err(Error::InvalidInput(ErrorDetails::map( 60 | "Failed to parse PEM file", 61 | Box::new(e), 62 | ))) 63 | } 64 | }; 65 | 66 | match content.tag.as_ref() { 67 | // This handles a PKCS#1 RSA Private key 68 | "RSA PRIVATE KEY" => Ok(PemEncodedKey { 69 | content: pem_contents, 70 | asn1: asn1_content, 71 | pem_type: PemType::RsaPrivate, 72 | standard: Standard::Pkcs1, 73 | }), 74 | "RSA PUBLIC KEY" => Ok(PemEncodedKey { 75 | content: pem_contents, 76 | asn1: asn1_content, 77 | pem_type: PemType::RsaPublic, 78 | standard: Standard::Pkcs1, 79 | }), 80 | 81 | // https://security.stackexchange.com/questions/84327/converting-ecc-private-key-to-pkcs1-format 82 | // "there is no such thing as a "PKCS#1 format" for elliptic curve (EC) keys" 83 | 84 | // At least recognize the key format specified in SEC 1: Elliptic Curve Cryptography 85 | // Ring doesn't support this so it will lead to an error, but at least we can give a meaningful error 86 | // (There's no equivalent standard for public EC keys) 87 | "EC PRIVATE KEY" => Ok(PemEncodedKey { 88 | content: pem_contents, 89 | asn1: asn1_content, 90 | pem_type: PemType::EcPrivate, 91 | standard: Standard::Sec1, 92 | }), 93 | 94 | // This handles PKCS#8 public & private keys 95 | tag @ "PRIVATE KEY" | tag @ "PUBLIC KEY" | tag @ "CERTIFICATE" => { 96 | match classify_pem(&asn1_content) { 97 | Some(c) => { 98 | let is_private = tag == "PRIVATE KEY"; 99 | let pem_type = match c { 100 | Classification::Ec => { 101 | if is_private { 102 | PemType::EcPrivate 103 | } else { 104 | PemType::EcPublic 105 | } 106 | } 107 | Classification::Rsa => { 108 | if is_private { 109 | PemType::RsaPrivate 110 | } else { 111 | PemType::RsaPublic 112 | } 113 | } 114 | }; 115 | Ok(PemEncodedKey { 116 | content: pem_contents, 117 | asn1: asn1_content, 118 | pem_type, 119 | standard: Standard::Pkcs8, 120 | }) 121 | } 122 | None => Err(Error::InvalidInput(ErrorDetails::new( 123 | "Failed to recognize any OID in PKCS#8 PEM file", 124 | ))), 125 | } 126 | } 127 | 128 | _ => Err(Error::InvalidInput(ErrorDetails::new( 129 | "Failed to recognize PKCS#1 or SEC1 or PKCS#8 markers in PEM file", 130 | ))), 131 | } 132 | } 133 | Err(e) => Err(Error::InvalidInput(ErrorDetails::map( 134 | "Failed to parse PEM file", 135 | Box::new(e), 136 | ))), 137 | } 138 | } 139 | 140 | pub fn as_ec_private_key(&self) -> Result<&[u8], Error> { 141 | match self.standard { 142 | Standard::Pkcs1 => Err(Error::InvalidInput(ErrorDetails::new( 143 | "Expected PKCS#8 PEM markers, not PKCS#1", 144 | ))), 145 | Standard::Sec1 => Err(Error::InvalidInput(ErrorDetails::new( 146 | "Expected PKCS#8 PEM markers, not SEC1", 147 | ))), 148 | Standard::Pkcs8 => match self.pem_type { 149 | PemType::EcPrivate => Ok(self.content.as_slice()), 150 | _ => Err(Error::InvalidInput(ErrorDetails::new( 151 | "PEM key type mismatch (expected EC private key)", 152 | ))), 153 | }, 154 | } 155 | } 156 | 157 | pub fn as_ec_public_key(&self) -> Result<&[u8], Error> { 158 | match self.standard { 159 | Standard::Pkcs1 => Err(Error::InvalidInput(ErrorDetails::new( 160 | "Expected PKCS#8 PEM markers, not PKCS#1", 161 | ))), 162 | Standard::Sec1 => Err(Error::InvalidInput(ErrorDetails::new( 163 | "Expected PKCS#8 PEM markers, not SEC1", 164 | ))), 165 | Standard::Pkcs8 => match self.pem_type { 166 | PemType::EcPublic => extract_first_bitstring(&self.asn1), 167 | _ => Err(Error::InvalidInput(ErrorDetails::new( 168 | "PEM key type mismatch (expected EC public key)", 169 | ))), 170 | }, 171 | } 172 | } 173 | 174 | pub fn as_rsa_public_key(&self) -> Result<&[u8], Error> { 175 | match self.standard { 176 | Standard::Pkcs1 => match self.pem_type { 177 | PemType::RsaPublic => Ok(self.content.as_slice()), 178 | _ => Err(Error::InvalidInput(ErrorDetails::new( 179 | "PEM key type mismatch (expected RSA public key)", 180 | ))), 181 | }, 182 | Standard::Sec1 => Err(Error::InvalidInput(ErrorDetails::new( 183 | "Expected PKCS#1 or PKCS#8 PEM markers, not SEC1", 184 | ))), 185 | Standard::Pkcs8 => match self.pem_type { 186 | PemType::RsaPublic => extract_first_bitstring(&self.asn1), 187 | _ => Err(Error::InvalidInput(ErrorDetails::new( 188 | "PEM key type mismatch (expected RSA public key)", 189 | ))), 190 | }, 191 | } 192 | } 193 | 194 | pub fn as_rsa_private_key(&self) -> Result<&[u8], Error> { 195 | match self.standard { 196 | Standard::Pkcs1 => match self.pem_type { 197 | PemType::RsaPrivate => Ok(self.content.as_slice()), 198 | _ => Err(Error::InvalidInput(ErrorDetails::new( 199 | "PEM key type mismatch (expected RSA private key)", 200 | ))), 201 | }, 202 | Standard::Sec1 => Err(Error::InvalidInput(ErrorDetails::new( 203 | "Expected PKCS#1 or PKCS#8 PEM markers, not SEC1", 204 | ))), 205 | Standard::Pkcs8 => match self.pem_type { 206 | PemType::RsaPrivate => extract_first_bitstring(&self.asn1), 207 | _ => Err(Error::InvalidInput(ErrorDetails::new( 208 | "PEM key type mismatch (expected RSA private key)", 209 | ))), 210 | }, 211 | } 212 | } 213 | } 214 | 215 | // This really just finds and returns the first bitstring or octet string 216 | // Which is the x coordinate for EC public keys 217 | // And the DER contents of an RSA key 218 | // Though PKCS#11 keys shouldn't have anything else. 219 | // It will get confusing with certificates. 220 | fn extract_first_bitstring(asn1: &[simple_asn1::ASN1Block]) -> Result<&[u8], Error> { 221 | for asn1_entry in asn1.iter() { 222 | match asn1_entry { 223 | simple_asn1::ASN1Block::Sequence(_, entries) => { 224 | if let Ok(result) = extract_first_bitstring(entries) { 225 | return Ok(result); 226 | } 227 | } 228 | simple_asn1::ASN1Block::BitString(_, _, value) => { 229 | return Ok(value.as_ref()); 230 | } 231 | simple_asn1::ASN1Block::OctetString(_, value) => { 232 | return Ok(value.as_ref()); 233 | } 234 | _ => (), 235 | } 236 | } 237 | 238 | Err(Error::InvalidInput(ErrorDetails::new( 239 | "Failed to extract ASN.1 bit string", 240 | ))) 241 | } 242 | 243 | /// Find whether this is EC or RSA 244 | fn classify_pem(asn1: &[simple_asn1::ASN1Block]) -> Option { 245 | // These should be constant but the macro requires 246 | // #![feature(const_vec_new)] 247 | let ec_public_key_oid = simple_asn1::oid!(1, 2, 840, 10_045, 2, 1); 248 | let rsa_public_key_oid = simple_asn1::oid!(1, 2, 840, 113_549, 1, 1, 1); 249 | 250 | for asn1_entry in asn1.iter() { 251 | match asn1_entry { 252 | simple_asn1::ASN1Block::Sequence(_, entries) => { 253 | if let Some(classification) = classify_pem(entries) { 254 | return Some(classification); 255 | } 256 | } 257 | simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => { 258 | if oid == ec_public_key_oid { 259 | return Some(Classification::Ec); 260 | } 261 | if oid == rsa_public_key_oid { 262 | return Some(Classification::Rsa); 263 | } 264 | } 265 | _ => {} 266 | } 267 | } 268 | None 269 | } 270 | -------------------------------------------------------------------------------- /src/pem/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod decoder; 2 | -------------------------------------------------------------------------------- /src/raw.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use serde::ser::Serialize; 4 | 5 | use crate::crypto::algorithm::{Algorithm, AlgorithmID}; 6 | use crate::error::{Error, ErrorDetails}; 7 | use crate::TokenData; 8 | 9 | use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; 10 | 11 | pub(crate) fn b64_encode(input: &[u8]) -> String { 12 | URL_SAFE_NO_PAD.encode(input) 13 | } 14 | 15 | pub(crate) fn b64_decode(input: &str) -> Result, Error> { 16 | URL_SAFE_NO_PAD 17 | .decode(input) 18 | .map_err(|e| Error::InvalidInput(ErrorDetails::map("base64 decode failure", Box::new(e)))) 19 | } 20 | 21 | /// Serializes a struct to JSON and encodes it in base64 22 | pub(crate) fn b64_encode_part(input: &T) -> Result { 23 | let json = serde_json::to_string(input).map_err(|e| { 24 | Error::InvalidInput(ErrorDetails::map("json serialize failure", Box::new(e))) 25 | })?; 26 | Ok(b64_encode(json.as_bytes())) 27 | } 28 | 29 | /// Takes the result of a str split and ensure we only get 2 parts 30 | /// Errors if we don't 31 | macro_rules! expect_two { 32 | ($iter:expr) => {{ 33 | let mut i = $iter; 34 | match (i.next(), i.next(), i.next()) { 35 | (Some(first), Some(second), None) => (first, second), 36 | _ => { 37 | return Err(Error::MalformedToken(ErrorDetails::new( 38 | "Failed to split JWT into header.claims.signature parts", 39 | ))) 40 | } 41 | } 42 | }}; 43 | } 44 | 45 | pub struct TokenSlices<'a> { 46 | /// The header and claims (including adjoining '.') but not the last '.' or signature 47 | pub message: &'a str, 48 | 49 | /// Just the trailing signature, no '.' 50 | pub signature: &'a str, 51 | 52 | /// Just the leading header, no '.' 53 | pub header: &'a str, 54 | 55 | /// Just the claims in between the header and signature, no '.'s 56 | pub claims: &'a str, 57 | } 58 | 59 | /// Splits a token that's in the form `"HEADER.CLAIMS.SIGNATURE"` into useful constituent 60 | /// parts for further parsing and validation. 61 | /// 62 | /// For example: 63 | /// ```rust 64 | /// # use jsonwebtokens as jwt; 65 | /// # use jwt::raw::{self, TokenSlices}; 66 | /// # fn main() -> Result<(), Box> { 67 | /// let token = "HEADER.CLAIMS.SIGNATURE"; 68 | /// let TokenSlices {message, signature, header, claims } = raw::split_token(token)?; 69 | /// println!("message: {}", message); 70 | /// println!("signature: {}", signature); 71 | /// println!("header: {}", header); 72 | /// println!("claims: {}", claims); 73 | /// # Ok(()) 74 | /// # } 75 | /// ``` 76 | /// will output: 77 | /// ```bash 78 | /// message: HEADER.CLAIMS 79 | /// signature: SIGNATURE 80 | /// header: HEADER 81 | /// claims: CLAIMS 82 | /// ``` 83 | /// 84 | /// After splitting a token, it can be further processed by using 85 | /// [raw::verify_signature_only](raw::verify_signature_only) to check the token's 86 | /// signature, then [raw::decode_json_token_slice](raw::decode_json_token_slice) 87 | /// can be used to decode the header and/or the claims, and finally the 88 | /// [Verifier::verify_claims_only](Verifier::verify_claims_only) api can be used 89 | /// to check the claims. 90 | /// 91 | pub fn split_token(token: &str) -> Result { 92 | let (signature, message) = expect_two!(token.rsplitn(2, '.')); 93 | let (header, claims) = expect_two!(message.splitn(2, '.')); 94 | 95 | Ok(TokenSlices { 96 | message, 97 | signature, 98 | header, 99 | claims, 100 | }) 101 | } 102 | 103 | /// Decodes a base64 encoded token header or claims and deserializes from JSON 104 | /// 105 | /// For example to just decode a token's header: 106 | /// ```rust 107 | /// # use jsonwebtokens as jwt; 108 | /// # use jwt::raw::{self, TokenSlices, decode_json_token_slice}; 109 | /// # fn main() -> Result<(), Box> { 110 | /// # let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 111 | /// let TokenSlices {header, .. } = raw::split_token(token)?; 112 | /// let header = raw::decode_json_token_slice(header)?; 113 | /// # Ok(()) 114 | /// # } 115 | /// ``` 116 | /// or similarly just a token's claims: 117 | /// ```rust 118 | /// # use jsonwebtokens as jwt; 119 | /// # use jwt::raw::{self, TokenSlices, decode_json_token_slice}; 120 | /// # fn main() -> Result<(), Box> { 121 | /// # let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 122 | /// let TokenSlices {claims, .. } = raw::split_token(token)?; 123 | /// let claims = raw::decode_json_token_slice(claims)?; 124 | /// # Ok(()) 125 | /// # } 126 | /// ``` 127 | pub fn decode_json_token_slice( 128 | encoded_slice: impl AsRef, 129 | ) -> Result { 130 | let s = String::from_utf8(b64_decode(encoded_slice.as_ref())?) 131 | .map_err(|e| Error::InvalidInput(ErrorDetails::map("utf8 decode failure", Box::new(e))))?; 132 | let value = serde_json::from_str(&s) 133 | .map_err(|e| Error::MalformedToken(ErrorDetails::map("json parse failure", Box::new(e))))?; 134 | Ok(value) 135 | } 136 | 137 | /// Decodes just the header of a token 138 | /// 139 | /// This just adds a little convenience over doing: 140 | /// ```rust 141 | /// # use jsonwebtokens as jwt; 142 | /// # use jwt::raw::{self, TokenSlices, decode_json_token_slice}; 143 | /// # fn main() -> Result<(), Box> { 144 | /// # let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 145 | /// let TokenSlices {header, .. } = raw::split_token(token)?; 146 | /// let header = raw::decode_json_token_slice(header)?; 147 | /// # Ok(()) 148 | /// # } 149 | /// ``` 150 | pub fn decode_header_only(token: impl AsRef) -> Result { 151 | let TokenSlices { header, .. } = split_token(token.as_ref())?; 152 | decode_json_token_slice(header) 153 | } 154 | 155 | /// Decodes the header and claims of a token without any verification checks 156 | /// 157 | /// This decodes the header and claims of a token without verifying the token's 158 | /// signature and without verifying any of the claims. 159 | /// 160 | /// This just adds a little convenience over doing: 161 | /// ```rust 162 | /// # use jsonwebtokens as jwt; 163 | /// # use jwt::raw::{self, TokenSlices, decode_json_token_slice}; 164 | /// # fn main() -> Result<(), Box> { 165 | /// # let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 166 | /// let TokenSlices {header, claims, .. } = raw::split_token(token)?; 167 | /// let header = raw::decode_json_token_slice(header)?; 168 | /// let claims = raw::decode_json_token_slice(claims)?; 169 | /// # Ok(()) 170 | /// # } 171 | /// ``` 172 | pub fn decode_only(token: impl AsRef) -> Result { 173 | let TokenSlices { header, claims, .. } = split_token(token.as_ref())?; 174 | let header = decode_json_token_slice(header)?; 175 | let claims = decode_json_token_slice(claims)?; 176 | Ok(TokenData { 177 | header, 178 | claims, 179 | _extensible: (), 180 | }) 181 | } 182 | 183 | /// Just verifies the signature of a token's message 184 | /// 185 | /// For example: 186 | /// ```rust 187 | /// # use jsonwebtokens as jwt; 188 | /// # use jwt::raw::{self, TokenSlices, decode_json_token_slice}; 189 | /// # use jwt::{AlgorithmID, Algorithm}; 190 | /// # fn main() -> Result<(), Box> { 191 | /// # let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 192 | /// let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 193 | /// let TokenSlices {message, signature, header, .. } = raw::split_token(token)?; 194 | /// let header = raw::decode_json_token_slice(header)?; 195 | /// raw::verify_signature_only(&header, message, signature, &alg)?; 196 | /// # Ok(()) 197 | /// # } 198 | /// ``` 199 | pub fn verify_signature_only( 200 | header: &serde_json::value::Value, 201 | message: impl AsRef, 202 | signature: impl AsRef, 203 | algorithm: &Algorithm, 204 | ) -> Result<(), Error> { 205 | match header.get("alg") { 206 | Some(serde_json::value::Value::String(alg)) => { 207 | let alg = AlgorithmID::from_str(alg)?; 208 | 209 | if alg != algorithm.id() { 210 | return Err(Error::AlgorithmMismatch()); 211 | } 212 | 213 | // An Algorithm may relate to a specific 'kid' which we verify... 214 | let kid = match header.get("kid") { 215 | Some(serde_json::value::Value::String(k)) => Some(k.as_ref()), 216 | Some(_) => { 217 | return Err(Error::MalformedToken(ErrorDetails::new( 218 | "Non-string 'kid' found", 219 | ))) 220 | } 221 | None => None, 222 | }; 223 | 224 | algorithm.verify(kid, message, signature)?; 225 | } 226 | _ => return Err(Error::AlgorithmMismatch()), 227 | } 228 | 229 | Ok(()) 230 | } 231 | -------------------------------------------------------------------------------- /src/verifier.rs: -------------------------------------------------------------------------------- 1 | use serde_json::value::Value; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::sync::Arc; 4 | use std::time::SystemTime; 5 | 6 | #[cfg(feature = "matching")] 7 | use regex::Regex; 8 | #[cfg(feature = "matching")] 9 | use std::fmt; 10 | #[cfg(feature = "matching")] 11 | use std::hash::{Hash, Hasher}; 12 | #[cfg(feature = "matching")] 13 | use std::ops::Deref; 14 | 15 | use crate::crypto::algorithm::Algorithm; 16 | use crate::error::{Error, ErrorDetails}; 17 | use crate::raw::*; 18 | use crate::TokenData; 19 | 20 | // Regex doesn't implement PartialEq, Eq or Hash so we nee a wrapper... 21 | #[cfg(feature = "matching")] 22 | #[derive(Debug, Clone)] 23 | struct Pattern(Regex); 24 | 25 | #[cfg(feature = "matching")] 26 | impl Deref for Pattern { 27 | type Target = Regex; 28 | fn deref(&self) -> &Self::Target { 29 | &self.0 30 | } 31 | } 32 | #[cfg(feature = "matching")] 33 | impl fmt::Display for Pattern { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | self.0.fmt(f) 36 | } 37 | } 38 | #[cfg(feature = "matching")] 39 | impl PartialEq for Pattern { 40 | fn eq(&self, other: &Self) -> bool { 41 | self.as_str() == other.as_str() 42 | } 43 | } 44 | #[cfg(feature = "matching")] 45 | impl Eq for Pattern {} 46 | #[cfg(feature = "matching")] 47 | impl Hash for Pattern { 48 | fn hash(&self, state: &mut H) { 49 | self.0.as_str().hash(state); 50 | } 51 | } 52 | 53 | #[derive(Clone)] 54 | struct VerifierClosure { 55 | func: Arc bool>, 56 | } 57 | impl Eq for VerifierClosure {} 58 | impl PartialEq for VerifierClosure { 59 | fn eq(&self, other: &Self) -> bool { 60 | // The libs team has resolved to update Arc::ptr_eq so that it doesn't compare 61 | // meta data for the wrapped value (such as a dyn trait vtable): 62 | // https://github.com/rust-lang/rust/issues/103763 63 | // 64 | // In the meantime we can silence this clippy suggestion since the vtable is 65 | // benign in this case anyway: 66 | // https://github.com/rust-lang/rust-clippy/issues/6524 67 | #[allow(clippy::vtable_address_comparisons)] 68 | Arc::ptr_eq(&self.func, &other.func) 69 | } 70 | } 71 | impl std::fmt::Debug for VerifierClosure { 72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 73 | write!(f, "VerifierClosure") 74 | } 75 | } 76 | 77 | #[derive(Clone, PartialEq, Eq, Debug)] 78 | enum VerifierKind { 79 | Closure(VerifierClosure), 80 | 81 | StringConstant(String), 82 | StringSet(HashSet), 83 | 84 | #[cfg(feature = "matching")] 85 | StringPattern(Pattern), 86 | #[cfg(feature = "matching")] 87 | StringPatternSet(HashSet), 88 | 89 | StringOrArrayContains(String), 90 | } 91 | 92 | /// Immutable requirements for checking token claims 93 | #[derive(Clone)] 94 | pub struct Verifier { 95 | leeway: u32, 96 | ignore_exp: bool, 97 | ignore_nbf: bool, 98 | ignore_iat: bool, 99 | 100 | claim_verifiers: HashMap, 101 | } 102 | 103 | impl Verifier { 104 | /// Start constructing a Verifier and configuring what claims should be verified. 105 | pub fn create() -> VerifierBuilder { 106 | VerifierBuilder::new() 107 | } 108 | 109 | /// Verifies a token's claims but does not look at any header or verify any signature 110 | pub fn verify_claims_only( 111 | &self, 112 | claims: &serde_json::value::Value, 113 | time_now: u64, 114 | ) -> Result<(), Error> { 115 | let claims = match claims { 116 | serde_json::value::Value::Object(map) => map, 117 | _ => { 118 | return Err(Error::MalformedToken(ErrorDetails::new( 119 | "Expected claims to be a JSON object", 120 | ))) 121 | } 122 | }; 123 | 124 | if !self.ignore_iat { 125 | match claims.get("iat") { 126 | Some(serde_json::value::Value::Number(number)) => { 127 | if let Some(iat) = number.as_u64() { 128 | if iat > time_now + (self.leeway as u64) { 129 | return Err(Error::MalformedToken(ErrorDetails::new( 130 | "Issued with a future 'iat' time", 131 | ))); 132 | } 133 | } else { 134 | return Err(Error::MalformedToken(ErrorDetails::new( 135 | "Failed to parse 'iat' as an integer", 136 | ))); 137 | } 138 | } 139 | Some(_) => { 140 | return Err(Error::MalformedToken(ErrorDetails::new( 141 | "Given 'iat' not a number", 142 | ))); 143 | } 144 | None => {} 145 | } 146 | } 147 | 148 | if !self.ignore_nbf { 149 | match claims.get("nbf") { 150 | Some(serde_json::value::Value::Number(number)) => { 151 | if let Some(nbf) = number.as_u64() { 152 | if nbf > time_now + (self.leeway as u64) { 153 | return Err(Error::MalformedToken(ErrorDetails::new( 154 | "Time is before 'nbf'", 155 | ))); 156 | } 157 | } else { 158 | return Err(Error::MalformedToken(ErrorDetails::new( 159 | "Failed to parse 'nbf' as an integer", 160 | ))); 161 | } 162 | } 163 | Some(_) => { 164 | return Err(Error::MalformedToken(ErrorDetails::new( 165 | "Given 'nbf' not a number", 166 | ))); 167 | } 168 | None => {} 169 | } 170 | } 171 | 172 | if !self.ignore_exp { 173 | match claims.get("exp") { 174 | Some(serde_json::value::Value::Number(number)) => { 175 | if let Some(exp) = number.as_u64() { 176 | if exp <= time_now - (self.leeway as u64) { 177 | return Err(Error::TokenExpiredAt(exp)); 178 | } 179 | } else { 180 | return Err(Error::MalformedToken(ErrorDetails::new( 181 | "Failed to parse 'exp' as an integer", 182 | ))); 183 | } 184 | } 185 | Some(_) => { 186 | return Err(Error::MalformedToken(ErrorDetails::new( 187 | "Given 'exp' not a number", 188 | ))); 189 | } 190 | None => {} 191 | } 192 | } 193 | 194 | // At least verify the type for these standard claims 195 | // (Values can separately be validated via .claim_verifiers) 196 | for &string_claim in &["iss", "sub"] { 197 | match claims.get(string_claim) { 198 | Some(serde_json::value::Value::String(_)) => {} 199 | Some(_) => { 200 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 201 | "Given '{string_claim}' not a string" 202 | )))); 203 | } 204 | None => {} 205 | } 206 | } 207 | 208 | for &string_or_array in &["aud"] { 209 | match claims.get(string_or_array) { 210 | Some(serde_json::value::Value::String(_)) => {} 211 | Some(serde_json::value::Value::Array(claim_array)) => { 212 | for subclaim in claim_array { 213 | if !subclaim.is_string() { 214 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 215 | "Claim {string_or_array}: array elements are not all strings" 216 | )))); 217 | } 218 | } 219 | } 220 | Some(_) => { 221 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 222 | "Given '{string_or_array}' not a string or an array of strings" 223 | )))); 224 | } 225 | None => {} 226 | } 227 | } 228 | 229 | let verifiers = &self.claim_verifiers; 230 | 231 | for (claim_key, claim_verifier) in verifiers.iter() { 232 | match claims.get(claim_key) { 233 | Some(claim_value) => { 234 | if let VerifierKind::Closure(closure_container) = claim_verifier { 235 | let closure = closure_container.func.as_ref(); 236 | if !closure(claim_value) { 237 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 238 | "Claim {claim_key}: verifier callback returned false for '{claim_value}'" 239 | )))); 240 | } 241 | } else if let Value::String(claim_string) = claim_value { 242 | match claim_verifier { 243 | VerifierKind::StringConstant(constant) => { 244 | if claim_string != constant { 245 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 246 | "Claim {claim_key}: {claim_string} != {constant}" 247 | )))); 248 | } 249 | } 250 | VerifierKind::StringSet(constant_set) => { 251 | if !constant_set.contains(claim_string) { 252 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 253 | "Claim {claim_key}: {claim_string} not in set" 254 | )))); 255 | } 256 | } 257 | #[cfg(feature = "matching")] 258 | VerifierKind::StringPattern(pattern) => { 259 | if !pattern.is_match(claim_string) { 260 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 261 | "Claim {claim_key}: {claim_string} doesn't match regex {pattern}" 262 | )))); 263 | } 264 | } 265 | #[cfg(feature = "matching")] 266 | VerifierKind::StringPatternSet(pattern_set) => { 267 | let mut found_match = false; 268 | for p in pattern_set { 269 | if p.is_match(claim_string) { 270 | found_match = true; 271 | break; 272 | } 273 | } 274 | if !found_match { 275 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 276 | "Claim {claim_key}: {claim_string} doesn't match regex set" 277 | )))); 278 | } 279 | } 280 | VerifierKind::StringOrArrayContains(contains) => { 281 | if claim_string != contains { 282 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 283 | "Claim {claim_key}: {claim_string} != {contains}" 284 | )))); 285 | } 286 | } 287 | _ => { 288 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 289 | "Claim {claim_key}: has unexpected type (String)" 290 | )))); 291 | } 292 | } 293 | } else if let Value::Array(claim_array) = claim_value { 294 | match claim_verifier { 295 | VerifierKind::StringOrArrayContains(contains) => { 296 | let mut found = false; 297 | for subclaim in claim_array { 298 | match subclaim { 299 | Value::String(subclaim_string) => { 300 | if subclaim_string == contains { 301 | found = true; 302 | // XXX: don't break from loop early since we want to 303 | // check _all_ array elements are strings 304 | } 305 | } 306 | _ => { 307 | return Err(Error::MalformedToken(ErrorDetails::new( 308 | format!( 309 | "Claim {claim_key}: array elements are not all strings" 310 | ), 311 | ))); 312 | } 313 | } 314 | } 315 | if !found { 316 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 317 | "Claim {claim_key}: array did not contain '{contains}'" 318 | )))); 319 | } 320 | } 321 | _ => { 322 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 323 | "Claim {claim_key}: has unexpected type (Array)" 324 | )))); 325 | } 326 | } 327 | } else if let Value::Number(_claim_number) = claim_value { 328 | // TODO: support verifying numeric claims 329 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 330 | "Claim {claim_key}: has unexpected type (Number)" 331 | )))); 332 | } else { 333 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 334 | "Claim {claim_key}: has unexpected type" 335 | )))); 336 | } 337 | } 338 | _ => { 339 | // If we have a verifier for particular claim then that claim is required 340 | return Err(Error::MalformedToken(ErrorDetails::new(format!( 341 | "Claim {claim_key}: missing" 342 | )))); 343 | } 344 | } 345 | } 346 | Ok(()) 347 | } 348 | 349 | /// Verify a token's signature and its claims, given a specific unix epoch timestamp 350 | pub fn verify_for_time( 351 | &self, 352 | token: impl AsRef, 353 | algorithm: &Algorithm, 354 | time_now: u64, 355 | ) -> Result { 356 | let TokenSlices { 357 | message, 358 | signature, 359 | header, 360 | claims, 361 | } = split_token(token.as_ref())?; 362 | 363 | let header = decode_json_token_slice(header)?; 364 | verify_signature_only(&header, message, signature, algorithm)?; 365 | let claims = decode_json_token_slice(claims)?; 366 | self.verify_claims_only(&claims, time_now)?; 367 | 368 | Ok(TokenData { 369 | header, 370 | claims, 371 | _extensible: (), 372 | }) 373 | } 374 | 375 | /// Verify a token's signature and its claims 376 | pub fn verify( 377 | &self, 378 | token: impl AsRef, 379 | algorithm: &Algorithm, 380 | ) -> Result { 381 | let timestamp = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { 382 | Ok(n) => n.as_secs(), 383 | Err(_) => { 384 | return Err(Error::InvalidInput(ErrorDetails::new( 385 | "SystemTime before UNIX EPOCH!", 386 | ))) 387 | } 388 | }; 389 | 390 | match self.verify_for_time(token.as_ref(), algorithm, timestamp) { 391 | Ok(data) => Ok(data.claims), 392 | Err(error) => Err(error), 393 | } 394 | } 395 | } 396 | 397 | /// Configures the requirements for checking token claims with a builder-pattern API 398 | #[derive(Debug, Default)] 399 | pub struct VerifierBuilder { 400 | leeway: u32, 401 | ignore_exp: bool, 402 | ignore_nbf: bool, 403 | ignore_iat: bool, 404 | 405 | claim_verifiers: HashMap, 406 | } 407 | 408 | impl VerifierBuilder { 409 | pub fn new() -> VerifierBuilder { 410 | VerifierBuilder { 411 | leeway: 0, 412 | ignore_exp: false, 413 | ignore_nbf: false, 414 | ignore_iat: false, 415 | claim_verifiers: HashMap::new(), 416 | } 417 | } 418 | 419 | /// Convenience for string_equals("iss", "value") 420 | pub fn issuer(&mut self, issuer: impl Into) -> &mut Self { 421 | self.string_equals("iss", issuer) 422 | } 423 | 424 | /// Convenience for string_or_array_contains("aud", "value") 425 | pub fn audience(&mut self, aud: impl Into) -> &mut Self { 426 | self.string_or_array_contains("aud", aud) 427 | } 428 | 429 | /// Convenience for string_equals("sub", "value") 430 | pub fn subject(&mut self, sub: impl Into) -> &mut Self { 431 | self.string_equals("sub", sub) 432 | } 433 | 434 | /// Convenience for string_equals("nonce", "value") 435 | pub fn nonce(&mut self, nonce: impl Into) -> &mut Self { 436 | self.string_equals("nonce", nonce) 437 | } 438 | 439 | /// Check that a claim has a specific string value 440 | pub fn string_equals( 441 | &mut self, 442 | claim: impl Into, 443 | value: impl Into, 444 | ) -> &mut Self { 445 | self.claim_verifiers 446 | .insert(claim.into(), VerifierKind::StringConstant(value.into())); 447 | self 448 | } 449 | 450 | /// Check that a claim equals one of the given string values 451 | pub fn string_equals_one_of(&mut self, claim: impl Into, values: &[&str]) -> &mut Self { 452 | let hash_set: HashSet = values.iter().cloned().map(|s| s.to_owned()).collect(); 453 | self.claim_verifiers 454 | .insert(claim.into(), VerifierKind::StringSet(hash_set)); 455 | self 456 | } 457 | 458 | /// Check that the claim matches the given regular expression 459 | #[cfg(feature = "matching")] 460 | pub fn string_matches( 461 | &mut self, 462 | claim: impl Into, 463 | value: impl Into, 464 | ) -> &mut Self { 465 | self.claim_verifiers.insert( 466 | claim.into(), 467 | VerifierKind::StringPattern(Pattern(value.into())), 468 | ); 469 | self 470 | } 471 | 472 | // Maybe this could be more ergonomic if it took &[&str] strings but then we'd have to 473 | // defer compiling the regular expressions until .build() which would be a bit of a pain 474 | 475 | /// Check that the claim matches one of the given regular expressions 476 | #[cfg(feature = "matching")] 477 | #[allow(clippy::mutable_key_type)] 478 | pub fn string_matches_one_of( 479 | &mut self, 480 | claim: impl Into, 481 | values: &[Regex], 482 | ) -> &mut Self { 483 | let hash_set: HashSet = values.iter().cloned().map(Pattern).collect(); 484 | self.claim_verifiers 485 | .insert(claim.into(), VerifierKind::StringPatternSet(hash_set)); 486 | self 487 | } 488 | 489 | pub fn string_or_array_contains( 490 | &mut self, 491 | claim: impl Into, 492 | value: impl Into, 493 | ) -> &mut Self { 494 | self.claim_verifiers.insert( 495 | claim.into(), 496 | VerifierKind::StringOrArrayContains(value.into()), 497 | ); 498 | self 499 | } 500 | 501 | /// Sets a leeway (in seconds) should be allowed when checking exp, nbf and iat claims 502 | pub fn leeway(&mut self, leeway: u32) -> &mut Self { 503 | self.leeway = leeway; 504 | self 505 | } 506 | 507 | /// Don't check the 'exp' expiry claim 508 | pub fn ignore_exp(&mut self) -> &mut Self { 509 | self.ignore_exp = true; 510 | self 511 | } 512 | 513 | /// Don't check the 'nbf' not before claim 514 | pub fn ignore_nbf(&mut self) -> &mut Self { 515 | self.ignore_nbf = true; 516 | self 517 | } 518 | 519 | /// Don't check the 'iat' issued at claim 520 | pub fn ignore_iat(&mut self) -> &mut Self { 521 | self.ignore_iat = true; 522 | self 523 | } 524 | 525 | /// Check a claim `Value` manually, returning `true` if ok, else `false` 526 | pub fn claim_callback( 527 | &mut self, 528 | claim: impl Into, 529 | func: impl Send + Sync + Fn(&serde_json::value::Value) -> bool + 'static, 530 | ) -> &mut Self { 531 | let closure_verifier = VerifierClosure { 532 | func: Arc::new(func), 533 | }; 534 | self.claim_verifiers 535 | .insert(claim.into(), VerifierKind::Closure(closure_verifier)); 536 | self 537 | } 538 | 539 | /// Build the final Verifier 540 | pub fn build(&self) -> Result { 541 | Ok(Verifier { 542 | leeway: self.leeway, 543 | ignore_exp: self.ignore_exp, 544 | ignore_nbf: self.ignore_nbf, 545 | ignore_iat: self.ignore_iat, 546 | claim_verifiers: self.claim_verifiers.clone(), 547 | }) 548 | } 549 | } 550 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | 3 | #[allow(dead_code)] 4 | pub fn get_time() -> u64 { 5 | SystemTime::now() 6 | .duration_since(UNIX_EPOCH) 7 | .expect("Time went backwards") 8 | .as_secs() 9 | } 10 | -------------------------------------------------------------------------------- /tests/ecdsa/README.md: -------------------------------------------------------------------------------- 1 | Note that Ring does not support SEC1 format private elliptic curve keys 2 | (I.e containing markers like `-----BEGIN EC PRIVATE KEY-----`) so they instead 3 | need to be converted to PKCS#8 format. 4 | 5 | Unfortunately the example keys found on jwt.io use the SEC1 format so there's 6 | a reasonable chance that anyone testing this API might hit this limitation. 7 | 8 | SEC1 private keys can be converted with openssl like this: 9 | 10 | ``` 11 | openssl pkcs8 -nocrypt -in private_ecdsa_key_jwtio_p256.pem -topk8 -out private_ecdsa_key_jwtio_p256_pkcs8.pem 12 | ``` 13 | -------------------------------------------------------------------------------- /tests/ecdsa/mod.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use serde_json::value::Value; 3 | 4 | use jsonwebtokens as jwt; 5 | use jwt::{Algorithm, AlgorithmID, Verifier}; 6 | 7 | use crate::common::get_time; 8 | 9 | struct KeyPair<'a> { 10 | id: AlgorithmID, 11 | privkey: &'a [u8], 12 | pubkey: &'a [u8], 13 | } 14 | 15 | const EC_ALGORITHMS: &[KeyPair] = &[ 16 | KeyPair { 17 | id: AlgorithmID::ES256, 18 | privkey: include_bytes!("private_ecdsa_key_jwtio_p256_pkcs8.pem"), 19 | pubkey: include_bytes!("public_ecdsa_key_jwtio_p256_pkcs8.pem"), 20 | }, 21 | KeyPair { 22 | id: AlgorithmID::ES384, 23 | privkey: include_bytes!("private_ecdsa_key_jwtio_p384_pkcs8.pem"), 24 | pubkey: include_bytes!("public_ecdsa_key_jwtio_p384_pkcs8.pem"), 25 | }, 26 | ]; 27 | 28 | #[test] 29 | #[should_panic(expected = "InvalidInput")] 30 | fn fails_with_non_ecdsa_pkcs8_key_format() { 31 | let privkey = include_bytes!("../rsa/private_rsa_key_pkcs1.pem"); 32 | let _alg = Algorithm::new_ecdsa_pem_signer(AlgorithmID::ES256, privkey).unwrap(); 33 | } 34 | 35 | #[test] 36 | #[should_panic(expected = "InvalidInput")] 37 | fn wrong_ecdsa_curve() { 38 | let privkey_pem = include_bytes!("private_ecdsa_key_jwtio_p256_pkcs8.pem"); 39 | 40 | let my_claims = json!({ 41 | "sub": "b@b.com", 42 | "company": "ACME", 43 | "exp": get_time() + 10000, 44 | }); 45 | 46 | let alg = Algorithm::new_ecdsa_pem_signer(AlgorithmID::ES384, privkey_pem).unwrap(); 47 | 48 | let header = json!({"alg": alg.name(), "my_hdr": "my_hdr_val"}); 49 | let _token = jwt::encode(&header, &my_claims, &alg).unwrap(); 50 | } 51 | 52 | #[test] 53 | fn round_trip_sign_verification_pem() { 54 | for keypair in EC_ALGORITHMS { 55 | let alg = Algorithm::new_ecdsa_pem_signer(keypair.id, keypair.privkey).unwrap(); 56 | let signature = alg.sign("hello world").unwrap(); 57 | let alg = Algorithm::new_ecdsa_pem_verifier(keypair.id, keypair.pubkey).unwrap(); 58 | alg.verify(None, "hello world", signature).unwrap(); 59 | } 60 | } 61 | 62 | #[test] 63 | fn round_trip_claims() { 64 | let my_claims = json!({ 65 | "sub": "b@b.com", 66 | "company": "ACME", 67 | "exp": get_time() + 10000, 68 | }); 69 | 70 | for keypair in EC_ALGORITHMS { 71 | let alg = Algorithm::new_ecdsa_pem_signer(keypair.id, keypair.privkey).unwrap(); 72 | 73 | let header = json!({"alg": alg.name(), "my_hdr": "my_hdr_val"}); 74 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 75 | 76 | let alg = Algorithm::new_ecdsa_pem_verifier(keypair.id, keypair.pubkey).unwrap(); 77 | let verifier = Verifier::create().build().unwrap(); 78 | let claims: Value = verifier.verify(token, &alg).unwrap(); 79 | 80 | assert_eq!(my_claims, claims); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/ecdsa/private_ecdsa_key_jwtio_p256_pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 3 | OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r 4 | 1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/ecdsa/private_ecdsa_key_jwtio_p256_sec1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 3 | OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r 4 | 1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/ecdsa/private_ecdsa_key_jwtio_p384_pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCAHpFQ62QnGCEvYh/p 3 | E9QmR1C9aLcDItRbslbmhen/h1tt8AyMhskeenT+rAyyPhGhZANiAAQLW5ZJePZz 4 | MIPAxMtZXkEWbDF0zo9f2n4+T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw 5 | 8lE5IPUWpgu553SteKigiKLUPeNpbqmYZUkWGh3MLfVzLmx85ii2vMU= 6 | -----END PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /tests/ecdsa/private_ecdsa_key_jwtio_p384_sec1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIGkAgEBBDCAHpFQ62QnGCEvYh/pE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhske 3 | enT+rAyyPhGgBwYFK4EEACKhZANiAAQLW5ZJePZzMIPAxMtZXkEWbDF0zo9f2n4+ 4 | T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw8lE5IPUWpgu553SteKigiKLU 5 | PeNpbqmYZUkWGh3MLfVzLmx85ii2vMU= 6 | -----END EC PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /tests/ecdsa/public_ecdsa_key_jwtio_p256_pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9 3 | q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /tests/ecdsa/public_ecdsa_key_jwtio_p384_pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+ 3 | Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii 4 | 1D3jaW6pmGVJFhodzC31cy5sfOYotrzF 5 | -----END PUBLIC KEY----- 6 | -------------------------------------------------------------------------------- /tests/hmac.rs: -------------------------------------------------------------------------------- 1 | use tokio_test::*; 2 | 3 | use serde_json::json; 4 | use serde_json::value::Value; 5 | 6 | use jsonwebtokens as jwt; 7 | use jwt::raw::TokenSlices; 8 | use jwt::{raw, Algorithm, AlgorithmID, TokenData, Verifier}; 9 | 10 | mod common; 11 | use common::get_time; 12 | 13 | use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; 14 | 15 | #[test] 16 | fn sign_hs256() { 17 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 18 | let result = alg.sign("hello world").unwrap(); 19 | let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; 20 | assert_eq!(result, expected); 21 | } 22 | 23 | #[test] 24 | fn verify_hs256() { 25 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 26 | let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; 27 | assert_ok!(alg.verify(None, "hello world", sig)); 28 | } 29 | 30 | #[test] 31 | fn verify_hs256_signature_only() { 32 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 33 | let header = json!({ "alg": "HS256" }); 34 | let claims = json!({ "aud": "test" }); 35 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 36 | 37 | let TokenSlices { 38 | message, 39 | signature, 40 | header, 41 | .. 42 | } = raw::split_token(&token_str).unwrap(); 43 | let header = raw::decode_json_token_slice(header).unwrap(); 44 | 45 | assert_ok!(raw::verify_signature_only( 46 | &header, message, signature, &alg 47 | )); 48 | } 49 | 50 | #[test] 51 | #[should_panic(expected = "InvalidSignature")] 52 | fn hmac_256_bad_secret() { 53 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 54 | let header = json!({ "alg": "HS256" }); 55 | let claims = json!({ "aud": "test" }); 56 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 57 | 58 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "wrong-secret").unwrap(); 59 | let validator = Verifier::create().build().unwrap(); 60 | let _claims: Value = validator.verify(token_str, &alg).unwrap(); 61 | } 62 | 63 | #[test] 64 | #[should_panic(expected = "AlgorithmMismatch")] 65 | fn missing_alg() { 66 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 67 | 68 | let header = json!({}); 69 | let claims = json!({ "aud": "test" }); 70 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 71 | 72 | let validator = Verifier::create().build().unwrap(); 73 | let _claims: Value = validator.verify(token_str, &alg).unwrap(); 74 | } 75 | 76 | #[test] 77 | fn round_trip_claims() { 78 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 79 | let my_claims = json!({ 80 | "sub": "b@b.com", 81 | "company": "ACME", 82 | "exp": get_time() + 10000, 83 | }); 84 | let header = json!({"alg": "HS256"}); 85 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 86 | 87 | let verifier = Verifier::create().build().unwrap(); 88 | let claims: Value = verifier.verify(token, &alg).unwrap(); 89 | 90 | assert_eq!(my_claims, claims); 91 | } 92 | 93 | #[test] 94 | fn round_trip_claims_and_custom_header() { 95 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 96 | let my_claims = json!({ 97 | "sub": "b@b.com", 98 | "company": "ACME", 99 | "exp": get_time() + 10000, 100 | }); 101 | let header = json!({"alg": "HS256", "my_hdr": "my_hdr_val"}); 102 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 103 | 104 | let verifier = Verifier::create().build().unwrap(); 105 | 106 | // We have to use the lower-level for_time API if we want to see the header 107 | let TokenData { header, claims, .. } = 108 | verifier.verify_for_time(token, &alg, get_time()).unwrap(); 109 | 110 | assert_eq!(my_claims, claims); 111 | assert_eq!(header.get("my_hdr").unwrap(), "my_hdr_val"); 112 | } 113 | 114 | #[test] 115 | fn round_trip_claims_and_kid() { 116 | let mut alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 117 | alg.set_kid("kid1234"); 118 | 119 | let my_claims = json!({ 120 | "sub": "b@b.com", 121 | "company": "ACME", 122 | "exp": get_time() + 10000, 123 | }); 124 | let header = json!({ 125 | "alg": alg.name(), 126 | "kid": alg.kid(), 127 | "my_hdr": "my_hdr_val" 128 | }); 129 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 130 | 131 | let verifier = Verifier::create().build().unwrap(); 132 | 133 | // We have to use the lower-level for_time API if we want to see the header 134 | let TokenData { header, claims, .. } = 135 | verifier.verify_for_time(token, &alg, get_time()).unwrap(); 136 | 137 | assert_eq!(my_claims, claims); 138 | assert_eq!(header.get("kid").unwrap(), "kid1234"); 139 | assert_eq!(header.get("my_hdr").unwrap(), "my_hdr_val"); 140 | } 141 | 142 | #[test] 143 | #[should_panic(expected = "MalformedToken")] 144 | fn round_trip_claims_and_wrong_kid() { 145 | let mut alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 146 | alg.set_kid("kid1234"); 147 | 148 | let my_claims = json!({ 149 | "sub": "b@b.com", 150 | "company": "ACME", 151 | "exp": get_time() + 10000, 152 | }); 153 | let header = json!({ 154 | "alg": alg.name(), 155 | "kid": "kid4321", 156 | "my_hdr": "my_hdr_val" 157 | }); 158 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 159 | 160 | let verifier = Verifier::create().build().unwrap(); 161 | 162 | // We have to use the lower-level for_time API if we want to see the header 163 | let TokenData { header, claims, .. } = 164 | verifier.verify_for_time(token, &alg, get_time()).unwrap(); 165 | 166 | assert_eq!(my_claims, claims); 167 | assert_eq!(header.get("kid").unwrap(), "kid1234"); 168 | assert_eq!(header.get("my_hdr").unwrap(), "my_hdr_val"); 169 | } 170 | 171 | #[test] 172 | fn decode_token() { 173 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 174 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 175 | 176 | let verifier = Verifier::create().build().unwrap(); 177 | let claims: Value = verifier.verify(token, &alg).unwrap(); 178 | println!("{claims:?}"); 179 | } 180 | 181 | #[test] 182 | #[should_panic(expected = "MalformedToken")] 183 | fn decode_token_missing_parts() { 184 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 185 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; 186 | let verifier = Verifier::create().build().unwrap(); 187 | let _claims: Value = verifier.verify(token, &alg).unwrap(); 188 | } 189 | 190 | #[test] 191 | #[should_panic(expected = "InvalidSignature")] 192 | fn decode_token_invalid_signature() { 193 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 194 | let token = 195 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; 196 | let verifier = Verifier::create().build().unwrap(); 197 | let _claims: Value = verifier.verify(token, &alg).unwrap(); 198 | } 199 | 200 | #[test] 201 | fn decode_token_with_bytes_secret() { 202 | let secret_b64 = URL_SAFE_NO_PAD.encode(b"\x01\x02\x03"); 203 | let alg = Algorithm::new_hmac_b64(AlgorithmID::HS256, secret_b64).unwrap(); 204 | let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; 205 | let verifier = Verifier::create().build().unwrap(); 206 | let _claims: Value = verifier.verify(token, &alg).unwrap(); 207 | } 208 | 209 | #[test] 210 | fn only_decode_token_header() { 211 | let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; 212 | let header = raw::decode_header_only(token).unwrap(); 213 | assert_eq!(header.get("alg").expect("missing alg"), "HS256"); 214 | assert_eq!(header.get("typ").expect("missing typ"), "JWT"); 215 | } 216 | 217 | #[test] 218 | fn only_decode_token_header_with_slice_api() { 219 | let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; 220 | let TokenSlices { header, .. } = raw::split_token(token).unwrap(); 221 | let header = raw::decode_json_token_slice(header).unwrap(); 222 | assert_eq!(header.get("alg").expect("missing alg"), "HS256"); 223 | assert_eq!(header.get("typ").expect("missing typ"), "JWT"); 224 | } 225 | 226 | #[test] 227 | fn only_decode_token() { 228 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 229 | let TokenData { header, claims, .. } = raw::decode_only(token).unwrap(); 230 | 231 | assert_eq!(header.get("alg").expect("missing alg"), "HS256"); 232 | assert_eq!(header.get("typ").expect("missing typ"), "JWT"); 233 | assert_eq!(claims.get("sub").expect("no sub"), "b@b.com"); 234 | assert_eq!(claims.get("company").expect("no company"), "ACME"); 235 | assert_eq!(claims.get("exp").expect("no exp"), 2532524891u64); 236 | } 237 | 238 | #[test] 239 | #[should_panic(expected = "MalformedToken")] 240 | fn split_token_missing_parts() { 241 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; 242 | let _token_slices = raw::split_token(token).unwrap(); 243 | } 244 | 245 | #[test] 246 | fn only_decode_token_invalid_signature() { 247 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.wrong"; 248 | let _token_data = raw::decode_only(token).unwrap(); 249 | } 250 | 251 | #[test] 252 | fn only_decode_token_wrong_algorithm() { 253 | let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.fLxey-hxAKX5rNHHIx1_Ch0KmrbiuoakDVbsJjLWrx8fbjKjrPuWMYEJzTU3SBnYgnZokC-wqSdqckXUOunC-g"; 254 | let _token_data = raw::decode_only(token).unwrap(); 255 | } 256 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod ecdsa; 3 | mod rsa; 4 | -------------------------------------------------------------------------------- /tests/none.rs: -------------------------------------------------------------------------------- 1 | use tokio_test::*; 2 | 3 | use serde_json::json; 4 | use serde_json::value::Value; 5 | 6 | use jsonwebtokens as jwt; 7 | use jwt::{Algorithm, AlgorithmID, Verifier}; 8 | 9 | mod common; 10 | use common::get_time; 11 | 12 | #[test] 13 | fn jwt_name() { 14 | let alg = Algorithm::new_unsecured().unwrap(); 15 | assert_eq!(alg.name(), "none"); 16 | } 17 | 18 | #[test] 19 | fn sign_none() { 20 | let alg = Algorithm::new_unsecured().unwrap(); 21 | let result = alg.sign("hello world").unwrap(); 22 | assert_eq!(result, ""); 23 | } 24 | 25 | #[test] 26 | fn verify_none() { 27 | let alg = Algorithm::new_unsecured().unwrap(); 28 | alg.verify(None, "hello world", "").unwrap(); 29 | } 30 | 31 | #[test] 32 | #[should_panic(expected = "InvalidSignature")] 33 | fn verify_none_with_non_empty_signature() { 34 | let alg = Algorithm::new_unsecured().unwrap(); 35 | let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; 36 | alg.verify(None, "hello world", sig).unwrap(); 37 | } 38 | 39 | #[test] 40 | #[should_panic(expected = "AlgorithmMismatch")] 41 | fn missing_alg() { 42 | let alg = Algorithm::new_unsecured().unwrap(); 43 | let header = json!({}); 44 | let claims = json!({ "aud": "test" }); 45 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 46 | 47 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 48 | let validator = Verifier::create().build().unwrap(); 49 | let _claims: Value = validator.verify(token_str, &alg).unwrap(); 50 | } 51 | 52 | #[test] 53 | #[should_panic(expected = "AlgorithmMismatch")] 54 | fn wrong_alg() { 55 | let alg = Algorithm::new_unsecured().unwrap(); 56 | 57 | let header = json!({ "alg": "HS256" }); 58 | let claims = json!({ "aud": "test" }); 59 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 60 | 61 | let validator = Verifier::create().build().unwrap(); 62 | let _claims: Value = validator.verify(token_str, &alg).unwrap(); 63 | } 64 | 65 | #[test] 66 | fn round_trip_claims() { 67 | let alg = Algorithm::new_unsecured().unwrap(); 68 | 69 | let my_claims = json!({ 70 | "sub": "b@b.com", 71 | "company": "ACME", 72 | "exp": get_time() + 10000, 73 | }); 74 | let header = json!({"alg": alg.name()}); 75 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 76 | 77 | let verifier = Verifier::create().build().unwrap(); 78 | let claims: Value = verifier.verify(token, &alg).unwrap(); 79 | 80 | assert_eq!(my_claims, claims); 81 | } 82 | 83 | #[test] 84 | fn no_trailing_dot() { 85 | let alg = Algorithm::new_unsecured().unwrap(); 86 | 87 | let token_ok = "eyJhbGciOiJub25lIn0.eyJjb21wYW55IjoiQUNNRSIsInN1YiI6ImJAYi5jb20ifQ."; 88 | let token_bad = "eyJhbGciOiJub25lIn0.eyJjb21wYW55IjoiQUNNRSIsInN1YiI6ImJAYi5jb20ifQ"; 89 | 90 | let verifier = Verifier::create().build().unwrap(); 91 | let result: Result = verifier.verify(token_ok, &alg); 92 | assert_ok!(result); 93 | 94 | let result: Result = verifier.verify(token_bad, &alg); 95 | assert_err!(result); 96 | } 97 | 98 | #[test] 99 | fn token_with_non_empty_signature() { 100 | let alg = Algorithm::new_unsecured().unwrap(); 101 | 102 | let token_ok = "eyJhbGciOiJub25lIn0.eyJjb21wYW55IjoiQUNNRSIsInN1YiI6ImJAYi5jb20ifQ."; 103 | let token_bad = "eyJhbGciOiJub25lIn0.eyJjb21wYW55IjoiQUNNRSIsInN1YiI6ImJAYi5jb20ifQ.1234"; 104 | 105 | let verifier = Verifier::create().build().unwrap(); 106 | let result: Result = verifier.verify(token_ok, &alg); 107 | assert_ok!(result); 108 | 109 | let result: Result = verifier.verify(token_bad, &alg); 110 | assert_err!(result); 111 | } 112 | -------------------------------------------------------------------------------- /tests/rsa/certificate_rsa_pkcs1.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDEzCCAfsCFFyLRBUHZzSzwHW4Nv3UnoluIrEAMA0GCSqGSIb3DQEBCwUAMEUx 3 | CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl 4 | cm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjIxMjA3MTMwMTI0WhgPMzAwMzAyMDgx 5 | MzAxMjRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD 6 | VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA 7 | A4IBDwAwggEKAoIBAQDJETqse41HRBsc7cfcq3ak4oZWFCoZlcic525A3FfO4qW9 8 | BMtRO/iXiyCCHn8JhiL9y8j5JdVP2Q9ZIpfElcFd3/guS9w+5RqQGgCR+H56IVUy 9 | HZWtTJbKPcwWXQdNUX0rBFcsBzCRESJLeelOEdHIjG7LRkx5l/FUvlqsyHDVJEQs 10 | HwegZ8b8C0fz0EgT2MMEdn10t6Ur1rXzjMB/wvCg8vG8lvciXmedyo9xJ8oMOh0w 11 | UEgxziVDMMovmC+aJctcHUAYubwoGN8TyzcvnGqL7JSh36Pwy28iPzXZ2RLhAyJF 12 | U39vLaHdljwthUaupldlNyCfa6Ofy4qNctlUPlN1AgMBAAEwDQYJKoZIhvcNAQEL 13 | BQADggEBABk9rA8RQXJUczV0iKF4U8yBaAVIscj2nLQQhZJJBXeeKLiRgMXEqcUz 14 | 3CozqPQF+Dps9pInAQkW9A+Hi8vWTj129OcIQblwwLCoH8EA1L+Jg5DBcX6OcWiE 15 | Y86S7uYe6yXuthSsw1pFphoqgo0GUxrQbf8dpYeGYR9dLbJMDSmWaGAeEjCPMAdp 16 | LmfIGH1R7MZPUXxu5+brc9FCQU8mu5xyUf+8P51TUnGnf68JXV9Fi+Q0VpI1t2wy 17 | 8qXXs18+xESYB4HnLi3nh6RYXmvbqORwJa+iJUdicnSQ0GN4bWyVmhyI+emaQ5UP 18 | KzdXwer91MRqKgJfS1HLnYS808QqGWU= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /tests/rsa/certificate_rsa_pkcs1.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx 3 | ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN 4 | AQEBBQADggEPADCCAQoCggEBAMkROqx7jUdEGxztx9yrdqTihlYUKhmVyJznbkDc 5 | V87ipb0Ey1E7+JeLIIIefwmGIv3LyPkl1U/ZD1kil8SVwV3f+C5L3D7lGpAaAJH4 6 | fnohVTIdla1Mlso9zBZdB01RfSsEVywHMJERIkt56U4R0ciMbstGTHmX8VS+WqzI 7 | cNUkRCwfB6BnxvwLR/PQSBPYwwR2fXS3pSvWtfOMwH/C8KDy8byW9yJeZ53Kj3En 8 | ygw6HTBQSDHOJUMwyi+YL5oly1wdQBi5vCgY3xPLNy+caovslKHfo/DLbyI/NdnZ 9 | EuEDIkVTf28tod2WPC2FRq6mV2U3IJ9ro5/Lio1y2VQ+U3UCAwEAAaAAMA0GCSqG 10 | SIb3DQEBCwUAA4IBAQAhhe1K0osJIZLOalQYWU2fmStHrTC6Hkakf6E5rjIAkQqZ 11 | G38LnrC/wrPurSKceS8Rb9jmpqODmVdeMnqzag0/t428pgesiO2mdpSg62b/9tEc 12 | xQmnhGkXce54AAnA27VcLpIrJPe/adX1yEIT3EZce26RMIjB1wN/Y1bSjMSvFjHo 13 | HDIDQi7ZSC/U4xXfjn+TSfBKkKg8Am9hxZNGUCt7PhMJ13i+nXdF2JzhjCisTCTu 14 | C2R+ccN3BtAKIs2BdYvYze55LBCbhb1Zo2jYDPi86CRPcozp6wzOxvKzzqxZRbcA 15 | HnyTfnHMPkQzAWMJXoDawPg5imwhM3kN2Qo5CzSA 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /tests/rsa/mod.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use serde_json::value::Value; 3 | 4 | use jsonwebtokens as jwt; 5 | use jwt::{Algorithm, AlgorithmID, TokenData, Verifier}; 6 | 7 | use crate::common::get_time; 8 | 9 | const RSA_ALGORITHMS: &[AlgorithmID] = &[ 10 | AlgorithmID::RS256, 11 | AlgorithmID::RS384, 12 | AlgorithmID::RS512, 13 | AlgorithmID::PS256, 14 | AlgorithmID::PS384, 15 | AlgorithmID::PS512, 16 | ]; 17 | 18 | #[test] 19 | #[should_panic(expected = "AlgorithmMismatch")] 20 | fn decode_token_wrong_algorithm() { 21 | let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); 22 | 23 | let alg = Algorithm::new_rsa_pem_verifier(AlgorithmID::RS256, pubkey_pem).unwrap(); 24 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; 25 | let verifier = Verifier::create().build().unwrap(); 26 | let _claims: Value = verifier.verify(token, &alg).unwrap(); 27 | } 28 | 29 | #[test] 30 | fn round_trip_sign_verification_pem_pkcs1() { 31 | let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); 32 | let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); 33 | 34 | for &id in RSA_ALGORITHMS { 35 | let alg = Algorithm::new_rsa_pem_signer(id, privkey_pem).unwrap(); 36 | let signature = alg.sign("hello world").unwrap(); 37 | let alg = Algorithm::new_rsa_pem_verifier(id, pubkey_pem).unwrap(); 38 | alg.verify(None, "hello world", signature).unwrap(); 39 | } 40 | } 41 | 42 | #[test] 43 | fn round_trip_sign_verification_certificate_pem() { 44 | let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); 45 | let certificate = include_bytes!("certificate_rsa_pkcs1.crt"); 46 | 47 | for &id in RSA_ALGORITHMS { 48 | let alg = Algorithm::new_rsa_pem_signer(id, privkey_pem).unwrap(); 49 | let signature = alg.sign("hello world").unwrap(); 50 | let alg = Algorithm::new_rsa_pem_verifier(id, certificate).unwrap(); 51 | alg.verify(None, "hello world", signature).unwrap(); 52 | } 53 | } 54 | 55 | #[test] 56 | fn round_trip_sign_verification_pem_pkcs8() { 57 | let privkey_pem = include_bytes!("private_rsa_key_pkcs8.pem"); 58 | let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem"); 59 | 60 | for &id in RSA_ALGORITHMS { 61 | let alg = Algorithm::new_rsa_pem_signer(id, privkey_pem).unwrap(); 62 | let signature = alg.sign("hello world").unwrap(); 63 | let alg = Algorithm::new_rsa_pem_verifier(id, pubkey_pem).unwrap(); 64 | alg.verify(None, "hello world", signature).unwrap(); 65 | } 66 | } 67 | 68 | #[test] 69 | fn round_trip_claims() { 70 | let privkey_pem = include_bytes!("private_rsa_key_pkcs8.pem"); 71 | let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem"); 72 | 73 | let my_claims = json!({ 74 | "sub": "b@b.com", 75 | "company": "ACME", 76 | "exp": get_time() + 10000, 77 | }); 78 | 79 | for &id in RSA_ALGORITHMS { 80 | let alg = Algorithm::new_rsa_pem_signer(id, privkey_pem).unwrap(); 81 | 82 | let header = json!({"alg": alg.name(), "my_hdr": "my_hdr_val"}); 83 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 84 | 85 | let alg = Algorithm::new_rsa_pem_verifier(id, pubkey_pem).unwrap(); 86 | let verifier = Verifier::create().build().unwrap(); 87 | let claims: Value = verifier.verify(token, &alg).unwrap(); 88 | 89 | assert_eq!(my_claims, claims); 90 | } 91 | } 92 | 93 | #[test] 94 | fn round_trip_claims_and_custom_header() { 95 | let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); 96 | let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); 97 | 98 | let my_claims = json!({ 99 | "sub": "b@b.com", 100 | "company": "ACME", 101 | "exp": get_time() + 10000, 102 | }); 103 | 104 | for &id in RSA_ALGORITHMS { 105 | let alg = Algorithm::new_rsa_pem_signer(id, privkey_pem).unwrap(); 106 | 107 | let header = json!({"alg": alg.name(), "kid": "kid1234", "my_hdr": "my_hdr_val"}); 108 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 109 | 110 | let mut alg = Algorithm::new_rsa_pem_verifier(id, pubkey_pem).unwrap(); 111 | alg.set_kid("kid1234"); 112 | let verifier = Verifier::create().build().unwrap(); 113 | 114 | // We have to use the lower-level for_time API if we want to see the header 115 | let TokenData { header, claims, .. } = 116 | verifier.verify_for_time(token, &alg, get_time()).unwrap(); 117 | 118 | assert_eq!(my_claims, claims); 119 | assert_eq!(header.get("kid").unwrap(), "kid1234"); 120 | assert_eq!(header.get("my_hdr").unwrap(), "my_hdr_val"); 121 | } 122 | } 123 | 124 | #[test] 125 | #[should_panic(expected = "InvalidInput")] 126 | fn dont_allow_sign_with_verify_algorithm() { 127 | let my_claims = json!({ 128 | "sub": "b@b.com", 129 | "company": "ACME", 130 | "exp": get_time() + 10000, 131 | }); 132 | let keypair = include_bytes!("private_rsa_key_pkcs1.pem"); 133 | 134 | let verify_alg = Algorithm::new_rsa_pem_verifier(AlgorithmID::RS256, keypair).unwrap(); 135 | 136 | let header = json!({"alg": verify_alg.name()}); 137 | let _token = jwt::encode(&header, &my_claims, &verify_alg).unwrap(); 138 | } 139 | 140 | #[test] 141 | #[should_panic(expected = "InvalidInput")] 142 | fn dont_allow_verify_with_sign_algorithm() { 143 | let my_claims = json!({ 144 | "sub": "b@b.com", 145 | "company": "ACME", 146 | "exp": get_time() + 10000, 147 | }); 148 | let keypair = include_bytes!("private_rsa_key_pkcs1.pem"); 149 | 150 | let sign_alg = Algorithm::new_rsa_pem_signer(AlgorithmID::RS256, keypair).unwrap(); 151 | 152 | let header = json!({"alg": sign_alg.name()}); 153 | let token = jwt::encode(&header, &my_claims, &sign_alg).unwrap(); 154 | 155 | let verifier = Verifier::create().build().unwrap(); 156 | let _claims: Value = verifier.verify(token, &sign_alg).unwrap(); 157 | } 158 | 159 | #[test] 160 | fn rsa_modulus_exponent() { 161 | let privkey = include_bytes!("private_rsa_key_pkcs1.pem"); 162 | let sign_alg = Algorithm::new_rsa_pem_signer(AlgorithmID::RS256, privkey).unwrap(); 163 | let my_claims = json!({ 164 | "sub": "b@b.com", 165 | "company": "ACME", 166 | "exp": get_time() + 10000, 167 | }); 168 | let n = "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ"; 169 | let e = "AQAB"; 170 | 171 | let header = json!({"alg": sign_alg.name() }); 172 | let token = jwt::encode(&header, &my_claims, &sign_alg).unwrap(); 173 | 174 | let verify_alg = Algorithm::new_rsa_n_e_b64_verifier(AlgorithmID::RS256, n, e).unwrap(); 175 | let verifier = Verifier::create().build().unwrap(); 176 | let _claims: Value = verifier.verify(token, &verify_alg).unwrap(); 177 | } 178 | 179 | // https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken 180 | #[test] 181 | fn roundtrip_with_jwtio_example_jey() { 182 | let privkey_pem = include_bytes!("private_rsa_key_jwtio_pkcs1.pem"); 183 | let pubkey_pem = include_bytes!("public_rsa_key_jwtio_pkcs1.pem"); 184 | 185 | let my_claims = json!({ 186 | "sub": "b@b.com", 187 | "company": "ACME", 188 | "exp": get_time() + 10000, 189 | }); 190 | 191 | for &id in RSA_ALGORITHMS { 192 | let alg = Algorithm::new_rsa_pem_signer(id, privkey_pem).unwrap(); 193 | 194 | let header = json!({"alg": alg.name()}); 195 | let token = jwt::encode(&header, &my_claims, &alg).unwrap(); 196 | 197 | let alg = Algorithm::new_rsa_pem_verifier(id, pubkey_pem).unwrap(); 198 | let verifier = Verifier::create().build().unwrap(); 199 | let claims: Value = verifier.verify(token, &alg).unwrap(); 200 | 201 | assert_eq!(my_claims, claims); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /tests/rsa/private_rsa_key_jwtio_pkcs1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw 3 | kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr 4 | m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi 5 | NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV 6 | 3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2 7 | QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs 8 | kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go 9 | amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM 10 | +bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9 11 | D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC 12 | 0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y 13 | lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+ 14 | hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp 15 | bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X 16 | +jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B 17 | BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC 18 | 2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx 19 | QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz 20 | 5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9 21 | Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0 22 | NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j 23 | 8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma 24 | 3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K 25 | y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB 26 | jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/rsa/private_rsa_key_pkcs1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAyRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTL 3 | UTv4l4sggh5/CYYi/cvI+SXVT9kPWSKXxJXBXd/4LkvcPuUakBoAkfh+eiFVMh2V 4 | rUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8H 5 | oGfG/AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBI 6 | Mc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi+yUod+j8MtvIj812dkS4QMiRVN/ 7 | by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQIDAQABAoIBAHREk0I0O9DvECKd 8 | WUpAmF3mY7oY9PNQiu44Yaf+AoSuyRpRUGTMIgc3u3eivOE8ALX0BmYUO5JtuRNZ 9 | Dpvt4SAwqCnVUinIf6C+eH/wSurCpapSM0BAHp4aOA7igptyOMgMPYBHNA1e9A7j 10 | E0dCxKWMl3DSWNyjQTk4zeRGEAEfbNjHrq6YCtjHSZSLmWiG80hnfnYos9hOr5Jn 11 | LnyS7ZmFE/5P3XVrxLc/tQ5zum0R4cbrgzHiQP5RgfxGJaEi7XcgherCCOgurJSS 12 | bYH29Gz8u5fFbS+Yg8s+OiCss3cs1rSgJ9/eHZuzGEdUZVARH6hVMjSuwvqVTFaE 13 | 8AgtleECgYEA+uLMn4kNqHlJS2A5uAnCkj90ZxEtNm3E8hAxUrhssktY5XSOAPBl 14 | xyf5RuRGIImGtUVIr4HuJSa5TX48n3Vdt9MYCprO/iYl6moNRSPt5qowIIOJmIjY 15 | 2mqPDfDt/zw+fcDD3lmCJrFlzcnh0uea1CohxEbQnL3cypeLt+WbU6kCgYEAzSp1 16 | 9m1ajieFkqgoB0YTpt/OroDx38vvI5unInJlEeOjQ+oIAQdN2wpxBvTrRorMU6P0 17 | 7mFUbt1j+Co6CbNiw+X8HcCaqYLR5clbJOOWNR36PuzOpQLkfK8woupBxzW9B8gZ 18 | mY8rB1mbJ+/WTPrEJy6YGmIEBkWylQ2VpW8O4O0CgYEApdbvvfFBlwD9YxbrcGz7 19 | MeNCFbMz+MucqQntIKoKJ91ImPxvtc0y6e/Rhnv0oyNlaUOwJVu0yNgNG117w0g4 20 | t/+Q38mvVC5xV7/cn7x9UMFk6MkqVir3dYGEqIl/OP1grY2Tq9HtB5iyG9L8NIam 21 | QOLMyUqqMUILxdthHyFmiGkCgYEAn9+PjpjGMPHxL0gj8Q8VbzsFtou6b1deIRRA 22 | 2CHmSltltR1gYVTMwXxQeUhPMmgkMqUXzs4/WijgpthY44hK1TaZEKIuoxrS70nJ 23 | 4WQLf5a9k1065fDsFZD6yGjdGxvwEmlGMZgTwqV7t1I4X0Ilqhav5hcs5apYL7gn 24 | PYPeRz0CgYALHCj/Ji8XSsDoF/MhVhnGdIs2P99NNdmo3R2Pv0CuZbDKMU559LJH 25 | UvrKS8WkuWRDuKrz1W/EQKApFjDGpdqToZqriUFQzwy7mR3ayIiogzNtHcvbDHx8 26 | oFnGY0OFksX/ye0/XGpy2SFxYRwGU98HPYeBvAQQrVjdkzfy7BmXQQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/rsa/private_rsa_key_pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJETqse41HRBsc 3 | 7cfcq3ak4oZWFCoZlcic525A3FfO4qW9BMtRO/iXiyCCHn8JhiL9y8j5JdVP2Q9Z 4 | IpfElcFd3/guS9w+5RqQGgCR+H56IVUyHZWtTJbKPcwWXQdNUX0rBFcsBzCRESJL 5 | eelOEdHIjG7LRkx5l/FUvlqsyHDVJEQsHwegZ8b8C0fz0EgT2MMEdn10t6Ur1rXz 6 | jMB/wvCg8vG8lvciXmedyo9xJ8oMOh0wUEgxziVDMMovmC+aJctcHUAYubwoGN8T 7 | yzcvnGqL7JSh36Pwy28iPzXZ2RLhAyJFU39vLaHdljwthUaupldlNyCfa6Ofy4qN 8 | ctlUPlN1AgMBAAECggEAdESTQjQ70O8QIp1ZSkCYXeZjuhj081CK7jhhp/4ChK7J 9 | GlFQZMwiBze7d6K84TwAtfQGZhQ7km25E1kOm+3hIDCoKdVSKch/oL54f/BK6sKl 10 | qlIzQEAenho4DuKCm3I4yAw9gEc0DV70DuMTR0LEpYyXcNJY3KNBOTjN5EYQAR9s 11 | 2MeurpgK2MdJlIuZaIbzSGd+diiz2E6vkmcufJLtmYUT/k/ddWvEtz+1DnO6bRHh 12 | xuuDMeJA/lGB/EYloSLtdyCF6sII6C6slJJtgfb0bPy7l8VtL5iDyz46IKyzdyzW 13 | tKAn394dm7MYR1RlUBEfqFUyNK7C+pVMVoTwCC2V4QKBgQD64syfiQ2oeUlLYDm4 14 | CcKSP3RnES02bcTyEDFSuGyyS1jldI4A8GXHJ/lG5EYgiYa1RUivge4lJrlNfjyf 15 | dV230xgKms7+JiXqag1FI+3mqjAgg4mYiNjaao8N8O3/PD59wMPeWYImsWXNyeHS 16 | 55rUKiHERtCcvdzKl4u35ZtTqQKBgQDNKnX2bVqOJ4WSqCgHRhOm386ugPHfy+8j 17 | m6cicmUR46ND6ggBB03bCnEG9OtGisxTo/TuYVRu3WP4KjoJs2LD5fwdwJqpgtHl 18 | yVsk45Y1Hfo+7M6lAuR8rzCi6kHHNb0HyBmZjysHWZsn79ZM+sQnLpgaYgQGRbKV 19 | DZWlbw7g7QKBgQCl1u+98UGXAP1jFutwbPsx40IVszP4y5ypCe0gqgon3UiY/G+1 20 | zTLp79GGe/SjI2VpQ7AlW7TI2A0bXXvDSDi3/5Dfya9ULnFXv9yfvH1QwWToySpW 21 | Kvd1gYSoiX84/WCtjZOr0e0HmLIb0vw0hqZA4szJSqoxQgvF22EfIWaIaQKBgQCf 22 | 34+OmMYw8fEvSCPxDxVvOwW2i7pvV14hFEDYIeZKW2W1HWBhVMzBfFB5SE8yaCQy 23 | pRfOzj9aKOCm2FjjiErVNpkQoi6jGtLvScnhZAt/lr2TXTrl8OwVkPrIaN0bG/AS 24 | aUYxmBPCpXu3UjhfQiWqFq/mFyzlqlgvuCc9g95HPQKBgAscKP8mLxdKwOgX8yFW 25 | GcZ0izY/30012ajdHY+/QK5lsMoxTnn0skdS+spLxaS5ZEO4qvPVb8RAoCkWMMal 26 | 2pOhmquJQVDPDLuZHdrIiKiDM20dy9sMfHygWcZjQ4WSxf/J7T9canLZIXFhHAZT 27 | 3wc9h4G8BBCtWN2TN/LsGZdB 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /tests/rsa/public_rsa_key_jwtio_pkcs1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv 3 | vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc 4 | aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy 5 | tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0 6 | e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb 7 | V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9 8 | MwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /tests/rsa/public_rsa_key_pkcs1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEAyRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4 3 | l4sggh5/CYYi/cvI+SXVT9kPWSKXxJXBXd/4LkvcPuUakBoAkfh+eiFVMh2VrUyW 4 | yj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG 5 | /AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4l 6 | QzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi+yUod+j8MtvIj812dkS4QMiRVN/by2h 7 | 3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /tests/rsa/public_rsa_key_pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRE6rHuNR0QbHO3H3Kt2 3 | pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5/CYYi/cvI+SXVT9kPWSKXxJXB 4 | Xd/4LkvcPuUakBoAkfh+eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHR 5 | yIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG/AtH89BIE9jDBHZ9dLelK9a184zAf8Lw 6 | oPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xq 7 | i+yUod+j8MtvIj812dkS4QMiRVN/by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5T 8 | dQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /tests/time.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | 3 | use jsonwebtokens as jwt; 4 | use jwt::{error::Error, Algorithm, AlgorithmID, Verifier}; 5 | 6 | mod common; 7 | 8 | const REFERENCE_TIME: u64 = 1575057015u64; 9 | 10 | #[test] 11 | fn token_just_expired() { 12 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 13 | let header = json!({ "alg": "HS256" }); 14 | let claims = json!({ "exp": REFERENCE_TIME }); 15 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 16 | 17 | // "requires that the current date/time MUST be before the expiration 18 | // date/time listed in the "exp" claim." 19 | // So being equal should be considered expired... 20 | let verifier = Verifier::create().build().unwrap(); 21 | let result = verifier.verify_for_time(token_str, &alg, REFERENCE_TIME); 22 | match result { 23 | Err(Error::TokenExpiredAt(at)) => { 24 | assert_eq!(at, REFERENCE_TIME); 25 | } 26 | _ => unreachable!("Token not expired"), 27 | } 28 | } 29 | 30 | #[test] 31 | fn token_expired() { 32 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 33 | let header = json!({ "alg": "HS256" }); 34 | let claims = json!({ "exp": REFERENCE_TIME }); 35 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 36 | 37 | let verifier = Verifier::create().build().unwrap(); 38 | let result = verifier.verify_for_time(token_str, &alg, REFERENCE_TIME + 100); 39 | match result { 40 | Err(Error::TokenExpiredAt(at)) => { 41 | assert_eq!(at, REFERENCE_TIME); 42 | } 43 | _ => unreachable!("Token not expired"), 44 | } 45 | } 46 | 47 | #[test] 48 | fn ignore_token_expired() { 49 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 50 | let header = json!({ "alg": "HS256" }); 51 | let claims = json!({ "exp": REFERENCE_TIME }); 52 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 53 | 54 | let verifier = Verifier::create().ignore_exp().build().unwrap(); 55 | let _token_data = verifier 56 | .verify_for_time(token_str, &alg, REFERENCE_TIME + 100) 57 | .unwrap(); 58 | } 59 | 60 | #[test] 61 | fn token_recently_expired_with_leeway() { 62 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 63 | let header = json!({ "alg": "HS256" }); 64 | let claims = json!({ "exp": REFERENCE_TIME }); 65 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 66 | 67 | let verifier = Verifier::create().leeway(5).build().unwrap(); 68 | let _token_data = verifier 69 | .verify_for_time(token_str, &alg, REFERENCE_TIME + 1) 70 | .unwrap(); 71 | } 72 | 73 | #[test] 74 | fn token_used_exactly_at_nbf_time() { 75 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 76 | let header = json!({ "alg": "HS256" }); 77 | let claims = json!({ "nbf": REFERENCE_TIME }); 78 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 79 | 80 | // "The "nbf" (not before) claim identifies the time before which the JWT 81 | // MUST NOT be accepted for processing. The processing of the "nbf" 82 | // claim requires that the current date/time MUST be after or equal to 83 | // the not-before date/time listed in the "nbf" claim." 84 | // 85 | let verifier = Verifier::create().build().unwrap(); 86 | let _token_data = verifier 87 | .verify_for_time(token_str, &alg, REFERENCE_TIME) 88 | .unwrap(); 89 | } 90 | 91 | #[test] 92 | #[should_panic(expected = "MalformedToken")] 93 | fn token_used_early() { 94 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 95 | let header = json!({ "alg": "HS256" }); 96 | let claims = json!({ "nbf": REFERENCE_TIME + 100 }); 97 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 98 | 99 | // "The "nbf" (not before) claim identifies the time before which the JWT 100 | // MUST NOT be accepted for processing. The processing of the "nbf" 101 | // claim requires that the current date/time MUST be after or equal to 102 | // the not-before date/time listed in the "nbf" claim." 103 | // 104 | let verifier = Verifier::create().build().unwrap(); 105 | let _token_data = verifier 106 | .verify_for_time(token_str, &alg, REFERENCE_TIME) 107 | .unwrap(); 108 | } 109 | 110 | #[test] 111 | fn ignore_token_used_early() { 112 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 113 | let header = json!({ "alg": "HS256" }); 114 | let claims = json!({ "nbf": REFERENCE_TIME + 100 }); 115 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 116 | 117 | let verifier = Verifier::create().ignore_nbf().build().unwrap(); 118 | let _token_data = verifier 119 | .verify_for_time(token_str, &alg, REFERENCE_TIME) 120 | .unwrap(); 121 | } 122 | 123 | #[test] 124 | fn token_used_slightly_early_with_leeway() { 125 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 126 | let header = json!({ "alg": "HS256" }); 127 | let claims = json!({ "nbf": REFERENCE_TIME + 1 }); 128 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 129 | 130 | let verifier = Verifier::create().leeway(5).build().unwrap(); 131 | let _token_data = verifier 132 | .verify_for_time(token_str, &alg, REFERENCE_TIME) 133 | .unwrap(); 134 | } 135 | 136 | #[test] 137 | #[should_panic(expected = "MalformedToken")] 138 | fn token_used_before_issue() { 139 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 140 | let header = json!({ "alg": "HS256" }); 141 | let claims = json!({ "iat": REFERENCE_TIME + 100 }); 142 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 143 | 144 | let verifier = Verifier::create().build().unwrap(); 145 | let _token_data = verifier 146 | .verify_for_time(token_str, &alg, REFERENCE_TIME) 147 | .unwrap(); 148 | } 149 | 150 | #[test] 151 | fn token_used_before_just_before_issue_with_leeway() { 152 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 153 | let header = json!({ "alg": "HS256" }); 154 | let claims = json!({ "iat": REFERENCE_TIME + 1 }); 155 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 156 | 157 | let verifier = Verifier::create().leeway(5).build().unwrap(); 158 | let _token_data = verifier 159 | .verify_for_time(token_str, &alg, REFERENCE_TIME) 160 | .unwrap(); 161 | } 162 | -------------------------------------------------------------------------------- /tests/verifier.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use serde_json::value::Value; 3 | 4 | #[cfg(feature = "matching")] 5 | use regex::Regex; 6 | 7 | use jsonwebtokens as jwt; 8 | use jwt::{Algorithm, AlgorithmID, Verifier}; 9 | 10 | mod common; 11 | use common::get_time; 12 | 13 | #[test] 14 | fn string_equals() { 15 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 16 | let header = json!({ "alg": "HS256" }); 17 | let claims = json!({ 18 | "aud": "test", 19 | "exp": get_time() + 10000, 20 | "my_claim": "foo" 21 | }); 22 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 23 | 24 | let verifier = Verifier::create() 25 | .audience("test") 26 | .string_equals("my_claim", "foo") 27 | .build() 28 | .unwrap(); 29 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 30 | } 31 | 32 | #[test] 33 | #[should_panic(expected = "MalformedToken")] 34 | fn string_equals_failure() { 35 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 36 | let header = json!({ "alg": "HS256" }); 37 | let claims = json!({ 38 | "aud": "test", 39 | "exp": get_time() + 10000, 40 | "my_claim": "foo" 41 | }); 42 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 43 | 44 | let verifier = Verifier::create() 45 | .audience("test") 46 | .string_equals("my_claim", "food") 47 | .build() 48 | .unwrap(); 49 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 50 | } 51 | 52 | #[test] 53 | #[should_panic(expected = "MalformedToken")] 54 | fn string_equals_missing() { 55 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 56 | let header = json!({ "alg": "HS256" }); 57 | let claims = json!({ 58 | "aud": "test", 59 | "exp": get_time() + 10000, 60 | }); 61 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 62 | 63 | let verifier = Verifier::create() 64 | .audience("test") 65 | .string_equals("my_claim", "foo") 66 | .build() 67 | .unwrap(); 68 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 69 | } 70 | 71 | #[cfg(feature = "matching")] 72 | #[test] 73 | fn string_matches() { 74 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 75 | let header = json!({ "alg": "HS256" }); 76 | let claims = json!({ 77 | "aud": "test", 78 | "exp": get_time() + 10000, 79 | "my_claim": "foo" 80 | }); 81 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 82 | 83 | let verifier = Verifier::create() 84 | .audience("test") 85 | .string_matches("my_claim", Regex::new("[fo]+").unwrap()) 86 | .build() 87 | .unwrap(); 88 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 89 | } 90 | 91 | #[cfg(feature = "matching")] 92 | #[test] 93 | #[should_panic(expected = "MalformedToken")] 94 | fn string_matches_failure() { 95 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 96 | let header = json!({ "alg": "HS256" }); 97 | let claims = json!({ 98 | "aud": "test", 99 | "exp": get_time() + 10000, 100 | "my_claim": "foo" 101 | }); 102 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 103 | 104 | let verifier = Verifier::create() 105 | .audience("test") 106 | .string_matches("my_claim", Regex::new("[bar]+").unwrap()) 107 | .build() 108 | .unwrap(); 109 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 110 | } 111 | 112 | #[cfg(feature = "matching")] 113 | #[test] 114 | #[should_panic(expected = "MalformedToken")] 115 | fn string_matches_missing() { 116 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 117 | let header = json!({ "alg": "HS256" }); 118 | let claims = json!({ 119 | "aud": "test", 120 | "exp": get_time() + 10000, 121 | }); 122 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 123 | 124 | let verifier = Verifier::create() 125 | .audience("test") 126 | .string_matches("my_claim", Regex::new("[bar]+").unwrap()) 127 | .build() 128 | .unwrap(); 129 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 130 | } 131 | 132 | #[test] 133 | #[should_panic(expected = "MalformedToken")] 134 | fn string_equals_wrong_type() { 135 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 136 | let header = json!({ "alg": "HS256" }); 137 | let claims = json!({ "my_claim": 1234 }); 138 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 139 | let verifier = Verifier::create() 140 | .string_equals("my_claim", "1234") 141 | .build() 142 | .unwrap(); 143 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 144 | } 145 | 146 | #[test] 147 | fn closure_verify_u64_claim() { 148 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 149 | let header = json!({ "alg": "HS256" }); 150 | let claims = json!({ "my_claim": 1234 }); 151 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 152 | let verifier = Verifier::create() 153 | .claim_callback("my_claim", |v| v.is_u64() && v.as_u64().unwrap() == 1234) 154 | .build() 155 | .unwrap(); 156 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 157 | } 158 | 159 | #[test] 160 | #[should_panic(expected = "MalformedToken")] 161 | fn closure_fail_verify_u64_claim() { 162 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 163 | let header = json!({ "alg": "HS256" }); 164 | let claims = json!({ "my_claim": "1234" }); 165 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 166 | let verifier = Verifier::create() 167 | .claim_callback("my_claim", |v| v.is_u64() && v.as_u64().unwrap() == 1234) 168 | .build() 169 | .unwrap(); 170 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 171 | } 172 | 173 | #[test] 174 | #[should_panic(expected = "MalformedToken")] 175 | fn non_integer_iat() { 176 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 177 | let header = json!({ "alg": "HS256" }); 178 | let claims = json!({ "iat": "1575057015" }); 179 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 180 | let verifier = Verifier::create().build().unwrap(); 181 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 182 | } 183 | 184 | #[test] 185 | #[should_panic(expected = "MalformedToken")] 186 | fn non_integer_exp() { 187 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 188 | let header = json!({ "alg": "HS256" }); 189 | let claims = json!({ "exp": "1575057015" }); 190 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 191 | let verifier = Verifier::create().build().unwrap(); 192 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 193 | } 194 | 195 | #[test] 196 | #[should_panic(expected = "MalformedToken")] 197 | fn non_integer_nbf() { 198 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 199 | let header = json!({ "alg": "HS256" }); 200 | let claims = json!({ "nbf": "1575057015" }); 201 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 202 | let verifier = Verifier::create().build().unwrap(); 203 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 204 | } 205 | 206 | #[test] 207 | #[should_panic(expected = "MalformedToken")] 208 | fn non_string_iss() { 209 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 210 | let header = json!({ "alg": "HS256" }); 211 | let claims = json!({ "iss": 1234 }); 212 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 213 | let verifier = Verifier::create().build().unwrap(); 214 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 215 | } 216 | 217 | #[test] 218 | #[should_panic(expected = "MalformedToken")] 219 | fn non_string_or_array_aud() { 220 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 221 | let header = json!({ "alg": "HS256" }); 222 | let claims = json!({ "aud": 1234 }); 223 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 224 | let verifier = Verifier::create().build().unwrap(); 225 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 226 | } 227 | 228 | #[test] 229 | fn string_or_array_aud() { 230 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 231 | let header = json!({ "alg": "HS256" }); 232 | let claims = json!({ "aud": "ACME" }); 233 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 234 | let verifier = Verifier::create().build().unwrap(); 235 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 236 | 237 | let claims = json!({ "aud": ["ACME", "ACME2"] }); 238 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 239 | let verifier = Verifier::create().build().unwrap(); 240 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 241 | } 242 | 243 | #[test] 244 | #[should_panic(expected = "MalformedToken")] 245 | fn non_string_array_aud() { 246 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 247 | let header = json!({ "alg": "HS256" }); 248 | let claims = json!({ "aud": ["ACME", "ACME2", 123] }); 249 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 250 | let verifier = Verifier::create().build().unwrap(); 251 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 252 | } 253 | 254 | #[test] 255 | #[should_panic(expected = "MalformedToken")] 256 | fn non_string_sub() { 257 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 258 | let header = json!({ "alg": "HS256" }); 259 | let claims = json!({ "sub": 1234 }); 260 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 261 | let verifier = Verifier::create().build().unwrap(); 262 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 263 | } 264 | 265 | #[test] 266 | fn iss_equal() { 267 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 268 | let header = json!({ "alg": "HS256" }); 269 | let claims = json!({ "iss": "ACME" }); 270 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 271 | let verifier = Verifier::create().issuer("ACME").build().unwrap(); 272 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 273 | } 274 | 275 | #[test] 276 | #[should_panic(expected = "MalformedToken")] 277 | fn iss_not_equal() { 278 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 279 | let header = json!({ "alg": "HS256" }); 280 | let claims = json!({ "iss": "ACMEv2" }); 281 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 282 | let verifier = Verifier::create().issuer("ACME").build().unwrap(); 283 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 284 | } 285 | 286 | #[test] 287 | fn aud_equal() { 288 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 289 | let header = json!({ "alg": "HS256" }); 290 | let claims = json!({ "aud": "ACME" }); 291 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 292 | let verifier = Verifier::create().audience("ACME").build().unwrap(); 293 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 294 | } 295 | 296 | #[test] 297 | fn aud_equal_with_array() { 298 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 299 | let header = json!({ "alg": "HS256" }); 300 | let claims = json!({ "aud": ["ACME", "ACME2"] }); 301 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 302 | let verifier = Verifier::create().audience("ACME").build().unwrap(); 303 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 304 | } 305 | 306 | #[test] 307 | #[should_panic(expected = "MalformedToken")] 308 | fn aud_not_equal() { 309 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 310 | let header = json!({ "alg": "HS256" }); 311 | let claims = json!({ "aud": "ACMEv2" }); 312 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 313 | let verifier = Verifier::create().audience("ACME").build().unwrap(); 314 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 315 | } 316 | 317 | #[test] 318 | fn sub_equal() { 319 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 320 | let header = json!({ "alg": "HS256" }); 321 | let claims = json!({ "sub": "ACME" }); 322 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 323 | let verifier = Verifier::create().subject("ACME").build().unwrap(); 324 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 325 | } 326 | 327 | #[test] 328 | #[should_panic(expected = "MalformedToken")] 329 | fn sub_not_equal() { 330 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 331 | let header = json!({ "alg": "HS256" }); 332 | let claims = json!({ "sub": "ACMEv2" }); 333 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 334 | let verifier = Verifier::create().subject("ACME").build().unwrap(); 335 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 336 | } 337 | 338 | #[test] 339 | fn equals_one_of() { 340 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 341 | let header = json!({ "alg": "HS256" }); 342 | let claims = json!({ "my_claim": "value0" }); 343 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 344 | let verifier = Verifier::create() 345 | .string_equals_one_of("my_claim", &["value0", "value1"]) 346 | .build() 347 | .unwrap(); 348 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 349 | } 350 | 351 | #[test] 352 | #[should_panic(expected = "MalformedToken")] 353 | fn equals_one_of_failure() { 354 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 355 | let header = json!({ "alg": "HS256" }); 356 | let claims = json!({ "my_claim": "value0" }); 357 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 358 | let verifier = Verifier::create() 359 | .string_equals_one_of("my_claim", &["value1", "value2"]) 360 | .build() 361 | .unwrap(); 362 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 363 | } 364 | 365 | #[test] 366 | #[should_panic(expected = "MalformedToken")] 367 | fn equals_one_of_wrong_type() { 368 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 369 | let header = json!({ "alg": "HS256" }); 370 | let claims = json!({ "my_claim": 1234 }); 371 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 372 | let verifier = Verifier::create() 373 | .string_equals_one_of("my_claim", &["1234"]) 374 | .build() 375 | .unwrap(); 376 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 377 | } 378 | 379 | #[cfg(feature = "matching")] 380 | #[test] 381 | fn matches_one_of() { 382 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 383 | let header = json!({ "alg": "HS256" }); 384 | let claims0 = json!({ "my_claim": "value0" }); 385 | let token0 = jwt::encode(&header, &claims0, &alg).unwrap(); 386 | let claims1 = json!({ "my_claim": "other3" }); 387 | let token1 = jwt::encode(&header, &claims1, &alg).unwrap(); 388 | let verifier = Verifier::create() 389 | .string_matches_one_of( 390 | "my_claim", 391 | &[ 392 | Regex::new("value[0123]").unwrap(), 393 | Regex::new("other[0123]").unwrap(), 394 | ], 395 | ) 396 | .build() 397 | .unwrap(); 398 | let _claims0: Value = verifier.verify(token0, &alg).unwrap(); 399 | let _claims1: Value = verifier.verify(token1, &alg).unwrap(); 400 | } 401 | 402 | #[cfg(feature = "matching")] 403 | #[test] 404 | #[should_panic(expected = "MalformedToken")] 405 | fn matches_one_of_failure() { 406 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 407 | let header = json!({ "alg": "HS256" }); 408 | let claims = json!({ "my_claim": "value4" }); 409 | let token = jwt::encode(&header, &claims, &alg).unwrap(); 410 | let verifier = Verifier::create() 411 | .string_matches_one_of( 412 | "my_claim", 413 | &[ 414 | Regex::new("value[0123]").unwrap(), 415 | Regex::new("other[0123]").unwrap(), 416 | ], 417 | ) 418 | .build() 419 | .unwrap(); 420 | let _claims: Value = verifier.verify(token, &alg).unwrap(); 421 | } 422 | 423 | #[cfg(feature = "matching")] 424 | #[test] 425 | #[should_panic(expected = "MalformedToken")] 426 | fn matches_one_of_missing() { 427 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 428 | let header = json!({ "alg": "HS256" }); 429 | let claims = json!({ "iss": "ACME" }); 430 | let token = jwt::encode(&header, &claims, &alg).unwrap(); 431 | let verifier = Verifier::create() 432 | .string_matches_one_of( 433 | "my_claim", 434 | &[ 435 | Regex::new("value[0123]").unwrap(), 436 | Regex::new("other[0123]").unwrap(), 437 | ], 438 | ) 439 | .build() 440 | .unwrap(); 441 | let _claims: Value = verifier.verify(token, &alg).unwrap(); 442 | } 443 | 444 | #[test] 445 | #[should_panic(expected = "MalformedToken")] 446 | fn non_string_array_contains() { 447 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 448 | let header = json!({ "alg": "HS256" }); 449 | let claims = json!({ "my_claim": ["value1", "value2", 123] }); 450 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 451 | let verifier = Verifier::create() 452 | .string_or_array_contains("my_claim", "value1") 453 | .build() 454 | .unwrap(); 455 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 456 | } 457 | 458 | #[test] 459 | fn string_array_contains() { 460 | let alg = Algorithm::new_hmac(AlgorithmID::HS256, "secret").unwrap(); 461 | let header = json!({ "alg": "HS256" }); 462 | let claims = json!({ "my_claim": ["value1", "value2", "value3"] }); 463 | let token_str = jwt::encode(&header, &claims, &alg).unwrap(); 464 | let verifier = Verifier::create() 465 | .string_or_array_contains("my_claim", "value2") 466 | .build() 467 | .unwrap(); 468 | let _claims: Value = verifier.verify(token_str, &alg).unwrap(); 469 | } 470 | --------------------------------------------------------------------------------