├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── custom_claims.rs └── hs256.rs ├── src ├── algorithm │ ├── mod.rs │ ├── openssl.rs │ ├── rust_crypto.rs │ └── store.rs ├── claims.rs ├── error.rs ├── header.rs ├── lib.rs └── token │ ├── mod.rs │ ├── signed.rs │ └── verified.rs └── test ├── es256-private.pem ├── es256-public.pem ├── rs256-private.pem ├── rs256-public-2.pem └── rs256-public.pem /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jwt" 3 | version = "0.16.0" 4 | authors = ["Michael Yang "] 5 | description = "JSON Web Token library" 6 | documentation = "https://docs.rs/jwt" 7 | homepage = "http://github.com/mikkyang/rust-jwt" 8 | repository = "http://github.com/mikkyang/rust-jwt" 9 | readme = "README.md" 10 | keywords = ["JWT", "token", "web"] 11 | license = "MIT" 12 | edition = "2018" 13 | 14 | [package.metadata.docs.rs] 15 | features = ["openssl"] 16 | 17 | [dependencies] 18 | base64 = "0.13" 19 | crypto-common = "0.1" 20 | digest = "0.10" 21 | hmac = { version = "0.12", features = ["reset"] } 22 | sha2 = "0.10" 23 | serde = { version = "1.0", features = ["derive"] } 24 | serde_json = "1.0" 25 | 26 | [dependencies.openssl] 27 | version = "0.10" 28 | optional = true 29 | 30 | [dev-dependencies] 31 | doc-comment = "0.3" 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015 Michael Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JWT [![Build Status]][Github Actions] [![Latest Version]][crates.io] [![Documentation]][docs.rs] 2 | 3 | [Build Status]: https://github.com/mikkyang/rust-jwt/actions/workflows/rust.yml/badge.svg?branch=master 4 | [Github Actions]: https://github.com/mikkyang/rust-jwt/actions/workflows/rust.yml?query=branch%3Amaster 5 | [Latest Version]: https://img.shields.io/crates/v/jwt.svg 6 | [crates.io]: https://crates.io/crates/jwt 7 | [Documentation]: https://img.shields.io/badge/rust-documentation-blue.svg 8 | [docs.rs]: https://docs.rs/jwt 9 | 10 | A JSON Web Token library. 11 | 12 | ### Only Claims 13 | 14 | If you don't care about that header as long as the header is verified, signing 15 | and verification can be done with just a few traits. 16 | 17 | #### Signing 18 | 19 | Claims can be any `serde::Serialize` type, usually derived with 20 | `serde_derive`. 21 | 22 | ```rust 23 | use hmac::{Hmac, Mac}; 24 | use jwt::SignWithKey; 25 | use sha2::Sha256; 26 | use std::collections::BTreeMap; 27 | 28 | let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); 29 | let mut claims = BTreeMap::new(); 30 | claims.insert("sub", "someone"); 31 | 32 | let token_str = claims.sign_with_key(&key).unwrap(); 33 | 34 | assert_eq!(token_str, "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lb25lIn0.5wwE1sBrs-vftww_BGIuTVDeHtc1Jsjo-fiHhDwR8m0"); 35 | ``` 36 | 37 | #### Verification 38 | 39 | Claims can be any `serde::Deserialize` type, usually derived with 40 | `serde_derive`. 41 | 42 | ```rust 43 | use hmac::{Hmac, Mac}; 44 | use jwt::VerifyWithKey; 45 | use sha2::Sha256; 46 | use std::collections::BTreeMap; 47 | 48 | let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); 49 | let token_str = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lb25lIn0.5wwE1sBrs-vftww_BGIuTVDeHtc1Jsjo-fiHhDwR8m0"; 50 | 51 | let claims: BTreeMap = token_str.verify_with_key(&key).unwrap(); 52 | 53 | assert_eq!(claims["sub"], "someone"); 54 | ``` 55 | 56 | ### Header and Claims 57 | 58 | If you need to customize the header, you can use the `Token` struct. For 59 | convenience, a `Header` struct is provided for all of the commonly defined 60 | fields, but any type that implements `JoseHeader` can be used. 61 | 62 | #### Signing 63 | 64 | Both header and claims have to implement `serde::Serialize`. 65 | 66 | ```rust 67 | use hmac::{Hmac, Mac}; 68 | use jwt::{AlgorithmType, Header, SignWithKey, Token}; 69 | use sha2::Sha384; 70 | use std::collections::BTreeMap; 71 | 72 | let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); 73 | let header = Header { 74 | algorithm: AlgorithmType::Hs384, 75 | ..Default::default() 76 | }; 77 | let mut claims = BTreeMap::new(); 78 | claims.insert("sub", "someone"); 79 | 80 | let token = Token::new(header, claims).sign_with_key(&key).unwrap(); 81 | 82 | assert_eq!(token.as_str(), "eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJzb21lb25lIn0.WM_WnPUkHK6zm6Wz7zk1kmIxz990Te7nlDjQ3vzcye29szZ-Sj47rLNSTJNzpQd_"); 83 | ``` 84 | 85 | #### Verification 86 | 87 | Both header and claims have to implement `serde::Deserialize`. 88 | 89 | ```rust 90 | use hmac::{Hmac, Mac}; 91 | use jwt::{AlgorithmType, Header, Token, VerifyWithKey}; 92 | use sha2::Sha384; 93 | use std::collections::BTreeMap; 94 | 95 | let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); 96 | let token_str = "eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJzb21lb25lIn0.WM_WnPUkHK6zm6Wz7zk1kmIxz990Te7nlDjQ3vzcye29szZ-Sj47rLNSTJNzpQd_"; 97 | 98 | let token: Token, _> = VerifyWithKey::verify_with_key(token_str, &key).unwrap(); 99 | let header = token.header(); 100 | let claims = token.claims(); 101 | 102 | assert_eq!(header.algorithm, AlgorithmType::Hs384); 103 | assert_eq!(claims["sub"], "someone"); 104 | ``` 105 | 106 | ### Store 107 | A `Store` can be used to represent a collection of keys indexed by key id. Right now, this is only automatically implemented for `BTreeMap` and `HashMap` with `Borrow` keys. If specialization lands then it will be implemented for all `Index<&str>` traits as it was before. 108 | 109 | For the trait `SignWithStore`, the key id will be automatically added to the header for bare claims. 110 | Because claims do not have a way to specify key id, a tuple of key id and claims is necessary. For 111 | tokens, the header's key id will be used to get the key. 112 | 113 | For the trait `VerifyWithStore`, the key id from the deserialized header will be used to choose the key 114 | to use. 115 | 116 | ```rust 117 | use hmac::{Hmac, Mac}; 118 | use jwt::{Header, SignWithStore, Token, VerifyWithStore}; 119 | use sha2::Sha512; 120 | use std::collections::BTreeMap; 121 | 122 | let mut store: BTreeMap<_, Hmac> = BTreeMap::new(); 123 | store.insert("first_key", Hmac::new_from_slice(b"first").unwrap()); 124 | store.insert("second_key", Hmac::new_from_slice(b"second").unwrap()); 125 | 126 | let mut claims = BTreeMap::new(); 127 | claims.insert("sub", "someone"); 128 | 129 | let token_str = ("second_key", claims).sign_with_store(&store).unwrap(); 130 | 131 | assert_eq!(token_str, "eyJhbGciOiJIUzUxMiIsImtpZCI6InNlY29uZF9rZXkifQ.eyJzdWIiOiJzb21lb25lIn0.9gALQon5Mk8r4BjOZ2SJQlauGmT4WUhpN152x9dfKvkPON1VwEN09Id8vjQ0ABlfLJUTVNP36dsdrpYEZDLUcw"); 132 | 133 | let verified_token: Token, _> = token_str.verify_with_store(&store).unwrap(); 134 | assert_eq!(verified_token.claims()["sub"], "someone"); 135 | assert_eq!(verified_token.header().key_id.as_ref().unwrap(), "second_key"); 136 | ``` 137 | 138 | ## Supported Algorithms 139 | 140 | Pure Rust HMAC is supported through [RustCrypto](https://github.com/RustCrypto). Implementations of RSA and ECDSA signatures are supported through OpenSSL, which is not enabled by default. OpenSSL types must be wrapped in the [`PKeyWithDigest`](http://mikkyang.github.io/rust-jwt/doc/jwt/algorithm/openssl/struct.PKeyWithDigest.html) struct. 141 | 142 | * HS256 143 | * HS384 144 | * HS512 145 | * RS256 146 | * RS384 147 | * RS512 148 | * ES256 149 | * ES384 150 | * ES512 151 | -------------------------------------------------------------------------------- /examples/custom_claims.rs: -------------------------------------------------------------------------------- 1 | use hmac::{Hmac, Mac}; 2 | use jwt::{Header, SignWithKey, Token, VerifyWithKey}; 3 | use serde::{Deserialize, Serialize}; 4 | use sha2::Sha256; 5 | 6 | #[derive(Default, Deserialize, Serialize)] 7 | struct Custom { 8 | sub: String, 9 | rhino: bool, 10 | } 11 | 12 | fn new_token(user_id: &str, password: &str) -> Result { 13 | // Dummy auth 14 | if password != "password" { 15 | return Err("Wrong password"); 16 | } 17 | 18 | let header: Header = Default::default(); 19 | let claims = Custom { 20 | sub: user_id.into(), 21 | rhino: true, 22 | }; 23 | let unsigned_token = Token::new(header, claims); 24 | 25 | let key: Hmac = Hmac::new_from_slice(b"secret_key").map_err(|_e| "Invalid key")?; 26 | 27 | let signed_token = unsigned_token 28 | .sign_with_key(&key) 29 | .map_err(|_e| "Sign error")?; 30 | Ok(signed_token.into()) 31 | } 32 | 33 | fn login(token: &str) -> Result { 34 | let key: Hmac = Hmac::new_from_slice(b"secret_key").map_err(|_e| "Invalid key")?; 35 | 36 | let token: Token = 37 | VerifyWithKey::verify_with_key(token, &key).map_err(|_e| "Verification failed")?; 38 | 39 | let (_, claims) = token.into(); 40 | Ok(claims.sub) 41 | } 42 | 43 | fn main() -> Result<(), &'static str> { 44 | let token = new_token("Michael Yang", "password")?; 45 | 46 | let logged_in_user = login(&*token)?; 47 | 48 | assert_eq!(logged_in_user, "Michael Yang"); 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /examples/hs256.rs: -------------------------------------------------------------------------------- 1 | use hmac::{Hmac, Mac}; 2 | use jwt::{RegisteredClaims, SignWithKey, VerifyWithKey}; 3 | use sha2::Sha256; 4 | 5 | fn new_token(user_id: &str, password: &str) -> Result { 6 | // Dummy auth 7 | if password != "password" { 8 | return Err("Wrong password"); 9 | } 10 | 11 | let claims = RegisteredClaims { 12 | issuer: Some("mikkyang.com".into()), 13 | subject: Some(user_id.into()), 14 | ..Default::default() 15 | }; 16 | 17 | let key: Hmac = Hmac::new_from_slice(b"secret_key").map_err(|_e| "Invalid key")?; 18 | 19 | let signed_token = claims.sign_with_key(&key).map_err(|_e| "Sign failed")?; 20 | 21 | Ok(signed_token) 22 | } 23 | 24 | fn login(token: &str) -> Result { 25 | let key: Hmac = Hmac::new_from_slice(b"secret_key").map_err(|_e| "Invalid key")?; 26 | let claims: RegisteredClaims = 27 | VerifyWithKey::verify_with_key(token, &key).map_err(|_e| "Parse failed")?; 28 | 29 | claims.subject.ok_or("Missing subject") 30 | } 31 | 32 | fn main() -> Result<(), &'static str> { 33 | let token = new_token("Michael Yang", "password")?; 34 | 35 | let logged_in_user = login(&*token)?; 36 | 37 | assert_eq!(logged_in_user, "Michael Yang"); 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /src/algorithm/mod.rs: -------------------------------------------------------------------------------- 1 | //! Algorithms capable of signing and verifying tokens. By default only the 2 | //! `hmac` crate's `Hmac` type is supported. For more algorithms, enable the 3 | //! feature `openssl` and see the [openssl](openssl/index.html) 4 | //! module. The `none` algorithm is explicitly not supported. 5 | //! ## Examples 6 | //! ``` 7 | //! use hmac::{Hmac, Mac}; 8 | //! use sha2::Sha256; 9 | //! 10 | //! let hs256_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); 11 | //! ``` 12 | 13 | use serde::{Deserialize, Serialize}; 14 | 15 | use crate::error::Error; 16 | 17 | #[cfg(feature = "openssl")] 18 | pub mod openssl; 19 | pub mod rust_crypto; 20 | pub mod store; 21 | 22 | /// The type of an algorithm, corresponding to the 23 | /// [JWA](https://tools.ietf.org/html/rfc7518) specification. 24 | #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] 25 | #[serde(rename_all = "UPPERCASE")] 26 | pub enum AlgorithmType { 27 | Hs256, 28 | Hs384, 29 | Hs512, 30 | Rs256, 31 | Rs384, 32 | Rs512, 33 | Es256, 34 | Es384, 35 | Es512, 36 | Ps256, 37 | Ps384, 38 | Ps512, 39 | #[serde(rename = "none")] 40 | None, 41 | } 42 | 43 | impl Default for AlgorithmType { 44 | fn default() -> Self { 45 | AlgorithmType::Hs256 46 | } 47 | } 48 | 49 | /// An algorithm capable of signing base64 encoded header and claims strings. 50 | /// strings. 51 | pub trait SigningAlgorithm { 52 | fn algorithm_type(&self) -> AlgorithmType; 53 | 54 | fn sign(&self, header: &str, claims: &str) -> Result; 55 | } 56 | 57 | /// An algorithm capable of verifying base64 encoded header and claims strings. 58 | pub trait VerifyingAlgorithm { 59 | fn algorithm_type(&self) -> AlgorithmType; 60 | 61 | fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result; 62 | 63 | fn verify(&self, header: &str, claims: &str, signature: &str) -> Result { 64 | let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?; 65 | self.verify_bytes(header, claims, &*signature_bytes) 66 | } 67 | } 68 | 69 | // TODO: investigate if these AsRef impls are necessary 70 | impl> VerifyingAlgorithm for T { 71 | fn algorithm_type(&self) -> AlgorithmType { 72 | self.as_ref().algorithm_type() 73 | } 74 | 75 | fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result { 76 | self.as_ref().verify_bytes(header, claims, signature) 77 | } 78 | } 79 | 80 | impl> SigningAlgorithm for T { 81 | fn algorithm_type(&self) -> AlgorithmType { 82 | self.as_ref().algorithm_type() 83 | } 84 | 85 | fn sign(&self, header: &str, claims: &str) -> Result { 86 | self.as_ref().sign(header, claims) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/algorithm/openssl.rs: -------------------------------------------------------------------------------- 1 | //! OpenSSL support through the openssl crate. 2 | //! Note that private keys can only be used for signing and that public keys 3 | //! can only be used for verification. 4 | //! ## Examples 5 | //! ``` 6 | //! use jwt::PKeyWithDigest; 7 | //! use openssl::hash::MessageDigest; 8 | //! use openssl::pkey::PKey; 9 | //! let pem = include_bytes!("../../test/rs256-public.pem"); 10 | //! let rs256_public_key = PKeyWithDigest { 11 | //! digest: MessageDigest::sha256(), 12 | //! key: PKey::public_key_from_pem(pem).unwrap(), 13 | //! }; 14 | //! ``` 15 | 16 | use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm}; 17 | use crate::error::Error; 18 | use crate::SEPARATOR; 19 | 20 | use openssl::bn::BigNum; 21 | use openssl::ecdsa::EcdsaSig; 22 | use openssl::hash::MessageDigest; 23 | use openssl::nid::Nid; 24 | use openssl::pkey::{Id, PKey, Private, Public}; 25 | use openssl::sign::{Signer, Verifier}; 26 | 27 | /// A wrapper class around [PKey](../../../openssl/pkey/struct.PKey.html) that 28 | /// associates the key with a 29 | /// [MessageDigest](../../../openssl/hash/struct.MessageDigest.html). 30 | pub struct PKeyWithDigest { 31 | pub digest: MessageDigest, 32 | pub key: PKey, 33 | } 34 | 35 | impl PKeyWithDigest { 36 | fn algorithm_type(&self) -> AlgorithmType { 37 | match (self.key.id(), self.digest.type_()) { 38 | (Id::RSA, Nid::SHA256) => AlgorithmType::Rs256, 39 | (Id::RSA, Nid::SHA384) => AlgorithmType::Rs384, 40 | (Id::RSA, Nid::SHA512) => AlgorithmType::Rs512, 41 | (Id::EC, Nid::SHA256) => AlgorithmType::Es256, 42 | (Id::EC, Nid::SHA384) => AlgorithmType::Es384, 43 | (Id::EC, Nid::SHA512) => AlgorithmType::Es512, 44 | _ => panic!("Invalid algorithm type"), 45 | } 46 | } 47 | } 48 | 49 | impl SigningAlgorithm for PKeyWithDigest { 50 | fn algorithm_type(&self) -> AlgorithmType { 51 | PKeyWithDigest::algorithm_type(self) 52 | } 53 | 54 | fn sign(&self, header: &str, claims: &str) -> Result { 55 | let mut signer = Signer::new(self.digest.clone(), &self.key)?; 56 | signer.update(header.as_bytes())?; 57 | signer.update(SEPARATOR.as_bytes())?; 58 | signer.update(claims.as_bytes())?; 59 | let signer_signature = signer.sign_to_vec()?; 60 | 61 | let signature = if self.key.id() == Id::EC { 62 | der_to_jose(&signer_signature)? 63 | } else { 64 | signer_signature 65 | }; 66 | 67 | Ok(base64::encode_config(&signature, base64::URL_SAFE_NO_PAD)) 68 | } 69 | } 70 | 71 | impl VerifyingAlgorithm for PKeyWithDigest { 72 | fn algorithm_type(&self) -> AlgorithmType { 73 | PKeyWithDigest::algorithm_type(self) 74 | } 75 | 76 | fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result { 77 | let mut verifier = Verifier::new(self.digest.clone(), &self.key)?; 78 | verifier.update(header.as_bytes())?; 79 | verifier.update(SEPARATOR.as_bytes())?; 80 | verifier.update(claims.as_bytes())?; 81 | 82 | let verified = if self.key.id() == Id::EC { 83 | let der = jose_to_der(signature)?; 84 | verifier.verify(&der)? 85 | } else { 86 | verifier.verify(signature)? 87 | }; 88 | 89 | Ok(verified) 90 | } 91 | } 92 | 93 | /// OpenSSL by default signs ECDSA in DER, but JOSE expects them in a concatenated (R, S) format 94 | fn der_to_jose(der: &[u8]) -> Result, Error> { 95 | let signature = EcdsaSig::from_der(&der)?; 96 | let r = signature.r().to_vec(); 97 | let s = signature.s().to_vec(); 98 | Ok([r, s].concat()) 99 | } 100 | 101 | /// OpenSSL by default verifies ECDSA in DER, but JOSE parses out a concatenated (R, S) format 102 | fn jose_to_der(jose: &[u8]) -> Result, Error> { 103 | let (r, s) = jose.split_at(jose.len() / 2); 104 | let ecdsa_signature = 105 | EcdsaSig::from_private_components(BigNum::from_slice(r)?, BigNum::from_slice(s)?)?; 106 | Ok(ecdsa_signature.to_der()?) 107 | } 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use crate::algorithm::openssl::PKeyWithDigest; 112 | use crate::algorithm::AlgorithmType::*; 113 | use crate::algorithm::{SigningAlgorithm, VerifyingAlgorithm}; 114 | use crate::error::Error; 115 | use crate::header::PrecomputedAlgorithmOnlyHeader as AlgOnly; 116 | use crate::ToBase64; 117 | 118 | use openssl::hash::MessageDigest; 119 | use openssl::pkey::PKey; 120 | 121 | // {"sub":"1234567890","name":"John Doe","admin":true} 122 | const CLAIMS: &'static str = 123 | "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"; 124 | 125 | const RS256_SIGNATURE: &'static str = 126 | "cQsAHF2jHvPGFP5zTD8BgoJrnzEx6JNQCpupebWLFnOc2r_punDDTylI6Ia4JZNkvy2dQP-7W-DEbFQ3oaarHsDndqUgwf9iYlDQxz4Rr2nEZX1FX0-FMEgFPeQpdwveCgjtTYUbVy37ijUySN_rW-xZTrsh_Ug-ica8t-zHRIw"; 127 | 128 | #[test] 129 | fn rs256_sign() -> Result<(), Error> { 130 | let pem = include_bytes!("../../test/rs256-private.pem"); 131 | 132 | let algorithm = PKeyWithDigest { 133 | digest: MessageDigest::sha256(), 134 | key: PKey::private_key_from_pem(pem)?, 135 | }; 136 | 137 | let result = algorithm.sign(&AlgOnly(Rs256).to_base64()?, CLAIMS)?; 138 | assert_eq!(result, RS256_SIGNATURE); 139 | Ok(()) 140 | } 141 | 142 | #[test] 143 | fn rs256_verify() -> Result<(), Error> { 144 | let pem = include_bytes!("../../test/rs256-public.pem"); 145 | 146 | let algorithm = PKeyWithDigest { 147 | digest: MessageDigest::sha256(), 148 | key: PKey::public_key_from_pem(pem)?, 149 | }; 150 | 151 | let verification_result = 152 | algorithm.verify(&AlgOnly(Rs256).to_base64()?, CLAIMS, RS256_SIGNATURE)?; 153 | assert!(verification_result); 154 | Ok(()) 155 | } 156 | 157 | #[test] 158 | fn es256() -> Result<(), Error> { 159 | let private_pem = include_bytes!("../../test/es256-private.pem"); 160 | let private_key = PKeyWithDigest { 161 | digest: MessageDigest::sha256(), 162 | key: PKey::private_key_from_pem(private_pem)?, 163 | }; 164 | 165 | let signature = private_key.sign(&AlgOnly(Es256).to_base64()?, CLAIMS)?; 166 | 167 | let public_pem = include_bytes!("../../test/es256-public.pem"); 168 | 169 | let public_key = PKeyWithDigest { 170 | digest: MessageDigest::sha256(), 171 | key: PKey::public_key_from_pem(public_pem)?, 172 | }; 173 | 174 | let verification_result = 175 | public_key.verify(&AlgOnly(Es256).to_base64()?, CLAIMS, &*signature)?; 176 | assert!(verification_result); 177 | Ok(()) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/algorithm/rust_crypto.rs: -------------------------------------------------------------------------------- 1 | //! RustCrypto implementations of signing and verifying algorithms. 2 | //! According to that organization, only hmac is safely implemented at the 3 | //! moment. 4 | 5 | use digest::{ 6 | block_buffer::Eager, 7 | consts::U256, 8 | core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore}, 9 | generic_array::typenum::{IsLess, Le, NonZero}, 10 | HashMarker, 11 | }; 12 | use hmac::{Hmac, Mac}; 13 | 14 | use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm}; 15 | use crate::error::Error; 16 | use crate::SEPARATOR; 17 | /// A trait used to make the implementation of `SigningAlgorithm` and 18 | /// `VerifyingAlgorithm` easier. 19 | /// RustCrypto crates tend to have algorithm types defined at the type level, 20 | /// so they cannot accept a self argument. 21 | pub trait TypeLevelAlgorithmType { 22 | fn algorithm_type() -> AlgorithmType; 23 | } 24 | 25 | macro_rules! type_level_algorithm_type { 26 | ($rust_crypto_type: ty, $algorithm_type: expr) => { 27 | impl TypeLevelAlgorithmType for $rust_crypto_type { 28 | fn algorithm_type() -> AlgorithmType { 29 | $algorithm_type 30 | } 31 | } 32 | }; 33 | } 34 | 35 | type_level_algorithm_type!(sha2::Sha256, AlgorithmType::Hs256); 36 | type_level_algorithm_type!(sha2::Sha384, AlgorithmType::Hs384); 37 | type_level_algorithm_type!(sha2::Sha512, AlgorithmType::Hs512); 38 | 39 | impl SigningAlgorithm for Hmac 40 | where 41 | D: CoreProxy + TypeLevelAlgorithmType, 42 | D::Core: HashMarker 43 | + BufferKindUser 44 | + FixedOutputCore 45 | + digest::Reset 46 | + Default 47 | + Clone, 48 | ::BlockSize: IsLess, 49 | Le<::BlockSize, U256>: NonZero, 50 | { 51 | fn algorithm_type(&self) -> AlgorithmType { 52 | D::algorithm_type() 53 | } 54 | 55 | fn sign(&self, header: &str, claims: &str) -> Result { 56 | let hmac = get_hmac_with_data(self, header, claims); 57 | let mac_result = hmac.finalize(); 58 | let code = mac_result.into_bytes(); 59 | Ok(base64::encode_config(&code, base64::URL_SAFE_NO_PAD)) 60 | } 61 | } 62 | 63 | impl VerifyingAlgorithm for Hmac 64 | where 65 | D: CoreProxy + TypeLevelAlgorithmType, 66 | D::Core: HashMarker 67 | + BufferKindUser 68 | + FixedOutputCore 69 | + digest::Reset 70 | + Default 71 | + Clone, 72 | ::BlockSize: IsLess, 73 | Le<::BlockSize, U256>: NonZero, 74 | { 75 | fn algorithm_type(&self) -> AlgorithmType { 76 | D::algorithm_type() 77 | } 78 | 79 | fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result { 80 | let hmac = get_hmac_with_data(self, header, claims); 81 | hmac.verify_slice(signature)?; 82 | Ok(true) 83 | } 84 | } 85 | 86 | fn get_hmac_with_data(hmac: &Hmac, header: &str, claims: &str) -> Hmac 87 | where 88 | D: CoreProxy, 89 | D::Core: HashMarker 90 | + BufferKindUser 91 | + FixedOutputCore 92 | + digest::Reset 93 | + Default 94 | + Clone, 95 | ::BlockSize: IsLess, 96 | Le<::BlockSize, U256>: NonZero, 97 | { 98 | let mut hmac = hmac.clone(); 99 | hmac.reset(); 100 | hmac.update(header.as_bytes()); 101 | hmac.update(SEPARATOR.as_bytes()); 102 | hmac.update(claims.as_bytes()); 103 | hmac 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | use crate::algorithm::{SigningAlgorithm, VerifyingAlgorithm}; 109 | use crate::error::Error; 110 | use hmac::{Hmac, Mac}; 111 | use sha2::Sha256; 112 | 113 | #[test] 114 | pub fn sign() -> Result<(), Error> { 115 | let header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; 116 | let claims = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"; 117 | let expected_signature = "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; 118 | 119 | let signer: Hmac = Hmac::new_from_slice(b"secret")?; 120 | let computed_signature = SigningAlgorithm::sign(&signer, header, claims)?; 121 | 122 | assert_eq!(computed_signature, expected_signature); 123 | Ok(()) 124 | } 125 | 126 | #[test] 127 | pub fn verify() -> Result<(), Error> { 128 | let header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; 129 | let claims = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"; 130 | let signature = "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; 131 | 132 | let verifier: Hmac = Hmac::new_from_slice(b"secret")?; 133 | assert!(VerifyingAlgorithm::verify( 134 | &verifier, header, claims, signature 135 | )?); 136 | Ok(()) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/algorithm/store.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use std::collections::{BTreeMap, HashMap}; 3 | use std::hash::Hash; 4 | 5 | /// A store of keys that can be retrieved by key id. 6 | pub trait Store { 7 | type Algorithm: ?Sized; 8 | 9 | fn get(&self, key_id: &str) -> Option<&Self::Algorithm>; 10 | } 11 | 12 | impl Store for BTreeMap 13 | where 14 | K: Borrow + Ord, 15 | { 16 | type Algorithm = A; 17 | 18 | fn get(&self, key_id: &str) -> Option<&A> { 19 | BTreeMap::get(self, key_id) 20 | } 21 | } 22 | 23 | impl Store for HashMap 24 | where 25 | K: Borrow + Ord + Hash, 26 | { 27 | type Algorithm = A; 28 | 29 | fn get(&self, key_id: &str) -> Option<&A> { 30 | HashMap::get(self, key_id) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/claims.rs: -------------------------------------------------------------------------------- 1 | //! Convenience structs for commonly defined fields in claims. 2 | 3 | use std::collections::BTreeMap; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | /// Generic [JWT claims](https://tools.ietf.org/html/rfc7519#page-8) with 8 | /// defined fields for registered and private claims. 9 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 10 | pub struct Claims { 11 | #[serde(flatten)] 12 | pub registered: RegisteredClaims, 13 | #[serde(flatten)] 14 | pub private: BTreeMap, 15 | } 16 | 17 | impl Claims { 18 | pub fn new(registered: RegisteredClaims) -> Self { 19 | Claims { 20 | registered, 21 | private: BTreeMap::new(), 22 | } 23 | } 24 | } 25 | 26 | pub type SecondsSinceEpoch = u64; 27 | 28 | /// Registered claims according to the 29 | /// [JWT specification](https://tools.ietf.org/html/rfc7519#page-9). 30 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 31 | pub struct RegisteredClaims { 32 | #[serde(rename = "iss", skip_serializing_if = "Option::is_none")] 33 | pub issuer: Option, 34 | 35 | #[serde(rename = "sub", skip_serializing_if = "Option::is_none")] 36 | pub subject: Option, 37 | 38 | #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] 39 | pub audience: Option, 40 | 41 | #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] 42 | pub expiration: Option, 43 | 44 | #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] 45 | pub not_before: Option, 46 | 47 | #[serde(rename = "iat", skip_serializing_if = "Option::is_none")] 48 | pub issued_at: Option, 49 | 50 | #[serde(rename = "jti", skip_serializing_if = "Option::is_none")] 51 | pub json_web_token_id: Option, 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use crate::claims::Claims; 57 | use crate::error::Error; 58 | use crate::{FromBase64, ToBase64}; 59 | use serde_json::Value; 60 | use std::default::Default; 61 | 62 | // {"iss":"mikkyang.com","exp":1302319100,"custom_claim":true} 63 | const ENCODED_PAYLOAD: &str = 64 | "eyJpc3MiOiJtaWtreWFuZy5jb20iLCJleHAiOjEzMDIzMTkxMDAsImN1c3RvbV9jbGFpbSI6dHJ1ZX0K"; 65 | 66 | #[test] 67 | fn registered_claims() -> Result<(), Error> { 68 | let claims = Claims::from_base64(ENCODED_PAYLOAD)?; 69 | 70 | assert_eq!(claims.registered.issuer.unwrap(), "mikkyang.com"); 71 | assert_eq!(claims.registered.expiration.unwrap(), 1302319100); 72 | Ok(()) 73 | } 74 | 75 | #[test] 76 | fn private_claims() -> Result<(), Error> { 77 | let claims = Claims::from_base64(ENCODED_PAYLOAD)?; 78 | 79 | assert_eq!(claims.private["custom_claim"], Value::Bool(true)); 80 | Ok(()) 81 | } 82 | 83 | #[test] 84 | fn roundtrip() -> Result<(), Error> { 85 | let mut claims: Claims = Default::default(); 86 | claims.registered.issuer = Some("mikkyang.com".into()); 87 | claims.registered.expiration = Some(1302319100); 88 | let enc = claims.to_base64()?; 89 | assert_eq!(claims, Claims::from_base64(&*enc)?); 90 | Ok(()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::string::FromUtf8Error; 3 | 4 | use base64::DecodeError; 5 | use crypto_common::InvalidLength; 6 | use digest::MacError; 7 | use serde_json::Error as JsonError; 8 | 9 | use self::Error::*; 10 | use crate::algorithm::AlgorithmType; 11 | 12 | #[derive(Debug)] 13 | pub enum Error { 14 | AlgorithmMismatch(AlgorithmType, AlgorithmType), 15 | Base64(DecodeError), 16 | Format, 17 | InvalidSignature, 18 | Json(JsonError), 19 | NoClaimsComponent, 20 | NoHeaderComponent, 21 | NoKeyId, 22 | NoKeyWithKeyId(String), 23 | NoSignatureComponent, 24 | RustCryptoMac(MacError), 25 | RustCryptoMacKeyLength(InvalidLength), 26 | TooManyComponents, 27 | Utf8(FromUtf8Error), 28 | #[cfg(feature = "openssl")] 29 | OpenSsl(openssl::error::ErrorStack), 30 | } 31 | 32 | impl fmt::Display for Error { 33 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 34 | match *self { 35 | AlgorithmMismatch(a, b) => { 36 | write!(f, "Expected algorithm type {:?} but found {:?}", a, b) 37 | } 38 | NoKeyId => write!(f, "No key id found"), 39 | NoKeyWithKeyId(ref kid) => write!(f, "Key with key id {} not found", kid), 40 | NoHeaderComponent => write!(f, "No header component found in token string"), 41 | NoClaimsComponent => write!(f, "No claims component found in token string"), 42 | NoSignatureComponent => write!(f, "No signature component found in token string"), 43 | TooManyComponents => write!(f, "Too many components found in token string"), 44 | Format => write!(f, "Format"), 45 | InvalidSignature => write!(f, "Invalid signature"), 46 | Base64(ref x) => write!(f, "{}", x), 47 | Json(ref x) => write!(f, "{}", x), 48 | Utf8(ref x) => write!(f, "{}", x), 49 | RustCryptoMac(ref x) => write!(f, "{}", x), 50 | RustCryptoMacKeyLength(ref x) => write!(f, "{}", x), 51 | #[cfg(feature = "openssl")] 52 | OpenSsl(ref x) => write!(f, "{}", x), 53 | } 54 | } 55 | } 56 | 57 | impl std::error::Error for Error {} 58 | 59 | macro_rules! error_wrap { 60 | ($f:ty, $e:expr) => { 61 | impl From<$f> for Error { 62 | fn from(f: $f) -> Error { 63 | $e(f) 64 | } 65 | } 66 | }; 67 | } 68 | 69 | error_wrap!(DecodeError, Base64); 70 | error_wrap!(JsonError, Json); 71 | error_wrap!(FromUtf8Error, Utf8); 72 | error_wrap!(MacError, RustCryptoMac); 73 | error_wrap!(InvalidLength, RustCryptoMacKeyLength); 74 | #[cfg(feature = "openssl")] 75 | error_wrap!(openssl::error::ErrorStack, Error::OpenSsl); 76 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | //! Convenience structs for commonly defined fields in headers. 2 | 3 | use std::borrow::Cow; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::algorithm::AlgorithmType; 8 | use crate::error::Error; 9 | use crate::ToBase64; 10 | 11 | /// A trait for any header than can conform to the 12 | /// [JWT specification](https://tools.ietf.org/html/rfc7519#page-11). 13 | pub trait JoseHeader { 14 | fn algorithm_type(&self) -> AlgorithmType; 15 | 16 | fn key_id(&self) -> Option<&str> { 17 | None 18 | } 19 | 20 | fn type_(&self) -> Option { 21 | None 22 | } 23 | 24 | fn content_type(&self) -> Option { 25 | None 26 | } 27 | } 28 | 29 | /// Generic [JWT header](https://tools.ietf.org/html/rfc7519#page-11) with 30 | /// defined fields for common fields. 31 | #[derive(Default, Debug, PartialEq, Serialize, Deserialize)] 32 | pub struct Header { 33 | #[serde(rename = "alg")] 34 | pub algorithm: AlgorithmType, 35 | 36 | #[serde(rename = "kid", skip_serializing_if = "Option::is_none")] 37 | pub key_id: Option, 38 | 39 | #[serde(rename = "typ", skip_serializing_if = "Option::is_none")] 40 | pub type_: Option, 41 | 42 | #[serde(rename = "cty", skip_serializing_if = "Option::is_none")] 43 | pub content_type: Option, 44 | } 45 | 46 | impl JoseHeader for Header { 47 | fn algorithm_type(&self) -> AlgorithmType { 48 | self.algorithm 49 | } 50 | 51 | fn key_id(&self) -> Option<&str> { 52 | self.key_id.as_deref() 53 | } 54 | 55 | fn type_(&self) -> Option { 56 | self.type_ 57 | } 58 | 59 | fn content_type(&self) -> Option { 60 | self.content_type 61 | } 62 | } 63 | 64 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 65 | #[serde(rename_all = "UPPERCASE")] 66 | pub enum HeaderType { 67 | #[serde(rename = "JWT")] 68 | JsonWebToken, 69 | } 70 | 71 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 72 | pub enum HeaderContentType { 73 | #[serde(rename = "JWT")] 74 | JsonWebToken, 75 | } 76 | 77 | /// A header that only contains the algorithm type. The `ToBase64` 78 | /// implementation uses static strings for faster serialization. 79 | pub struct PrecomputedAlgorithmOnlyHeader(pub AlgorithmType); 80 | 81 | impl JoseHeader for PrecomputedAlgorithmOnlyHeader { 82 | fn algorithm_type(&self) -> AlgorithmType { 83 | let PrecomputedAlgorithmOnlyHeader(algorithm_type) = *self; 84 | algorithm_type 85 | } 86 | } 87 | 88 | impl ToBase64 for PrecomputedAlgorithmOnlyHeader { 89 | fn to_base64(&self) -> Result, Error> { 90 | let precomputed_str = match self.algorithm_type() { 91 | AlgorithmType::Hs256 => "eyJhbGciOiAiSFMyNTYifQ", 92 | AlgorithmType::Hs384 => "eyJhbGciOiAiSFMzODQifQ", 93 | AlgorithmType::Hs512 => "eyJhbGciOiAiSFM1MTIifQ", 94 | AlgorithmType::Rs256 => "eyJhbGciOiAiUlMyNTYifQ", 95 | AlgorithmType::Rs384 => "eyJhbGciOiAiUlMzODQifQ", 96 | AlgorithmType::Rs512 => "eyJhbGciOiAiUlM1MTIifQ", 97 | AlgorithmType::Es256 => "eyJhbGciOiAiRVMyNTYifQ", 98 | AlgorithmType::Es384 => "eyJhbGciOiAiRVMzODQifQ", 99 | AlgorithmType::Es512 => "eyJhbGciOiAiRVM1MTIifQ", 100 | AlgorithmType::Ps256 => "eyJhbGciOiAiUFMyNTYifQ", 101 | AlgorithmType::Ps384 => "eyJhbGciOiAiUFMzODQifQ", 102 | AlgorithmType::Ps512 => "eyJhbGciOiAiUFM1MTIifQ", 103 | AlgorithmType::None => "eyJhbGciOiAibm9uZSJ9Cg", 104 | }; 105 | 106 | Ok(Cow::Borrowed(precomputed_str)) 107 | } 108 | } 109 | 110 | /// A header with a borrowed key. Used for signing claims with a Store 111 | /// conveniently. 112 | #[derive(Serialize)] 113 | pub(crate) struct BorrowedKeyHeader<'a> { 114 | #[serde(rename = "alg")] 115 | pub algorithm: AlgorithmType, 116 | 117 | #[serde(rename = "kid")] 118 | pub key_id: &'a str, 119 | } 120 | 121 | impl<'a> JoseHeader for BorrowedKeyHeader<'a> { 122 | fn algorithm_type(&self) -> AlgorithmType { 123 | self.algorithm 124 | } 125 | 126 | fn key_id(&self) -> Option<&str> { 127 | Some(self.key_id) 128 | } 129 | } 130 | 131 | #[cfg(test)] 132 | mod tests { 133 | use crate::algorithm::AlgorithmType; 134 | use crate::error::Error; 135 | use crate::header::{Header, HeaderType, PrecomputedAlgorithmOnlyHeader}; 136 | use crate::{FromBase64, ToBase64}; 137 | 138 | #[test] 139 | fn from_base64() -> Result<(), Error> { 140 | let enc = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; 141 | let header = Header::from_base64(enc)?; 142 | 143 | assert_eq!(header.type_.unwrap(), HeaderType::JsonWebToken); 144 | assert_eq!(header.algorithm, AlgorithmType::Hs256); 145 | 146 | let enc = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFLU0YzZyJ9"; 147 | let header = Header::from_base64(enc)?; 148 | 149 | assert_eq!(header.key_id.unwrap(), "1KSF3g".to_string()); 150 | assert_eq!(header.algorithm, AlgorithmType::Rs256); 151 | 152 | Ok(()) 153 | } 154 | 155 | #[test] 156 | fn roundtrip() -> Result<(), Error> { 157 | let header: Header = Default::default(); 158 | let enc = header.to_base64()?; 159 | assert_eq!(header, Header::from_base64(&*enc)?); 160 | Ok(()) 161 | } 162 | 163 | #[test] 164 | fn precomputed_headers() -> Result<(), Error> { 165 | let algorithms = [ 166 | AlgorithmType::Hs256, 167 | AlgorithmType::Hs384, 168 | AlgorithmType::Hs512, 169 | AlgorithmType::Rs256, 170 | AlgorithmType::Rs384, 171 | AlgorithmType::Rs512, 172 | AlgorithmType::Es256, 173 | AlgorithmType::Es384, 174 | AlgorithmType::Es512, 175 | AlgorithmType::Ps256, 176 | AlgorithmType::Ps384, 177 | AlgorithmType::Ps512, 178 | AlgorithmType::None, 179 | ]; 180 | 181 | for algorithm in algorithms.iter() { 182 | let precomputed = PrecomputedAlgorithmOnlyHeader(*algorithm); 183 | let precomputed_str = precomputed.to_base64()?; 184 | 185 | let header = Header::from_base64(&*precomputed_str)?; 186 | assert_eq!(*algorithm, header.algorithm); 187 | } 188 | 189 | Ok(()) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ### Only Claims 2 | //! If you don't care about that header as long as the header is verified, signing 3 | //! and verification can be done with just a few traits. 4 | //! #### Signing 5 | //! Claims can be any `serde::Serialize` type, usually derived with 6 | //! `serde_derive`. 7 | //! ```rust 8 | //! use hmac::{Hmac, Mac}; 9 | //! use jwt::SignWithKey; 10 | //! use sha2::Sha256; 11 | //! use std::collections::BTreeMap; 12 | //! 13 | //! # use jwt::Error; 14 | //! # fn try_main() -> Result<(), Error> { 15 | //! let key: Hmac = Hmac::new_from_slice(b"some-secret")?; 16 | //! let mut claims = BTreeMap::new(); 17 | //! claims.insert("sub", "someone"); 18 | //! let token_str = claims.sign_with_key(&key)?; 19 | //! assert_eq!(token_str, "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lb25lIn0.5wwE1sBrs-vftww_BGIuTVDeHtc1Jsjo-fiHhDwR8m0"); 20 | //! # Ok(()) 21 | //! # } 22 | //! # try_main().unwrap() 23 | //! ``` 24 | //! #### Verification 25 | //! Claims can be any `serde::Deserialize` type, usually derived with 26 | //! `serde_derive`. 27 | //! ```rust 28 | //! use hmac::{Hmac, Mac}; 29 | //! use jwt::VerifyWithKey; 30 | //! use sha2::Sha256; 31 | //! use std::collections::BTreeMap; 32 | //! 33 | //! # use jwt::Error; 34 | //! # fn try_main() -> Result<(), Error> { 35 | //! let key: Hmac = Hmac::new_from_slice(b"some-secret")?; 36 | //! let token_str = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lb25lIn0.5wwE1sBrs-vftww_BGIuTVDeHtc1Jsjo-fiHhDwR8m0"; 37 | //! let claims: BTreeMap = token_str.verify_with_key(&key)?; 38 | //! assert_eq!(claims["sub"], "someone"); 39 | //! # Ok(()) 40 | //! # } 41 | //! # try_main().unwrap() 42 | //! ``` 43 | //! ### Header and Claims 44 | //! If you need to customize the header, you can use the `Token` struct. For 45 | //! convenience, a `Header` struct is provided for all of the commonly defined 46 | //! fields, but any type that implements `JoseHeader` can be used. 47 | //! #### Signing 48 | //! Both header and claims have to implement `serde::Serialize`. 49 | //! ```rust 50 | //! use hmac::{Hmac, Mac}; 51 | //! use jwt::{AlgorithmType, Header, SignWithKey, Token}; 52 | //! use sha2::Sha384; 53 | //! use std::collections::BTreeMap; 54 | //! 55 | //! # use jwt::Error; 56 | //! # fn try_main() -> Result<(), Error> { 57 | //! let key: Hmac = Hmac::new_from_slice(b"some-secret")?; 58 | //! let header = Header { 59 | //! algorithm: AlgorithmType::Hs384, 60 | //! ..Default::default() 61 | //! }; 62 | //! let mut claims = BTreeMap::new(); 63 | //! claims.insert("sub", "someone"); 64 | //! let token = Token::new(header, claims).sign_with_key(&key)?; 65 | //! assert_eq!(token.as_str(), "eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJzb21lb25lIn0.WM_WnPUkHK6zm6Wz7zk1kmIxz990Te7nlDjQ3vzcye29szZ-Sj47rLNSTJNzpQd_"); 66 | //! # Ok(()) 67 | //! # } 68 | //! # try_main().unwrap() 69 | //! ``` 70 | //! #### Verification 71 | //! Both header and claims have to implement `serde::Deserialize`. 72 | //! ```rust 73 | //! use hmac::{Hmac, Mac}; 74 | //! use jwt::{AlgorithmType, Header, Token, VerifyWithKey}; 75 | //! use sha2::Sha384; 76 | //! use std::collections::BTreeMap; 77 | //! 78 | //! # use jwt::Error; 79 | //! # fn try_main() -> Result<(), Error> { 80 | //! let key: Hmac = Hmac::new_from_slice(b"some-secret")?; 81 | //! let token_str = "eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJzb21lb25lIn0.WM_WnPUkHK6zm6Wz7zk1kmIxz990Te7nlDjQ3vzcye29szZ-Sj47rLNSTJNzpQd_"; 82 | //! let token: Token, _> = token_str.verify_with_key(&key)?; 83 | //! let header = token.header(); 84 | //! let claims = token.claims(); 85 | //! assert_eq!(header.algorithm, AlgorithmType::Hs384); 86 | //! assert_eq!(claims["sub"], "someone"); 87 | //! # Ok(()) 88 | //! # } 89 | //! # try_main().unwrap() 90 | //! ``` 91 | 92 | #[cfg(doctest)] 93 | doctest!("../README.md"); 94 | 95 | use std::borrow::Cow; 96 | 97 | #[cfg(doctest)] 98 | use doc_comment::doctest; 99 | use serde::{Deserialize, Serialize}; 100 | 101 | #[cfg(feature = "openssl")] 102 | pub use crate::algorithm::openssl::PKeyWithDigest; 103 | pub use crate::algorithm::store::Store; 104 | pub use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm}; 105 | pub use crate::claims::Claims; 106 | pub use crate::claims::RegisteredClaims; 107 | pub use crate::error::Error; 108 | pub use crate::header::{Header, JoseHeader}; 109 | pub use crate::token::signed::{SignWithKey, SignWithStore}; 110 | pub use crate::token::verified::{VerifyWithKey, VerifyWithStore}; 111 | pub use crate::token::{Unsigned, Unverified, Verified}; 112 | 113 | pub mod algorithm; 114 | pub mod claims; 115 | pub mod error; 116 | pub mod header; 117 | pub mod token; 118 | 119 | const SEPARATOR: &str = "."; 120 | 121 | /// Representation of a structured JWT. Methods vary based on the signature 122 | /// type `S`. 123 | pub struct Token { 124 | header: H, 125 | claims: C, 126 | signature: S, 127 | } 128 | 129 | impl Token { 130 | pub fn header(&self) -> &H { 131 | &self.header 132 | } 133 | 134 | pub fn claims(&self) -> &C { 135 | &self.claims 136 | } 137 | 138 | pub fn remove_signature(self) -> Token { 139 | Token { 140 | header: self.header, 141 | claims: self.claims, 142 | signature: Unsigned, 143 | } 144 | } 145 | } 146 | 147 | impl From> for (H, C) { 148 | fn from(token: Token) -> Self { 149 | (token.header, token.claims) 150 | } 151 | } 152 | 153 | /// A trait used to convert objects in base64 encoding. The return type can 154 | /// be either owned if the header is dynamic, or it can be borrowed if the 155 | /// header is a static, pre-computed value. It is implemented automatically 156 | /// for every type that implements 157 | /// [Serialize](../../serde/trait.Serialize.html). as a base64 encoding of 158 | /// the object's JSON representation. 159 | pub trait ToBase64 { 160 | fn to_base64(&self) -> Result, Error>; 161 | } 162 | 163 | impl ToBase64 for T { 164 | fn to_base64(&self) -> Result, Error> { 165 | let json_bytes = serde_json::to_vec(&self)?; 166 | let encoded_json_bytes = base64::encode_config(&json_bytes, base64::URL_SAFE_NO_PAD); 167 | Ok(Cow::Owned(encoded_json_bytes)) 168 | } 169 | } 170 | 171 | /// A trait used to parse objects from base64 encoding. The return type can 172 | /// be either owned if the header is dynamic, or it can be borrowed if the 173 | /// header is a static, pre-computed value. It is implemented automatically 174 | /// for every type that implements 175 | /// [DeserializeOwned](../../serde/trait.Deserialize.html) for 176 | /// the base64 encoded JSON representation. 177 | pub trait FromBase64: Sized { 178 | fn from_base64>(raw: &Input) -> Result; 179 | } 180 | 181 | impl Deserialize<'de> + Sized> FromBase64 for T { 182 | fn from_base64>(raw: &Input) -> Result { 183 | let json_bytes = base64::decode_config(raw, base64::URL_SAFE_NO_PAD)?; 184 | Ok(serde_json::from_slice(&json_bytes)?) 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod tests { 190 | use crate::algorithm::AlgorithmType::Hs256; 191 | use crate::error::Error; 192 | use crate::header::Header; 193 | use crate::token::signed::SignWithKey; 194 | use crate::token::verified::VerifyWithKey; 195 | use crate::Claims; 196 | use crate::Token; 197 | use hmac::Hmac; 198 | use hmac::Mac; 199 | use sha2::Sha256; 200 | 201 | #[test] 202 | pub fn raw_data() -> Result<(), Error> { 203 | let raw = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; 204 | let token: Token = Token::parse_unverified(raw)?; 205 | 206 | assert_eq!(token.header.algorithm, Hs256); 207 | 208 | let verifier: Hmac = Hmac::new_from_slice(b"secret")?; 209 | assert!(token.verify_with_key(&verifier).is_ok()); 210 | 211 | Ok(()) 212 | } 213 | 214 | #[test] 215 | pub fn roundtrip() -> Result<(), Error> { 216 | let token: Token = Default::default(); 217 | let key: Hmac = Hmac::new_from_slice(b"secret")?; 218 | let signed_token = token.sign_with_key(&key)?; 219 | let signed_token_str = signed_token.as_str(); 220 | 221 | let recreated_token: Token = Token::parse_unverified(signed_token_str)?; 222 | 223 | assert_eq!(signed_token.header(), recreated_token.header()); 224 | assert_eq!(signed_token.claims(), recreated_token.claims()); 225 | recreated_token.verify_with_key(&key)?; 226 | Ok(()) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/token/mod.rs: -------------------------------------------------------------------------------- 1 | //! A structured representation of a JWT. 2 | 3 | pub mod signed; 4 | pub mod verified; 5 | 6 | pub struct Unsigned; 7 | 8 | pub struct Signed { 9 | pub token_string: String, 10 | } 11 | 12 | pub struct Verified; 13 | 14 | pub struct Unverified<'a> { 15 | pub header_str: &'a str, 16 | pub claims_str: &'a str, 17 | pub signature_str: &'a str, 18 | } 19 | -------------------------------------------------------------------------------- /src/token/signed.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::store::Store; 2 | use crate::algorithm::SigningAlgorithm; 3 | use crate::error::Error; 4 | use crate::header::{BorrowedKeyHeader, Header, JoseHeader}; 5 | use crate::token::{Signed, Unsigned}; 6 | use crate::{ToBase64, Token, SEPARATOR}; 7 | 8 | /// Allow objects to be signed with a key. 9 | pub trait SignWithKey { 10 | fn sign_with_key(self, key: &impl SigningAlgorithm) -> Result; 11 | } 12 | 13 | /// Allow objects to be signed with a store. 14 | pub trait SignWithStore { 15 | fn sign_with_store(self, store: &S) -> Result 16 | where 17 | S: Store, 18 | A: SigningAlgorithm; 19 | } 20 | 21 | impl Token { 22 | /// Create a new unsigned token, with mutable headers and claims. 23 | pub fn new(header: H, claims: C) -> Self { 24 | Token { 25 | header, 26 | claims, 27 | signature: Unsigned, 28 | } 29 | } 30 | 31 | pub fn header_mut(&mut self) -> &mut H { 32 | &mut self.header 33 | } 34 | 35 | pub fn claims_mut(&mut self) -> &mut C { 36 | &mut self.claims 37 | } 38 | } 39 | 40 | impl Default for Token 41 | where 42 | H: Default, 43 | C: Default, 44 | { 45 | fn default() -> Self { 46 | Token::new(H::default(), C::default()) 47 | } 48 | } 49 | 50 | impl SignWithKey for C { 51 | fn sign_with_key(self, key: &impl SigningAlgorithm) -> Result { 52 | let header = Header { 53 | algorithm: key.algorithm_type(), 54 | ..Default::default() 55 | }; 56 | 57 | let token = Token::new(header, self).sign_with_key(key)?; 58 | Ok(token.signature.token_string) 59 | } 60 | } 61 | 62 | impl<'a, C: ToBase64> SignWithStore for (&'a str, C) { 63 | fn sign_with_store(self, store: &S) -> Result 64 | where 65 | S: Store, 66 | A: SigningAlgorithm, 67 | { 68 | let (key_id, claims) = self; 69 | let key = store 70 | .get(key_id) 71 | .ok_or_else(|| Error::NoKeyWithKeyId(key_id.to_owned()))?; 72 | 73 | let header = BorrowedKeyHeader { 74 | algorithm: key.algorithm_type(), 75 | key_id, 76 | }; 77 | 78 | let token = Token::new(header, claims).sign_with_key(key)?; 79 | Ok(token.signature.token_string) 80 | } 81 | } 82 | 83 | impl SignWithKey> for Token 84 | where 85 | H: ToBase64 + JoseHeader, 86 | C: ToBase64, 87 | { 88 | fn sign_with_key(self, key: &impl SigningAlgorithm) -> Result, Error> { 89 | let header_algorithm = self.header.algorithm_type(); 90 | let key_algorithm = key.algorithm_type(); 91 | if header_algorithm != key_algorithm { 92 | return Err(Error::AlgorithmMismatch(header_algorithm, key_algorithm)); 93 | } 94 | 95 | let header = self.header.to_base64()?; 96 | let claims = self.claims.to_base64()?; 97 | let signature = key.sign(&header, &claims)?; 98 | 99 | let token_string = [&*header, &*claims, &signature].join(SEPARATOR); 100 | 101 | Ok(Token { 102 | header: self.header, 103 | claims: self.claims, 104 | signature: Signed { token_string }, 105 | }) 106 | } 107 | } 108 | 109 | impl SignWithStore> for Token 110 | where 111 | H: ToBase64 + JoseHeader, 112 | C: ToBase64, 113 | { 114 | fn sign_with_store(self, store: &S) -> Result, Error> 115 | where 116 | S: Store, 117 | A: SigningAlgorithm, 118 | { 119 | let key_id = self.header.key_id().ok_or(Error::NoKeyId)?; 120 | let key = store 121 | .get(key_id) 122 | .ok_or_else(|| Error::NoKeyWithKeyId(key_id.to_owned()))?; 123 | self.sign_with_key(key) 124 | } 125 | } 126 | 127 | impl<'a, H, C> Token { 128 | /// Get the string representation of the token. 129 | pub fn as_str(&self) -> &str { 130 | &self.signature.token_string 131 | } 132 | } 133 | 134 | impl From> for String { 135 | fn from(token: Token) -> Self { 136 | token.signature.token_string 137 | } 138 | } 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use std::collections::BTreeMap; 143 | 144 | use hmac::{Hmac, Mac}; 145 | use serde::Serialize; 146 | use sha2::{Sha256, Sha512}; 147 | 148 | use crate::algorithm::AlgorithmType; 149 | use crate::error::Error; 150 | use crate::header::Header; 151 | use crate::token::signed::{SignWithKey, SignWithStore}; 152 | use crate::Token; 153 | 154 | #[derive(Serialize)] 155 | struct Claims<'a> { 156 | name: &'a str, 157 | } 158 | 159 | #[test] 160 | pub fn sign_claims() -> Result<(), Error> { 161 | let claims = Claims { name: "John Doe" }; 162 | let key: Hmac = Hmac::new_from_slice(b"secret")?; 163 | 164 | let signed_token = claims.sign_with_key(&key)?; 165 | 166 | assert_eq!(signed_token, "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UifQ.LlTGHPZRXbci-y349jXXN0byQniQQqwKGybzQCFIgY0"); 167 | Ok(()) 168 | } 169 | 170 | #[test] 171 | pub fn sign_unsigned_with_store() -> Result<(), Error> { 172 | let mut key_store = BTreeMap::new(); 173 | let key1: Hmac = Hmac::new_from_slice(b"first")?; 174 | let key2: Hmac = Hmac::new_from_slice(b"second")?; 175 | key_store.insert("first_key".to_owned(), key1); 176 | key_store.insert("second_key".to_owned(), key2); 177 | 178 | let header = Header { 179 | algorithm: AlgorithmType::Hs512, 180 | key_id: Some(String::from("second_key")), 181 | ..Default::default() 182 | }; 183 | let claims = Claims { name: "Jane Doe" }; 184 | let token = Token::new(header, claims); 185 | let signed_token = token.sign_with_store(&key_store)?; 186 | 187 | assert_eq!(signed_token.as_str(), "eyJhbGciOiJIUzUxMiIsImtpZCI6InNlY29uZF9rZXkifQ.eyJuYW1lIjoiSmFuZSBEb2UifQ.t2ON5s8DDb2hefBIWAe0jaEcp-T7b2Wevmj0kKJ8BFxKNQURHpdh4IA-wbmBmqtiCnqTGoRdqK45hhW0AOtz0A"); 188 | Ok(()) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/token/verified.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::store::Store; 2 | use crate::algorithm::VerifyingAlgorithm; 3 | use crate::error::Error; 4 | use crate::header::{Header, JoseHeader}; 5 | use crate::token::{Unverified, Verified}; 6 | use crate::{FromBase64, Token, SEPARATOR}; 7 | 8 | /// Allow objects to be verified with a key. 9 | pub trait VerifyWithKey { 10 | fn verify_with_key(self, key: &impl VerifyingAlgorithm) -> Result; 11 | } 12 | 13 | /// Allow objects to be verified with a store. 14 | pub trait VerifyWithStore { 15 | fn verify_with_store(self, store: &S) -> Result 16 | where 17 | S: Store, 18 | A: VerifyingAlgorithm; 19 | } 20 | 21 | impl<'a, H: JoseHeader, C> VerifyWithKey> for Token> { 22 | fn verify_with_key( 23 | self, 24 | key: &impl VerifyingAlgorithm, 25 | ) -> Result, Error> { 26 | let header = self.header(); 27 | let header_algorithm = header.algorithm_type(); 28 | let key_algorithm = key.algorithm_type(); 29 | if header_algorithm != key_algorithm { 30 | return Err(Error::AlgorithmMismatch(header_algorithm, key_algorithm)); 31 | } 32 | 33 | let Unverified { 34 | header_str, 35 | claims_str, 36 | signature_str, 37 | } = self.signature; 38 | 39 | if key.verify(header_str, claims_str, signature_str)? { 40 | Ok(Token { 41 | header: self.header, 42 | claims: self.claims, 43 | signature: Verified, 44 | }) 45 | } else { 46 | Err(Error::InvalidSignature) 47 | } 48 | } 49 | } 50 | 51 | impl<'a, H: JoseHeader, C> VerifyWithStore> for Token> { 52 | fn verify_with_store(self, store: &S) -> Result, Error> 53 | where 54 | S: Store, 55 | A: VerifyingAlgorithm, 56 | { 57 | let header = self.header(); 58 | let key_id = header.key_id().ok_or(Error::NoKeyId)?; 59 | let key = store 60 | .get(key_id) 61 | .ok_or_else(|| Error::NoKeyWithKeyId(key_id.to_owned()))?; 62 | 63 | self.verify_with_key(key) 64 | } 65 | } 66 | 67 | impl<'a, H, C> VerifyWithKey> for &'a str 68 | where 69 | H: FromBase64 + JoseHeader, 70 | C: FromBase64, 71 | { 72 | fn verify_with_key( 73 | self, 74 | key: &impl VerifyingAlgorithm, 75 | ) -> Result, Error> { 76 | let unverified = Token::parse_unverified(self)?; 77 | unverified.verify_with_key(key) 78 | } 79 | } 80 | 81 | impl<'a, H, C> VerifyWithStore> for &'a str 82 | where 83 | H: FromBase64 + JoseHeader, 84 | C: FromBase64, 85 | { 86 | fn verify_with_store(self, store: &S) -> Result, Error> 87 | where 88 | S: Store, 89 | A: VerifyingAlgorithm, 90 | { 91 | let unverified: Token = Token::parse_unverified(self)?; 92 | unverified.verify_with_store(store) 93 | } 94 | } 95 | 96 | impl<'a, C: FromBase64> VerifyWithKey for &'a str { 97 | fn verify_with_key(self, key: &impl VerifyingAlgorithm) -> Result { 98 | let token: Token = self.verify_with_key(key)?; 99 | Ok(token.claims) 100 | } 101 | } 102 | 103 | impl<'a, C: FromBase64> VerifyWithStore for &'a str { 104 | fn verify_with_store(self, store: &S) -> Result 105 | where 106 | S: Store, 107 | A: VerifyingAlgorithm, 108 | { 109 | let token: Token = self.verify_with_store(store)?; 110 | Ok(token.claims) 111 | } 112 | } 113 | 114 | impl<'a, H: FromBase64, C: FromBase64> Token> { 115 | /// Not recommended. Parse the header and claims without checking the validity of the signature. 116 | pub fn parse_unverified(token_str: &str) -> Result, Error> { 117 | let [header_str, claims_str, signature_str] = split_components(token_str)?; 118 | let header = H::from_base64(header_str)?; 119 | let claims = C::from_base64(claims_str)?; 120 | let signature = Unverified { 121 | header_str, 122 | claims_str, 123 | signature_str, 124 | }; 125 | 126 | Ok(Token { 127 | header, 128 | claims, 129 | signature, 130 | }) 131 | } 132 | } 133 | 134 | pub(crate) fn split_components(token: &str) -> Result<[&str; 3], Error> { 135 | let mut components = token.split(SEPARATOR); 136 | let header = components.next().ok_or(Error::NoHeaderComponent)?; 137 | let claims = components.next().ok_or(Error::NoClaimsComponent)?; 138 | let signature = components.next().ok_or(Error::NoSignatureComponent)?; 139 | 140 | if components.next().is_some() { 141 | return Err(Error::TooManyComponents); 142 | } 143 | 144 | Ok([header, claims, signature]) 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use std::collections::{BTreeMap, HashMap}; 150 | use std::iter::FromIterator; 151 | 152 | use hmac::{Hmac, Mac}; 153 | use serde::Deserialize; 154 | use sha2::{Sha256, Sha512}; 155 | 156 | use crate::algorithm::VerifyingAlgorithm; 157 | use crate::error::Error; 158 | use crate::token::verified::{VerifyWithKey, VerifyWithStore}; 159 | 160 | #[derive(Debug, Deserialize)] 161 | struct Claims { 162 | name: String, 163 | } 164 | 165 | #[test] 166 | #[cfg(feature = "openssl")] 167 | pub fn token_can_not_be_verified_with_a_wrong_key() -> Result<(), Error> { 168 | use crate::{token::signed::SignWithKey, AlgorithmType, Header, PKeyWithDigest, Token}; 169 | use openssl::{hash::MessageDigest, pkey::PKey}; 170 | 171 | let private_pem = include_bytes!("../../test/rs256-private.pem"); 172 | let public_pem = include_bytes!("../../test/rs256-public-2.pem"); 173 | 174 | let rs256_private_key = PKeyWithDigest { 175 | digest: MessageDigest::sha256(), 176 | key: PKey::private_key_from_pem(private_pem).unwrap(), 177 | }; 178 | let rs256_public_key = PKeyWithDigest { 179 | digest: MessageDigest::sha256(), 180 | key: PKey::public_key_from_pem(public_pem).unwrap(), 181 | }; 182 | 183 | let header = Header { 184 | algorithm: AlgorithmType::Rs256, 185 | ..Default::default() 186 | }; 187 | let mut claims = BTreeMap::new(); 188 | claims.insert("sub", "someone"); 189 | 190 | let signed_token = Token::new(header, claims).sign_with_key(&rs256_private_key)?; 191 | let token_str = signed_token.as_str(); 192 | let unverified_token: Token, _> = 193 | Token::parse_unverified(token_str)?; 194 | let verified_token_result = unverified_token.verify_with_key(&rs256_public_key); 195 | assert!(verified_token_result.is_err()); 196 | match verified_token_result.err().unwrap() { 197 | Error::InvalidSignature => Ok(()), 198 | other => panic!("Wrong error type: {:?}", other), 199 | } 200 | } 201 | 202 | #[test] 203 | pub fn component_errors() { 204 | let key: Hmac = Hmac::new_from_slice(b"first").unwrap(); 205 | 206 | let no_claims = "header"; 207 | match VerifyWithKey::::verify_with_key(no_claims, &key) { 208 | Err(Error::NoClaimsComponent) => (), 209 | Ok(s) => panic!("Verify should not have succeeded with output {:?}", s), 210 | x => panic!("Incorrect error type {:?}", x), 211 | } 212 | 213 | let no_signature = "header.claims"; 214 | match VerifyWithKey::::verify_with_key(no_signature, &key) { 215 | Err(Error::NoSignatureComponent) => (), 216 | Ok(s) => panic!("Verify should not have succeeded with output {:?}", s), 217 | x => panic!("Incorrect error type {:?}", x), 218 | } 219 | 220 | let too_many = "header.claims.signature."; 221 | match VerifyWithKey::::verify_with_key(too_many, &key) { 222 | Err(Error::TooManyComponents) => (), 223 | Ok(s) => panic!("Verify should not have succeeded with output {:?}", s), 224 | x => panic!("Incorrect error type {:?}", x), 225 | } 226 | } 227 | 228 | // Test stores 229 | 230 | fn create_test_data() -> Result 231 | where 232 | T: FromIterator<(&'static str, Box)>, 233 | { 234 | // Test two different algorithms in the same store 235 | let key1: Hmac = Hmac::new_from_slice(b"first")?; 236 | let key2: Hmac = Hmac::new_from_slice(b"second")?; 237 | 238 | let name_to_key_tuples = vec![ 239 | ("first_key", Box::new(key1) as Box), 240 | ("second_key", Box::new(key2) as Box), 241 | ] 242 | .into_iter() 243 | .collect(); 244 | 245 | Ok(name_to_key_tuples) 246 | } 247 | 248 | // Header {"alg":"HS512","kid":"second_key"} 249 | // Claims {"name":"Jane Doe"} 250 | const JANE_DOE_SECOND_KEY_TOKEN: &str = "eyJhbGciOiJIUzUxMiIsImtpZCI6InNlY29uZF9rZXkifQ.eyJuYW1lIjoiSmFuZSBEb2UifQ.t2ON5s8DDb2hefBIWAe0jaEcp-T7b2Wevmj0kKJ8BFxKNQURHpdh4IA-wbmBmqtiCnqTGoRdqK45hhW0AOtz0A"; 251 | 252 | #[test] 253 | pub fn verify_claims_with_b_tree_map() -> Result<(), Error> { 254 | let key_store: BTreeMap<_, _> = create_test_data()?; 255 | 256 | let claims: Claims = JANE_DOE_SECOND_KEY_TOKEN.verify_with_store(&key_store)?; 257 | 258 | assert_eq!(claims.name, "Jane Doe"); 259 | Ok(()) 260 | } 261 | 262 | #[test] 263 | pub fn verify_claims_with_hash_map() -> Result<(), Error> { 264 | let key_store: HashMap<_, _> = create_test_data()?; 265 | 266 | let claims: Claims = JANE_DOE_SECOND_KEY_TOKEN.verify_with_store(&key_store)?; 267 | 268 | assert_eq!(claims.name, "Jane Doe"); 269 | Ok(()) 270 | } 271 | 272 | #[test] 273 | pub fn verify_claims_with_missing_key() -> Result<(), Error> { 274 | let key_store: BTreeMap<_, _> = create_test_data()?; 275 | let missing_key_token = "eyJhbGciOiJIUzUxMiIsImtpZCI6Im1pc3Npbmdfa2V5In0.eyJuYW1lIjoiSmFuZSBEb2UifQ.MC9hmBjv9OABdv5bsjVdwUgPOhvpe6a924KU-U7PjVWF2N-f_HXa1PVWtDVJ-dqt1GKutVwixrz7hgVvE_G5_w"; 276 | 277 | let should_fail_claims: Result = missing_key_token.verify_with_store(&key_store); 278 | 279 | match should_fail_claims { 280 | Err(Error::NoKeyWithKeyId(key_id)) => assert_eq!(key_id, "missing_key"), 281 | _ => panic!( 282 | "Missing key should have triggered specific error but returned {:?}", 283 | should_fail_claims 284 | ), 285 | } 286 | 287 | Ok(()) 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /test/es256-private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIDaCNjB5eSARlkILmSrpnfoI8TYi7tJ7xzpy4Bb1D6HVoAoGCCqGSM49 3 | AwEHoUQDQgAEU9PK2chNDTNdJygt1lkhLwuhLOkcuG90J5kHbnHNC/PG9ww1D7q0 4 | d9dWWzfRfZfx3Duw+3j8NnBt6zBgnUG5Uw== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /test/es256-public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEU9PK2chNDTNdJygt1lkhLwuhLOkc 3 | uG90J5kHbnHNC/PG9ww1D7q0d9dWWzfRfZfx3Duw+3j8NnBt6zBgnUG5Uw== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test/rs256-private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQABAoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5CpuGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0KSu5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aPFaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== 3 | -----END RSA PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /test/rs256-public-2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3rAEiYZsc3E8FUGiBCt8e4dkt 3 | qc77U8lnpAfi8DaGSoYu9jelD8/za7XIqFD99EgBA+DXbR01jzSU+ILsfG21jS2B 4 | pPE39zuJYj5X7sQvKg1wRGVS0pi+DVLNEJUhTP8B3fUf1TD72t4aYJh4t8ZrgD90 5 | 9VQglfyJZ7wuuHYHqQIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /test/rs256-public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB 3 | -----END PUBLIC KEY----- 4 | --------------------------------------------------------------------------------