├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── jwt.rs ├── examples ├── auth0.rs ├── custom_header.rs ├── custom_time.rs ├── ed25519.rs └── validation.rs ├── rustfmt.toml ├── src ├── algorithms.rs ├── crypto │ ├── ecdsa.rs │ ├── eddsa.rs │ ├── mod.rs │ └── rsa.rs ├── decoding.rs ├── encoding.rs ├── errors.rs ├── header.rs ├── jwk.rs ├── lib.rs ├── pem │ ├── decoder.rs │ └── mod.rs ├── serialization.rs └── validation.rs └── tests ├── ecdsa ├── mod.rs ├── private_ecdsa_key.pem ├── private_ecdsa_key.pk8 ├── private_jwtio.pem ├── private_jwtio_pkcs8.pem ├── public_ecdsa_key.pem ├── public_ecdsa_key.pk8 └── public_jwtio.pem ├── eddsa ├── mod.rs ├── private_ed25519_key.pem ├── private_ed25519_key.pk8 ├── public_ed25519_key.pem └── public_ed25519_key.pk8 ├── header ├── cert_chain.json ├── cert_chain.pem └── mod.rs ├── hmac.rs ├── lib.rs └── rsa ├── README.md ├── certificate_jwtio.crt ├── certificate_jwtio.csr ├── certificate_rsa_key_pkcs1.crt ├── certificate_rsa_key_pkcs1.csr ├── certificate_rsa_key_pkcs8.crt ├── certificate_rsa_key_pkcs8.csr ├── mod.rs ├── private_jwtio.pem ├── private_rsa_key.der ├── private_rsa_key_pkcs1.pem ├── private_rsa_key_pkcs8.pem ├── public_jwtio.pem ├── public_rsa_key.der ├── public_rsa_key_pkcs1.pem └── public_rsa_key_pkcs8.pem /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | 8 | jobs: 9 | style: 10 | name: Format 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Install Rust 15 | uses: dtolnay/rust-toolchain@stable 16 | with: 17 | components: rustfmt 18 | - name: Check format 19 | run: cargo fmt --check 20 | 21 | clippy: 22 | name: Clippy 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: dtolnay/rust-toolchain@stable 27 | with: 28 | components: clippy 29 | - run: cargo clippy --all-targets --all-features -- -D warnings 30 | 31 | tests: 32 | name: Tests 33 | runs-on: ${{ matrix.os }} 34 | strategy: 35 | matrix: 36 | build: [pinned, stable, nightly] 37 | include: 38 | - build: pinned 39 | os: ubuntu-20.04 40 | rust: 1.73.0 41 | - build: stable 42 | os: ubuntu-20.04 43 | rust: stable 44 | - build: nightly 45 | os: ubuntu-20.04 46 | rust: nightly 47 | steps: 48 | - uses: actions/checkout@v3 49 | - name: Install Rust 50 | uses: dtolnay/rust-toolchain@master 51 | with: 52 | toolchain: ${{ matrix.rust }} 53 | 54 | - name: Build System Info 55 | run: rustc --version 56 | 57 | - name: Run tests default features 58 | run: cargo test 59 | 60 | - name: Run tests no features 61 | run: cargo test --no-default-features 62 | 63 | wasm: 64 | name: Run tests in wasm 65 | runs-on: ubuntu-latest 66 | steps: 67 | - uses: actions/checkout@v3 68 | - name: Install Rust 69 | uses: dtolnay/rust-toolchain@stable 70 | with: 71 | targets: wasm32-unknown-unknown 72 | 73 | - uses: actions/setup-node@v4 74 | 75 | - name: Install wasm-pack 76 | run: cargo install wasm-pack 77 | 78 | - name: Run tests default features 79 | run: wasm-pack test --node 80 | 81 | - name: Run tests no features 82 | run: wasm-pack test --node --no-default-features 83 | 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea/ 4 | .vscode 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | 7 | sudo: false 8 | 9 | notifications: 10 | email: false 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 9.3.1 (2024-02-06) 4 | 5 | - Update base64 6 | 7 | ## 9.3.0 (2024-03-12) 8 | 9 | - Add `Validation.reject_tokens_expiring_in_less_than`, the opposite of leeway 10 | 11 | ## 9.2.0 (2023-12-01) 12 | 13 | - Add an option to not validate `aud` in the Validation struct 14 | - Get the current timestamp in wasm without using std 15 | - Update ring to 0.17 16 | 17 | ## 9.1.0 (2023-10-21) 18 | 19 | - Supports deserialization of unsupported algorithms for JWKs 20 | 21 | 22 | ## 9.0.0 (2023-10-16) 23 | 24 | - Update ring 25 | - Rejects JWTs containing audiences when the Validation doesn't contain any 26 | 27 | ## 8.3.0 (2023-03-15) 28 | 29 | - Update base64 30 | - Implement Clone for TokenData if T impls Clone 31 | 32 | 33 | ## 8.2.0 (2022-12-03) 34 | 35 | - Add DecodingKey::from_jwk 36 | - Can now use PEM certificates if you have the `use_pem` feature enabled 37 | 38 | 39 | ## 8.1.1 (2022-06-17) 40 | 41 | - Fix invalid field name on OctetKeyParameters 42 | 43 | ## 8.1.0 (2022-04-12) 44 | 45 | - Make optional fields in the spec really optional 46 | - Implements `Hash` for `Header` 47 | 48 | ## 8.0.1 (2022-02-03) 49 | 50 | - Fix documentation of leeway 51 | 52 | 53 | ## 8.0.0 (2022-02-02) 54 | 55 | - Add EdDSA algorithm 56 | - `sign`/`verify` now takes a `&[u8]` instead of `&str` to be more flexible 57 | - `DecodingKey` now own its data 58 | - Remove deprecated `dangerous_unsafe_decode` 59 | - `Validation::iss` is now a `HashSet` instead of a single value 60 | - `decode` will now error if `Validation::algorithms` is empty 61 | - Add JWKs types for easy interop with various Oauth provider, see `examples/auth0.rs` for an example 62 | - Removed `decode_*` functions in favour of using the `Validation` struct 63 | - Allow float values for `exp` and `nbf`, yes it's in the spec... floats will be rounded and converted to u64 64 | - Error now implements Clone/Eq 65 | - Change default leeway from 0s to 60s 66 | - Add `Validation::require_spec_claims` to validate presence of the spec claims 67 | - Add default feature for pem decoding named `use_pem` that can be disabled to avoid 2 dependencies 68 | 69 | ## 7.2.0 (2020-06-30) 70 | 71 | - Add `dangerous_insecure_decode` to replace `dangerous_unsafe_decode`, which is now deprecated 72 | - Add `dangerous_insecure_decode_with_validation` 73 | 74 | ## 7.1.2 (2020-06-16) 75 | 76 | - Derive `Hash` for `Header` and `Algorithm` 77 | 78 | ## 7.1.1 (2020-06-09) 79 | 80 | - Update dependencies 81 | 82 | ## 7.1.0 (2020-03-01) 83 | 84 | - Add `into_static` to `DecodingKey` for easier re-use 85 | 86 | # 7.0.0 (2020-01-28) 87 | 88 | - Add support for PS256, PS384 and PS512 89 | - Add support for verifying with modulus/exponent components for RSA 90 | - Update to 2018 edition 91 | - Changed aud field type in Validation to `Option>`. Audience 92 | validation now tests for "any-of-these" audience membership. 93 | - Add support for keys in PEM format 94 | - Add EncodingKey/DecodingKey API to improve performance and UX 95 | 96 | ## 6.0.1 (2019-05-10) 97 | 98 | - Fix Algorithm mapping in FromStr for RSA 99 | 100 | ## 6.0.0 (2019-04-21) 101 | 102 | - Update Ring to 0.14 103 | - Remove `iat` check to match the JWT spec 104 | - Add ES256 and ES384 signing decoding 105 | 106 | ## 5.0.1 (2018-09-10) 107 | 108 | - Add implementation of FromStr for Algorithm 109 | 110 | ## 5.0.0 (2018-08-13) 111 | 112 | - Update ring 113 | - Change error handling to be based on simple struct/enum rather than error-chain 114 | - Fix validations not being called properly in some cases 115 | - Default validation is not checking `iat` and `nbf` anymore 116 | 117 | ## 4.0.1 (2018-03-19) 118 | 119 | - Add method to decode a token without signature verification 120 | 121 | ## 4.0.0 (2017-11-22) 122 | 123 | ### Breaking changes 124 | 125 | - Make it mandatory to specify the algorithm in `decode` 126 | 127 | ## 3.0.0 (2017-09-08) 128 | 129 | ### Breaking changes 130 | - Remove `validate_signature` from `Validation`, use `decode_header` instead if you don't know the alg used 131 | - Make `typ` optional in header, some providers apparently don't use it 132 | 133 | ### Others 134 | 135 | - Update ring & error-chain 136 | - Fix documentation about `leeway` being in seconds and not milliseconds 137 | - Add `decode_header` to only decode the header: replaces the use case of `validate_signature` 138 | 139 | ## 2.0.3 (2017-07-18) 140 | 141 | - Make `TokenData` public 142 | 143 | ## 2.0.2 (2017-06-24) 144 | 145 | - Update ring & chrono 146 | 147 | ## 2.0.1 (2017-05-09) 148 | 149 | - Update ring 150 | 151 | ## 2.0.0 (2017-04-23) 152 | 153 | - Use Serde instead of rustc_serialize 154 | - Add RSA support 155 | - API overhaul, see README for new usage 156 | - Add validation 157 | - Update all dependencies 158 | 159 | ## Previous 160 | 161 | - 1.1.7: update ring 162 | - 1.1.6: update ring 163 | - 1.1.5: update ring version 164 | - 1.1.4: use ring instead of rust-crypto 165 | - 1.1.3: Make sign and verify public 166 | - 1.1.2: Update rust-crypto to 0.2.35 167 | - 1.1.1: Don't serialize empty fields in header 168 | - 1.1.0: Impl Error for jsonwebtoken errors 169 | - 1.0: Initial release 170 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonwebtoken" 3 | version = "9.3.1" 4 | authors = ["Vincent Prouillet "] 5 | license = "MIT" 6 | readme = "README.md" 7 | description = "Create and decode JWTs in a strongly typed way." 8 | homepage = "https://github.com/Keats/jsonwebtoken" 9 | repository = "https://github.com/Keats/jsonwebtoken" 10 | keywords = ["jwt", "api", "token", "jwk"] 11 | edition = "2021" 12 | include = [ 13 | "src/**/*", 14 | "benches/**/*", 15 | "tests/**/*", 16 | "LICENSE", 17 | "README.md", 18 | "CHANGELOG.md", 19 | ] 20 | rust-version = "1.73.0" 21 | 22 | [dependencies] 23 | serde_json = "1.0" 24 | serde = { version = "1.0", features = ["derive"] } 25 | base64 = "0.22" 26 | # For PEM decoding 27 | pem = { version = "3", optional = true } 28 | simple_asn1 = { version = "0.6", optional = true } 29 | 30 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 31 | ring = { version = "0.17.4", features = ["std"] } 32 | 33 | [target.'cfg(target_arch = "wasm32")'.dependencies] 34 | js-sys = "0.3" 35 | ring = { version = "0.17.4", features = ["std", "wasm32_unknown_unknown_js"] } 36 | 37 | [dev-dependencies] 38 | wasm-bindgen-test = "0.3.1" 39 | 40 | [target.'cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))'.dev-dependencies] 41 | # For the custom time example 42 | time = "0.3" 43 | criterion = "0.4" 44 | 45 | [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] 46 | # For the custom time example 47 | time = { version = "0.3", features = ["wasm-bindgen"] } 48 | criterion = { version = "0.4", default-features = false } 49 | 50 | [features] 51 | default = ["use_pem"] 52 | use_pem = ["pem", "simple_asn1"] 53 | 54 | [[bench]] 55 | name = "jwt" 56 | harness = false 57 | 58 | [badges] 59 | maintenance = { status = "passively-maintained" } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vincent Prouillet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsonwebtoken 2 | 3 | [API documentation on docs.rs](https://docs.rs/jsonwebtoken/) 4 | 5 | See [JSON Web Tokens](https://en.wikipedia.org/wiki/JSON_Web_Token) for more information on what JSON Web Tokens are. 6 | 7 | ## Installation 8 | Add the following to Cargo.toml: 9 | 10 | ```toml 11 | jsonwebtoken = "9" 12 | # If you do not need pem decoding, you can disable the default feature `use_pem` that way: 13 | # jsonwebtoken = {version = "9", default-features = false } 14 | serde = {version = "1.0", features = ["derive"] } 15 | ``` 16 | 17 | The minimum required Rust version (MSRV) is specified in the `rust-version` field in this project's [Cargo.toml](Cargo.toml). 18 | 19 | ## Algorithms 20 | This library currently supports the following: 21 | 22 | - HS256 23 | - HS384 24 | - HS512 25 | - RS256 26 | - RS384 27 | - RS512 28 | - PS256 29 | - PS384 30 | - PS512 31 | - ES256 32 | - ES384 33 | - EdDSA 34 | 35 | 36 | ## How to use 37 | Complete examples are available in the examples directory: a basic one and one with a custom header. 38 | 39 | In terms of imports and structs: 40 | ```rust 41 | use serde::{Serialize, Deserialize}; 42 | use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey}; 43 | 44 | /// Our claims struct, it needs to derive `Serialize` and/or `Deserialize` 45 | #[derive(Debug, Serialize, Deserialize)] 46 | struct Claims { 47 | sub: String, 48 | company: String, 49 | exp: usize, 50 | } 51 | ``` 52 | 53 | ### Claims 54 | The claims fields which can be validated. (see [validation](#validation)) 55 | 56 | ```rust 57 | #[derive(Debug, Serialize, Deserialize)] 58 | struct Claims { 59 | aud: String, // Optional. Audience 60 | exp: usize, // Required (validate_exp defaults to true in validation). Expiration time (as UTC timestamp) 61 | iat: usize, // Optional. Issued at (as UTC timestamp) 62 | iss: String, // Optional. Issuer 63 | nbf: usize, // Optional. Not Before (as UTC timestamp) 64 | sub: String, // Optional. Subject (whom token refers to) 65 | } 66 | ``` 67 | 68 | ### Header 69 | The default algorithm is HS256, which uses a shared secret. 70 | 71 | ```rust 72 | let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?; 73 | ``` 74 | 75 | #### Custom headers & changing algorithm 76 | All the parameters from the RFC are supported but the default header only has `typ` and `alg` set. 77 | If you want to set the `kid` parameter or change the algorithm for example: 78 | 79 | ```rust 80 | let mut header = Header::new(Algorithm::HS512); 81 | header.kid = Some("blabla".to_owned()); 82 | 83 | let mut extras = HashMap::with_capacity(1); 84 | extras.insert("custom".to_string(), "header".to_string()); 85 | header.extras = Some(extras); 86 | 87 | let token = encode(&header, &my_claims, &EncodingKey::from_secret("secret".as_ref()))?; 88 | ``` 89 | Look at `examples/custom_header.rs` for a full working example. 90 | 91 | ### Encoding 92 | 93 | ```rust 94 | // HS256 95 | let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?; 96 | // RSA 97 | let token = encode(&Header::new(Algorithm::RS256), &my_claims, &EncodingKey::from_rsa_pem(include_bytes!("privkey.pem"))?)?; 98 | ``` 99 | Encoding a JWT takes 3 parameters: 100 | 101 | - a header: the `Header` struct 102 | - some claims: your own struct 103 | - a key/secret 104 | 105 | When using HS256, HS384, or HS512, the key is always a shared secret like in the example above. When using 106 | RSA/EC, the key should always be the content of the private key in PEM or DER format. 107 | 108 | If your key is in PEM format, it is better performance wise to generate the `EncodingKey` once in a `lazy_static` or 109 | something similar and reuse it. 110 | 111 | ### Decoding 112 | 113 | ```rust 114 | // `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct. 115 | let token = decode::(&token, &DecodingKey::from_secret("secret".as_ref()), &Validation::default())?; 116 | ``` 117 | `decode` can result in errors for a variety of reasons: 118 | 119 | - the token or its signature is invalid 120 | - the token had invalid base64 121 | - validation of at least one reserved claim failed 122 | 123 | As with encoding, when using HS256, HS384, or HS512, the key is always a shared secret like in the example above. When using 124 | RSA/EC, the key should always be the content of the public key in PEM (or certificate in this case) or DER format. 125 | 126 | In some cases, for example if you don't know the algorithm used or need to grab the `kid`, you can choose to decode only the header: 127 | 128 | ```rust 129 | let header = decode_header(&token)?; 130 | ``` 131 | 132 | This does not perform any signature verification or validate the token claims. 133 | 134 | You can also decode a token using the public key components of a RSA key in base64 format. 135 | The main use-case is for JWK where your public key is in a JSON format like so: 136 | 137 | ```json 138 | { 139 | "kty":"RSA", 140 | "e":"AQAB", 141 | "kid":"6a7a119f-0876-4f7e-8d0f-bf3ea1391dd8", 142 | "n":"yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ" 143 | } 144 | ``` 145 | 146 | ```rust 147 | // `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct. 148 | let token = decode::(&token, &DecodingKey::from_rsa_components(jwk["n"], jwk["e"]), &Validation::new(Algorithm::RS256))?; 149 | ``` 150 | 151 | If your key is in PEM format, it is better performance wise to generate the `DecodingKey` once in a `lazy_static` or 152 | something similar and reuse it. 153 | 154 | ### Convert SEC1 private key to PKCS8 155 | `jsonwebtoken` currently only supports PKCS8 format for private EC keys. If your key has `BEGIN EC PRIVATE KEY` at the top, 156 | this is a SEC1 type and can be converted to PKCS8 like so: 157 | 158 | ```bash 159 | openssl pkcs8 -topk8 -nocrypt -in sec1.pem -out pkcs8.pem 160 | ``` 161 | 162 | 163 | ## Validation 164 | This library automatically validates the `exp` claim, and `nbf` is validated if present. You can also validate the `sub`, `iss`, and `aud` but 165 | those require setting the expected values in the `Validation` struct. In the case of `aud`, if there is a value set in the token but 166 | not in the `Validation`, the token will be rejected. 167 | 168 | Validation is only made on present fields in the claims. It is possible to define the required claims, hence verifying that a JWT has a value for each of these claims before it is considered for validation. The required claims can be set in the `Validation` struct. The default option requires the `exp` claim to be present. 169 | 170 | Since validating time fields is always a bit tricky due to clock skew, 171 | you can add some leeway to the `iat`, `exp`, and `nbf` validation by setting the `leeway` field. 172 | 173 | Last but not least, you will need to set the algorithm(s) allowed for this token if you are not using `HS256`. 174 | 175 | Look at `examples/validation.rs` for a full working example. 176 | -------------------------------------------------------------------------------- /benches/jwt.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 7 | struct Claims { 8 | sub: String, 9 | company: String, 10 | } 11 | 12 | fn bench_encode(c: &mut Criterion) { 13 | let claim = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned() }; 14 | let key = EncodingKey::from_secret("secret".as_ref()); 15 | 16 | c.bench_function("bench_encode", |b| { 17 | b.iter(|| encode(black_box(&Header::default()), black_box(&claim), black_box(&key))) 18 | }); 19 | } 20 | 21 | fn bench_encode_custom_extra_headers(c: &mut Criterion) { 22 | let claim = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned() }; 23 | let key = EncodingKey::from_secret("secret".as_ref()); 24 | let mut extras = HashMap::with_capacity(1); 25 | extras.insert("custom".to_string(), "header".to_string()); 26 | let header = &Header { extras, ..Default::default() }; 27 | 28 | c.bench_function("bench_encode", |b| { 29 | b.iter(|| encode(black_box(header), black_box(&claim), black_box(&key))) 30 | }); 31 | } 32 | 33 | fn bench_decode(c: &mut Criterion) { 34 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; 35 | let key = DecodingKey::from_secret("secret".as_ref()); 36 | 37 | c.bench_function("bench_decode", |b| { 38 | b.iter(|| { 39 | decode::( 40 | black_box(token), 41 | black_box(&key), 42 | black_box(&Validation::new(Algorithm::HS256)), 43 | ) 44 | }) 45 | }); 46 | } 47 | 48 | criterion_group!(benches, bench_encode, bench_encode_custom_extra_headers, bench_decode); 49 | criterion_main!(benches); 50 | -------------------------------------------------------------------------------- /examples/auth0.rs: -------------------------------------------------------------------------------- 1 | /// Example for the backend to backend implementation 2 | use std::collections::HashMap; 3 | 4 | use jsonwebtoken::jwk::{AlgorithmParameters, JwkSet}; 5 | use jsonwebtoken::{decode, decode_header, DecodingKey, Validation}; 6 | 7 | const TOKEN: &str = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjFaNTdkX2k3VEU2S1RZNTdwS3pEeSJ9.eyJpc3MiOiJodHRwczovL2Rldi1kdXp5YXlrNC5ldS5hdXRoMC5jb20vIiwic3ViIjoiNDNxbW44c281R3VFU0U1N0Fkb3BhN09jYTZXeVNidmRAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZGV2LWR1enlheWs0LmV1LmF1dGgwLmNvbS9hcGkvdjIvIiwiaWF0IjoxNjIzNTg1MzAxLCJleHAiOjE2MjM2NzE3MDEsImF6cCI6IjQzcW1uOHNvNUd1RVNFNTdBZG9wYTdPY2E2V3lTYnZkIiwic2NvcGUiOiJyZWFkOnVzZXJzIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.0MpewU1GgvRqn4F8fK_-Eu70cUgWA5JJrdbJhkCPCxXP-8WwfI-qx1ZQg2a7nbjXICYAEl-Z6z4opgy-H5fn35wGP0wywDqZpqL35IPqx6d0wRvpPMjJM75zVXuIjk7cEhDr2kaf1LOY9auWUwGzPiDB_wM-R0uvUMeRPMfrHaVN73xhAuQWVjCRBHvNscYS5-i6qBQKDMsql87dwR72DgHzMlaC8NnaGREBC-xiSamesqhKPVyGzSkFSaF3ZKpGrSDapqmHkNW9RDBE3GQ9OHM33vzUdVKOjU1g9Leb9PDt0o1U4p3NQoGJPShQ6zgWSUEaqvUZTfkbpD_DoYDRxA"; 8 | const JWKS_REPLY: &str = r#" 9 | {"keys":[{"alg":"RS256","kty":"RSA","use":"sig","n":"2V31IZF-EY2GxXQPI5OaEE--sezizPamNZDW9AjBE2cCErfufM312nT2jUsCnfjsXnh6Z_b-ncOMr97zIZkq1ofU7avemv8nX7NpKmoPBpVrMPprOax2-e3wt-bSfFLIHyghjFLKpkT0LOL_Fimi7xY-J86R06WHojLo3yGzAgQCswZmD4CFf6NcBWDcb6l6kx5vk_AdzHIkVEZH4aikUL_fn3zq5qbE25oOg6pT7F7Pp4zdHOAEKnIRS8tvP8tvvVRkUCrjBxz_Kx6Ne1YOD-fkIMRk_MgIWeKZZzZOYx4VrC0vqYiM-PcKWbNdt1kNoTHOeL06XZeSE6WPZ3VB1Q","e":"AQAB","kid":"1Z57d_i7TE6KTY57pKzDy","x5t":"1gA-aTE9VglLXZnrqvzwWhHsFdk","x5c":["MIIDDTCCAfWgAwIBAgIJHwhLfcIbNvmkMA0GCSqGSIb3DQEBCwUAMCQxIjAgBgNVBAMTGWRldi1kdXp5YXlrNC5ldS5hdXRoMC5jb20wHhcNMjEwNjEzMDcxMTQ1WhcNMzUwMjIwMDcxMTQ1WjAkMSIwIAYDVQQDExlkZXYtZHV6eWF5azQuZXUuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2V31IZF+EY2GxXQPI5OaEE++sezizPamNZDW9AjBE2cCErfufM312nT2jUsCnfjsXnh6Z/b+ncOMr97zIZkq1ofU7avemv8nX7NpKmoPBpVrMPprOax2+e3wt+bSfFLIHyghjFLKpkT0LOL/Fimi7xY+J86R06WHojLo3yGzAgQCswZmD4CFf6NcBWDcb6l6kx5vk/AdzHIkVEZH4aikUL/fn3zq5qbE25oOg6pT7F7Pp4zdHOAEKnIRS8tvP8tvvVRkUCrjBxz/Kx6Ne1YOD+fkIMRk/MgIWeKZZzZOYx4VrC0vqYiM+PcKWbNdt1kNoTHOeL06XZeSE6WPZ3VB1QIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRPX3shmtgajnR4ly5t9VYB66ufGDAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAHtKpX70WU4uXOMjbFKj0e9HMXyCrdcX6TuYiMFqqlOGWM4yghSM8Bd0HkKcirm4DUoC+1dDMzXMZ+tbntavPt1xG0eRFjeocP+kIYTMQEG2LDM5HQ+Z7bdcwlxnuYOZQfpgKAfYbQ8Cxu38sB6q82I+5NJ0w0VXuG7nUZ1RD+rkXaeMYHNoibAtKBoTWrCaFWGV0E55OM+H0ckcHKUUnNXJOyZ+zEOzPFY5iuYIUmn1LfR1P0SLgIMfiooNC5ZuR/wLdbtyKtor2vzz7niEiewz+aPvfuPnWe/vMtQrfS37/yEhCozFnbIps/+S2Ay78mNBDuOAA9fg5yrnOmjABCU="]},{"alg":"RS256","kty":"RSA","use":"sig","n":"0KDpAuJZyDwPg9CfKi0R3QwDROyH0rvd39lmAoqQNqtYPghDToxFMDLpul0QHttbofHPJMKrPfeEFEOvw7KJgelCHZmckVKaz0e4tfu_2Uvw2kFljCmJGfspUU3mXxLyEea9Ef9JqUru6L8f_0_JIDMT3dceqU5ZqbG8u6-HRgRQ5Jqc_fF29Xyw3gxNP_Q46nsp_0yE68UZE1iPy1om0mpu8mpsY1-Nbvm51C8i4_tFQHdUXbhF4cjAoR0gZFNkzr7FCrL4On0hKeLcvxIHD17SxaBsTuCBGd35g7TmXsA4hSimD9taRHA-SkXh558JG5dr-YV9x80qjeSAvTyjcQ","e":"AQAB","kid":"v2HFn4VqJB-U4vtQRJ3Ql","x5t":"AhUBZjtsFdx7C1PFtWAJ756bo5k","x5c":["MIIDDTCCAfWgAwIBAgIJSSFLkuG8uAM8MA0GCSqGSIb3DQEBCwUAMCQxIjAgBgNVBAMTGWRldi1kdXp5YXlrNC5ldS5hdXRoMC5jb20wHhcNMjEwNjEzMDcxMTQ2WhcNMzUwMjIwMDcxMTQ2WjAkMSIwIAYDVQQDExlkZXYtZHV6eWF5azQuZXUuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0KDpAuJZyDwPg9CfKi0R3QwDROyH0rvd39lmAoqQNqtYPghDToxFMDLpul0QHttbofHPJMKrPfeEFEOvw7KJgelCHZmckVKaz0e4tfu/2Uvw2kFljCmJGfspUU3mXxLyEea9Ef9JqUru6L8f/0/JIDMT3dceqU5ZqbG8u6+HRgRQ5Jqc/fF29Xyw3gxNP/Q46nsp/0yE68UZE1iPy1om0mpu8mpsY1+Nbvm51C8i4/tFQHdUXbhF4cjAoR0gZFNkzr7FCrL4On0hKeLcvxIHD17SxaBsTuCBGd35g7TmXsA4hSimD9taRHA+SkXh558JG5dr+YV9x80qjeSAvTyjcQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSEkRwvkyYzzzY/jPd1n7/1VRQNdzAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAGtdl7QwzpaWZjbmd6UINAIlpuWIo2v4EJD9kGan/tUZTiUdBaJVwFHOkLRsbZHc5PmBB5IryjOcrqsmKvFdo6wUZA92qTuQVZrOTea07msOKSWE6yRUh1/VCXH2+vAiB9A4DFZ23WpZikBR+DmiD8NGwVgAwWw9jM6pe7ODY+qxFXGjQdTCHcDdbqG2160nKEHCBvjR1Sc/F0pzHPv8CBJCyGAPTCXX42sKZI92pPzdKSmNNijCuIEYLsjzKVxaUuwEqIshk3mYeu6im4VmXXFj+MlyMsusVWi2py7fGFadamzyiV/bxZe+4xzzrRG1Kow/WnVEizfTdEzFXO6YikE="]}]} 10 | "#; 11 | 12 | fn main() -> Result<(), Box> { 13 | let jwks: JwkSet = serde_json::from_str(JWKS_REPLY).unwrap(); 14 | let header = decode_header(TOKEN).unwrap(); 15 | 16 | let Some(kid) = header.kid else { 17 | return Err("Token doesn't have a `kid` header field".into()); 18 | }; 19 | 20 | let Some(jwk) = jwks.find(&kid) else { 21 | return Err("No matching JWK found for the given kid".into()); 22 | }; 23 | 24 | let decoding_key = match &jwk.algorithm { 25 | AlgorithmParameters::RSA(rsa) => DecodingKey::from_rsa_components(&rsa.n, &rsa.e)?, 26 | _ => unreachable!("algorithm should be a RSA in this example"), 27 | }; 28 | 29 | let validation = { 30 | let mut validation = Validation::new(header.alg); 31 | validation.set_audience(&["https://dev-duzyayk4.eu.auth0.com/api/v2/"]); 32 | validation.validate_exp = false; 33 | validation 34 | }; 35 | 36 | let decoded_token = 37 | decode::>(TOKEN, &decoding_key, &validation)?; 38 | 39 | println!("{:#?}", decoded_token); 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /examples/custom_header.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | use jsonwebtoken::errors::ErrorKind; 5 | use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; 6 | 7 | #[derive(Debug, Serialize, Deserialize)] 8 | struct Claims { 9 | sub: String, 10 | company: String, 11 | exp: u64, 12 | } 13 | 14 | fn main() { 15 | let my_claims = 16 | Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned(), exp: 10000000000 }; 17 | let key = b"secret"; 18 | 19 | let mut extras = HashMap::with_capacity(1); 20 | extras.insert("custom".to_string(), "header".to_string()); 21 | 22 | let header = Header { 23 | kid: Some("signing_key".to_owned()), 24 | alg: Algorithm::HS512, 25 | extras, 26 | ..Default::default() 27 | }; 28 | 29 | let token = match encode(&header, &my_claims, &EncodingKey::from_secret(key)) { 30 | Ok(t) => t, 31 | Err(_) => panic!(), // in practice you would return the error 32 | }; 33 | println!("{:?}", token); 34 | 35 | let token_data = match decode::( 36 | &token, 37 | &DecodingKey::from_secret(key), 38 | &Validation::new(Algorithm::HS512), 39 | ) { 40 | Ok(c) => c, 41 | Err(err) => match *err.kind() { 42 | ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error 43 | _ => panic!(), 44 | }, 45 | }; 46 | println!("{:?}", token_data.claims); 47 | println!("{:?}", token_data.header); 48 | } 49 | -------------------------------------------------------------------------------- /examples/custom_time.rs: -------------------------------------------------------------------------------- 1 | use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation}; 2 | use serde::{Deserialize, Serialize}; 3 | use time::{Duration, OffsetDateTime}; 4 | 5 | const SECRET: &str = "some-secret"; 6 | 7 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 8 | struct Claims { 9 | sub: String, 10 | #[serde(with = "jwt_numeric_date")] 11 | iat: OffsetDateTime, 12 | #[serde(with = "jwt_numeric_date")] 13 | exp: OffsetDateTime, 14 | } 15 | 16 | impl Claims { 17 | /// If a token should always be equal to its representation after serializing and deserializing 18 | /// again, this function must be used for construction. `OffsetDateTime` contains a microsecond 19 | /// field but JWT timestamps are defined as UNIX timestamps (seconds). This function normalizes 20 | /// the timestamps. 21 | pub fn new(sub: String, iat: OffsetDateTime, exp: OffsetDateTime) -> Self { 22 | // normalize the timestamps by stripping of microseconds 23 | let iat = iat 24 | .date() 25 | .with_hms_milli(iat.hour(), iat.minute(), iat.second(), 0) 26 | .unwrap() 27 | .assume_utc(); 28 | let exp = exp 29 | .date() 30 | .with_hms_milli(exp.hour(), exp.minute(), exp.second(), 0) 31 | .unwrap() 32 | .assume_utc(); 33 | 34 | Self { sub, iat, exp } 35 | } 36 | } 37 | 38 | mod jwt_numeric_date { 39 | //! Custom serialization of OffsetDateTime to conform with the JWT spec (RFC 7519 section 2, "Numeric Date") 40 | use serde::{self, Deserialize, Deserializer, Serializer}; 41 | use time::OffsetDateTime; 42 | 43 | /// Serializes an OffsetDateTime to a Unix timestamp (milliseconds since 1970/1/1T00:00:00T) 44 | pub fn serialize(date: &OffsetDateTime, serializer: S) -> Result 45 | where 46 | S: Serializer, 47 | { 48 | let timestamp = date.unix_timestamp(); 49 | serializer.serialize_i64(timestamp) 50 | } 51 | 52 | /// Attempts to deserialize an i64 and use as a Unix timestamp 53 | pub fn deserialize<'de, D>(deserializer: D) -> Result 54 | where 55 | D: Deserializer<'de>, 56 | { 57 | OffsetDateTime::from_unix_timestamp(i64::deserialize(deserializer)?) 58 | .map_err(|_| serde::de::Error::custom("invalid Unix timestamp value")) 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gT2Zmc2V0RGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.BcPipupP9oIV6uFRI6Acn7FMLws_wA3oo6CrfeFF3Gg"; 64 | 65 | use super::super::{Claims, SECRET}; 66 | use jsonwebtoken::{ 67 | decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation, 68 | }; 69 | use time::{Duration, OffsetDateTime}; 70 | 71 | #[test] 72 | fn round_trip() { 73 | let sub = "Custom OffsetDateTime ser/de".to_string(); 74 | let iat = OffsetDateTime::from_unix_timestamp(0).unwrap(); 75 | let exp = OffsetDateTime::from_unix_timestamp(32503680000).unwrap(); 76 | 77 | let claims = Claims::new(sub.clone(), iat, exp); 78 | 79 | let token = 80 | encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET.as_ref())) 81 | .expect("Failed to encode claims"); 82 | 83 | assert_eq!(&token, EXPECTED_TOKEN); 84 | 85 | let decoded = decode::( 86 | &token, 87 | &DecodingKey::from_secret(SECRET.as_ref()), 88 | &Validation::new(Algorithm::HS256), 89 | ) 90 | .expect("Failed to decode token"); 91 | 92 | assert_eq!(decoded.claims, claims); 93 | } 94 | 95 | #[test] 96 | fn should_fail_on_invalid_timestamp() { 97 | // A token with the expiry of i64::MAX + 1 98 | let overflow_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjkyMjMzNzIwMzY4NTQ3NzYwMDB9.G2PKreA27U8_xOwuIeCYXacFYeR46f9FyENIZfCrvEc"; 99 | 100 | let decode_result = decode::( 101 | &overflow_token, 102 | &DecodingKey::from_secret(SECRET.as_ref()), 103 | &Validation::new(Algorithm::HS256), 104 | ); 105 | 106 | assert!(decode_result.is_err()); 107 | } 108 | 109 | #[test] 110 | fn to_token_and_parse_equals_identity() { 111 | let iat = OffsetDateTime::now_utc(); 112 | let exp = iat + Duration::days(1); 113 | let sub = "Custom OffsetDateTime ser/de".to_string(); 114 | 115 | let claims = Claims::new(sub.clone(), iat, exp); 116 | 117 | let token = 118 | encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET.as_ref())) 119 | .expect("Failed to encode claims"); 120 | 121 | let decoded = decode::( 122 | &token, 123 | &DecodingKey::from_secret(SECRET.as_ref()), 124 | &Validation::new(Algorithm::HS256), 125 | ) 126 | .expect("Failed to decode token") 127 | .claims; 128 | 129 | assert_eq!(claims, decoded); 130 | } 131 | } 132 | } 133 | 134 | fn main() -> Result<(), Box> { 135 | let sub = "Custom OffsetDateTime ser/de".to_string(); 136 | let iat = OffsetDateTime::now_utc(); 137 | let exp = iat + Duration::days(1); 138 | 139 | let claims = Claims::new(sub, iat, exp); 140 | 141 | let token = jsonwebtoken::encode( 142 | &Header::default(), 143 | &claims, 144 | &EncodingKey::from_secret(SECRET.as_ref()), 145 | )?; 146 | 147 | println!("serialized token: {}", &token); 148 | 149 | let token_data = jsonwebtoken::decode::( 150 | &token, 151 | &DecodingKey::from_secret(SECRET.as_ref()), 152 | &Validation::new(Algorithm::HS256), 153 | )?; 154 | 155 | println!("token data:\n{:#?}", &token_data); 156 | Ok(()) 157 | } 158 | -------------------------------------------------------------------------------- /examples/ed25519.rs: -------------------------------------------------------------------------------- 1 | use jsonwebtoken::{ 2 | decode, encode, get_current_timestamp, Algorithm, DecodingKey, EncodingKey, Validation, 3 | }; 4 | use ring::signature::{Ed25519KeyPair, KeyPair}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Debug, Serialize, Deserialize)] 8 | pub struct Claims { 9 | sub: String, 10 | exp: u64, 11 | } 12 | 13 | fn main() { 14 | let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap(); 15 | let encoding_key = EncodingKey::from_ed_der(doc.as_ref()); 16 | 17 | let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap(); 18 | let decoding_key = DecodingKey::from_ed_der(pair.public_key().as_ref()); 19 | 20 | let claims = Claims { sub: "test".to_string(), exp: get_current_timestamp() }; 21 | 22 | let token = 23 | encode(&jsonwebtoken::Header::new(Algorithm::EdDSA), &claims, &encoding_key).unwrap(); 24 | 25 | let validation = Validation::new(Algorithm::EdDSA); 26 | let _token_data = decode::(&token, &decoding_key, &validation).unwrap(); 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use super::*; 32 | 33 | struct Jot { 34 | encoding_key: EncodingKey, 35 | decoding_key: DecodingKey, 36 | } 37 | 38 | impl Jot { 39 | fn new() -> Jot { 40 | let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap(); 41 | let encoding_key = EncodingKey::from_ed_der(doc.as_ref()); 42 | 43 | let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap(); 44 | let decoding_key = DecodingKey::from_ed_der(pair.public_key().as_ref()); 45 | Jot { encoding_key, decoding_key } 46 | } 47 | } 48 | 49 | #[test] 50 | fn test() { 51 | let jot = Jot::new(); 52 | let claims = Claims { sub: "test".to_string(), exp: get_current_timestamp() }; 53 | 54 | let token = 55 | encode(&jsonwebtoken::Header::new(Algorithm::EdDSA), &claims, &jot.encoding_key) 56 | .unwrap(); 57 | 58 | let validation = Validation::new(Algorithm::EdDSA); 59 | let token_data = decode::(&token, &jot.decoding_key, &validation).unwrap(); 60 | assert_eq!(token_data.claims.sub, "test"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/validation.rs: -------------------------------------------------------------------------------- 1 | use jsonwebtoken::errors::ErrorKind; 2 | use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | struct Claims { 7 | aud: String, 8 | sub: String, 9 | company: String, 10 | exp: u64, 11 | } 12 | 13 | fn main() { 14 | let key = b"secret"; 15 | let my_claims = Claims { 16 | aud: "me".to_owned(), 17 | sub: "b@b.com".to_owned(), 18 | company: "ACME".to_owned(), 19 | exp: 10000000000, 20 | }; 21 | let token = match encode(&Header::default(), &my_claims, &EncodingKey::from_secret(key)) { 22 | Ok(t) => t, 23 | Err(_) => panic!(), // in practice you would return the error 24 | }; 25 | 26 | let mut validation = Validation::new(Algorithm::HS256); 27 | validation.sub = Some("b@b.com".to_string()); 28 | validation.set_audience(&["me"]); 29 | validation.set_required_spec_claims(&["exp", "sub", "aud"]); 30 | let token_data = match decode::(&token, &DecodingKey::from_secret(key), &validation) { 31 | Ok(c) => c, 32 | Err(err) => match *err.kind() { 33 | ErrorKind::InvalidToken => panic!("Token is invalid"), // Example on how to handle a specific error 34 | ErrorKind::InvalidIssuer => panic!("Issuer is invalid"), // Example on how to handle a specific error 35 | _ => panic!("Some other errors"), 36 | }, 37 | }; 38 | println!("{:?}", token_data.claims); 39 | println!("{:?}", token_data.header); 40 | } 41 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "max" 2 | -------------------------------------------------------------------------------- /src/algorithms.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, ErrorKind, Result}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::str::FromStr; 4 | 5 | #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] 6 | pub(crate) enum AlgorithmFamily { 7 | Hmac, 8 | Rsa, 9 | Ec, 10 | Ed, 11 | } 12 | 13 | /// The algorithms supported for signing/verifying JWTs 14 | #[allow(clippy::upper_case_acronyms)] 15 | #[derive(Debug, Default, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] 16 | pub enum Algorithm { 17 | /// HMAC using SHA-256 18 | #[default] 19 | HS256, 20 | /// HMAC using SHA-384 21 | HS384, 22 | /// HMAC using SHA-512 23 | HS512, 24 | 25 | /// ECDSA using SHA-256 26 | ES256, 27 | /// ECDSA using SHA-384 28 | ES384, 29 | 30 | /// RSASSA-PKCS1-v1_5 using SHA-256 31 | RS256, 32 | /// RSASSA-PKCS1-v1_5 using SHA-384 33 | RS384, 34 | /// RSASSA-PKCS1-v1_5 using SHA-512 35 | RS512, 36 | 37 | /// RSASSA-PSS using SHA-256 38 | PS256, 39 | /// RSASSA-PSS using SHA-384 40 | PS384, 41 | /// RSASSA-PSS using SHA-512 42 | PS512, 43 | 44 | /// Edwards-curve Digital Signature Algorithm (EdDSA) 45 | EdDSA, 46 | } 47 | 48 | impl FromStr for Algorithm { 49 | type Err = Error; 50 | fn from_str(s: &str) -> Result { 51 | match s { 52 | "HS256" => Ok(Algorithm::HS256), 53 | "HS384" => Ok(Algorithm::HS384), 54 | "HS512" => Ok(Algorithm::HS512), 55 | "ES256" => Ok(Algorithm::ES256), 56 | "ES384" => Ok(Algorithm::ES384), 57 | "RS256" => Ok(Algorithm::RS256), 58 | "RS384" => Ok(Algorithm::RS384), 59 | "PS256" => Ok(Algorithm::PS256), 60 | "PS384" => Ok(Algorithm::PS384), 61 | "PS512" => Ok(Algorithm::PS512), 62 | "RS512" => Ok(Algorithm::RS512), 63 | "EdDSA" => Ok(Algorithm::EdDSA), 64 | _ => Err(ErrorKind::InvalidAlgorithmName.into()), 65 | } 66 | } 67 | } 68 | 69 | impl Algorithm { 70 | pub(crate) fn family(self) -> AlgorithmFamily { 71 | match self { 72 | Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => AlgorithmFamily::Hmac, 73 | Algorithm::RS256 74 | | Algorithm::RS384 75 | | Algorithm::RS512 76 | | Algorithm::PS256 77 | | Algorithm::PS384 78 | | Algorithm::PS512 => AlgorithmFamily::Rsa, 79 | Algorithm::ES256 | Algorithm::ES384 => AlgorithmFamily::Ec, 80 | Algorithm::EdDSA => AlgorithmFamily::Ed, 81 | } 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use wasm_bindgen_test::wasm_bindgen_test; 88 | 89 | use super::*; 90 | 91 | #[test] 92 | #[wasm_bindgen_test] 93 | fn generate_algorithm_enum_from_str() { 94 | assert!(Algorithm::from_str("HS256").is_ok()); 95 | assert!(Algorithm::from_str("HS384").is_ok()); 96 | assert!(Algorithm::from_str("HS512").is_ok()); 97 | assert!(Algorithm::from_str("RS256").is_ok()); 98 | assert!(Algorithm::from_str("RS384").is_ok()); 99 | assert!(Algorithm::from_str("RS512").is_ok()); 100 | assert!(Algorithm::from_str("PS256").is_ok()); 101 | assert!(Algorithm::from_str("PS384").is_ok()); 102 | assert!(Algorithm::from_str("PS512").is_ok()); 103 | assert!(Algorithm::from_str("").is_err()); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/crypto/ecdsa.rs: -------------------------------------------------------------------------------- 1 | use ring::{rand, signature}; 2 | 3 | use crate::algorithms::Algorithm; 4 | use crate::errors::Result; 5 | use crate::serialization::b64_encode; 6 | 7 | /// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. 8 | pub(crate) fn alg_to_ec_verification( 9 | alg: Algorithm, 10 | ) -> &'static signature::EcdsaVerificationAlgorithm { 11 | match alg { 12 | Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED, 13 | Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED, 14 | _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), 15 | } 16 | } 17 | 18 | /// Only used internally when signing EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. 19 | pub(crate) fn alg_to_ec_signing(alg: Algorithm) -> &'static signature::EcdsaSigningAlgorithm { 20 | match alg { 21 | Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED_SIGNING, 22 | Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED_SIGNING, 23 | _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), 24 | } 25 | } 26 | 27 | /// The actual ECDSA signing + encoding 28 | /// The key needs to be in PKCS8 format 29 | pub fn sign( 30 | alg: &'static signature::EcdsaSigningAlgorithm, 31 | key: &[u8], 32 | message: &[u8], 33 | ) -> Result { 34 | let rng = rand::SystemRandom::new(); 35 | let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, key, &rng)?; 36 | let out = signing_key.sign(&rng, message)?; 37 | Ok(b64_encode(out)) 38 | } 39 | -------------------------------------------------------------------------------- /src/crypto/eddsa.rs: -------------------------------------------------------------------------------- 1 | use ring::signature; 2 | 3 | use crate::algorithms::Algorithm; 4 | use crate::errors::Result; 5 | use crate::serialization::b64_encode; 6 | 7 | /// Only used internally when signing or validating EdDSA, to map from our enum to the Ring EdDSAParameters structs. 8 | pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EdDSAParameters { 9 | // To support additional key subtypes, like Ed448, we would need to match on the JWK's ("crv") 10 | // parameter. 11 | match alg { 12 | Algorithm::EdDSA => &signature::ED25519, 13 | _ => unreachable!("Tried to get EdDSA alg for a non-EdDSA algorithm"), 14 | } 15 | } 16 | 17 | /// The actual EdDSA signing + encoding 18 | /// The key needs to be in PKCS8 format 19 | pub fn sign(key: &[u8], message: &[u8]) -> Result { 20 | let signing_key = signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(key)?; 21 | let out = signing_key.sign(message); 22 | Ok(b64_encode(out)) 23 | } 24 | -------------------------------------------------------------------------------- /src/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | use ring::constant_time::verify_slices_are_equal; 2 | use ring::{hmac, signature}; 3 | 4 | use crate::algorithms::Algorithm; 5 | use crate::decoding::{DecodingKey, DecodingKeyKind}; 6 | use crate::encoding::EncodingKey; 7 | use crate::errors::Result; 8 | use crate::serialization::{b64_decode, b64_encode}; 9 | 10 | pub(crate) mod ecdsa; 11 | pub(crate) mod eddsa; 12 | pub(crate) mod rsa; 13 | 14 | /// The actual HS signing + encoding 15 | /// Could be in its own file to match RSA/EC but it's 2 lines... 16 | pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &[u8]) -> String { 17 | let digest = hmac::sign(&hmac::Key::new(alg, key), message); 18 | b64_encode(digest) 19 | } 20 | 21 | /// Take the payload of a JWT, sign it using the algorithm given and return 22 | /// the base64 url safe encoded of the result. 23 | /// 24 | /// If you just want to encode a JWT, use `encode` instead. 25 | pub fn sign(message: &[u8], key: &EncodingKey, algorithm: Algorithm) -> Result { 26 | match algorithm { 27 | Algorithm::HS256 => Ok(sign_hmac(hmac::HMAC_SHA256, key.inner(), message)), 28 | Algorithm::HS384 => Ok(sign_hmac(hmac::HMAC_SHA384, key.inner(), message)), 29 | Algorithm::HS512 => Ok(sign_hmac(hmac::HMAC_SHA512, key.inner(), message)), 30 | 31 | Algorithm::ES256 | Algorithm::ES384 => { 32 | ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key.inner(), message) 33 | } 34 | 35 | Algorithm::EdDSA => eddsa::sign(key.inner(), message), 36 | 37 | Algorithm::RS256 38 | | Algorithm::RS384 39 | | Algorithm::RS512 40 | | Algorithm::PS256 41 | | Algorithm::PS384 42 | | Algorithm::PS512 => rsa::sign(rsa::alg_to_rsa_signing(algorithm), key.inner(), message), 43 | } 44 | } 45 | 46 | /// See Ring docs for more details 47 | fn verify_ring( 48 | alg: &'static dyn signature::VerificationAlgorithm, 49 | signature: &str, 50 | message: &[u8], 51 | key: &[u8], 52 | ) -> Result { 53 | let signature_bytes = b64_decode(signature)?; 54 | let public_key = signature::UnparsedPublicKey::new(alg, key); 55 | let res = public_key.verify(message, &signature_bytes); 56 | 57 | Ok(res.is_ok()) 58 | } 59 | 60 | /// Compares the signature given with a re-computed signature for HMAC or using the public key 61 | /// for RSA/EC. 62 | /// 63 | /// If you just want to decode a JWT, use `decode` instead. 64 | /// 65 | /// `signature` is the signature part of a jwt (text after the second '.') 66 | /// 67 | /// `message` is base64(header) + "." + base64(claims) 68 | pub fn verify( 69 | signature: &str, 70 | message: &[u8], 71 | key: &DecodingKey, 72 | algorithm: Algorithm, 73 | ) -> Result { 74 | match algorithm { 75 | Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { 76 | // we just re-sign the message with the key and compare if they are equal 77 | let signed = sign(message, &EncodingKey::from_secret(key.as_bytes()), algorithm)?; 78 | Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok()) 79 | } 80 | Algorithm::ES256 | Algorithm::ES384 => verify_ring( 81 | ecdsa::alg_to_ec_verification(algorithm), 82 | signature, 83 | message, 84 | key.as_bytes(), 85 | ), 86 | Algorithm::EdDSA => verify_ring( 87 | eddsa::alg_to_ec_verification(algorithm), 88 | signature, 89 | message, 90 | key.as_bytes(), 91 | ), 92 | Algorithm::RS256 93 | | Algorithm::RS384 94 | | Algorithm::RS512 95 | | Algorithm::PS256 96 | | Algorithm::PS384 97 | | Algorithm::PS512 => { 98 | let alg = rsa::alg_to_rsa_parameters(algorithm); 99 | match &key.kind { 100 | DecodingKeyKind::SecretOrDer(bytes) => verify_ring(alg, signature, message, bytes), 101 | DecodingKeyKind::RsaModulusExponent { n, e } => { 102 | rsa::verify_from_components(alg, signature, message, (n, e)) 103 | } 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/crypto/rsa.rs: -------------------------------------------------------------------------------- 1 | use ring::{rand, signature}; 2 | 3 | use crate::algorithms::Algorithm; 4 | use crate::errors::{ErrorKind, Result}; 5 | use crate::serialization::{b64_decode, b64_encode}; 6 | 7 | /// Only used internally when validating RSA, to map from our enum to the Ring param structs. 8 | pub(crate) fn alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters { 9 | match alg { 10 | Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256, 11 | Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384, 12 | Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512, 13 | Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256, 14 | Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384, 15 | Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512, 16 | _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), 17 | } 18 | } 19 | 20 | /// Only used internally when signing with RSA, to map from our enum to the Ring signing structs. 21 | pub(crate) fn alg_to_rsa_signing(alg: Algorithm) -> &'static dyn signature::RsaEncoding { 22 | match alg { 23 | Algorithm::RS256 => &signature::RSA_PKCS1_SHA256, 24 | Algorithm::RS384 => &signature::RSA_PKCS1_SHA384, 25 | Algorithm::RS512 => &signature::RSA_PKCS1_SHA512, 26 | Algorithm::PS256 => &signature::RSA_PSS_SHA256, 27 | Algorithm::PS384 => &signature::RSA_PSS_SHA384, 28 | Algorithm::PS512 => &signature::RSA_PSS_SHA512, 29 | _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), 30 | } 31 | } 32 | 33 | /// The actual RSA signing + encoding 34 | /// The key needs to be in PKCS8 format 35 | /// Taken from Ring doc https://docs.rs/ring/latest/ring/signature/index.html 36 | pub(crate) fn sign( 37 | alg: &'static dyn signature::RsaEncoding, 38 | key: &[u8], 39 | message: &[u8], 40 | ) -> Result { 41 | let key_pair = signature::RsaKeyPair::from_der(key) 42 | .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; 43 | 44 | let mut signature = vec![0; key_pair.public().modulus_len()]; 45 | let rng = rand::SystemRandom::new(); 46 | key_pair.sign(alg, &rng, message, &mut signature).map_err(|_| ErrorKind::RsaFailedSigning)?; 47 | 48 | Ok(b64_encode(signature)) 49 | } 50 | 51 | /// Checks that a signature is valid based on the (n, e) RSA pubkey components 52 | pub(crate) fn verify_from_components( 53 | alg: &'static signature::RsaParameters, 54 | signature: &str, 55 | message: &[u8], 56 | components: (&[u8], &[u8]), 57 | ) -> Result { 58 | let signature_bytes = b64_decode(signature)?; 59 | let pubkey = signature::RsaPublicKeyComponents { n: components.0, e: components.1 }; 60 | let res = pubkey.verify(alg, message, &signature_bytes); 61 | Ok(res.is_ok()) 62 | } 63 | -------------------------------------------------------------------------------- /src/decoding.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine::general_purpose::STANDARD, Engine}; 2 | use serde::de::DeserializeOwned; 3 | 4 | use crate::algorithms::AlgorithmFamily; 5 | use crate::crypto::verify; 6 | use crate::errors::{new_error, ErrorKind, Result}; 7 | use crate::header::Header; 8 | use crate::jwk::{AlgorithmParameters, Jwk}; 9 | #[cfg(feature = "use_pem")] 10 | use crate::pem::decoder::PemEncodedKey; 11 | use crate::serialization::{b64_decode, DecodedJwtPartClaims}; 12 | use crate::validation::{validate, Validation}; 13 | 14 | /// The return type of a successful call to [decode](fn.decode.html). 15 | #[derive(Debug)] 16 | pub struct TokenData { 17 | /// The decoded JWT header 18 | pub header: Header, 19 | /// The decoded JWT claims 20 | pub claims: T, 21 | } 22 | 23 | impl Clone for TokenData 24 | where 25 | T: Clone, 26 | { 27 | fn clone(&self) -> Self { 28 | Self { header: self.header.clone(), claims: self.claims.clone() } 29 | } 30 | } 31 | 32 | /// Takes the result of a rsplit and ensure we only get 2 parts 33 | /// Errors if we don't 34 | macro_rules! expect_two { 35 | ($iter:expr) => {{ 36 | let mut i = $iter; 37 | match (i.next(), i.next(), i.next()) { 38 | (Some(first), Some(second), None) => (first, second), 39 | _ => return Err(new_error(ErrorKind::InvalidToken)), 40 | } 41 | }}; 42 | } 43 | 44 | #[derive(Clone)] 45 | pub(crate) enum DecodingKeyKind { 46 | SecretOrDer(Vec), 47 | RsaModulusExponent { n: Vec, e: Vec }, 48 | } 49 | 50 | /// All the different kind of keys we can use to decode a JWT. 51 | /// This key can be re-used so make sure you only initialize it once if you can for better performance. 52 | #[derive(Clone)] 53 | pub struct DecodingKey { 54 | pub(crate) family: AlgorithmFamily, 55 | pub(crate) kind: DecodingKeyKind, 56 | } 57 | 58 | impl DecodingKey { 59 | /// If you're using HMAC, use this. 60 | pub fn from_secret(secret: &[u8]) -> Self { 61 | DecodingKey { 62 | family: AlgorithmFamily::Hmac, 63 | kind: DecodingKeyKind::SecretOrDer(secret.to_vec()), 64 | } 65 | } 66 | 67 | /// If you're using HMAC with a base64 encoded secret, use this. 68 | pub fn from_base64_secret(secret: &str) -> Result { 69 | let out = STANDARD.decode(secret)?; 70 | Ok(DecodingKey { family: AlgorithmFamily::Hmac, kind: DecodingKeyKind::SecretOrDer(out) }) 71 | } 72 | 73 | /// If you are loading a public RSA key in a PEM format, use this. 74 | /// Only exists if the feature `use_pem` is enabled. 75 | #[cfg(feature = "use_pem")] 76 | pub fn from_rsa_pem(key: &[u8]) -> Result { 77 | let pem_key = PemEncodedKey::new(key)?; 78 | let content = pem_key.as_rsa_key()?; 79 | Ok(DecodingKey { 80 | family: AlgorithmFamily::Rsa, 81 | kind: DecodingKeyKind::SecretOrDer(content.to_vec()), 82 | }) 83 | } 84 | 85 | /// If you have (n, e) RSA public key components as strings, use this. 86 | pub fn from_rsa_components(modulus: &str, exponent: &str) -> Result { 87 | let n = b64_decode(modulus)?; 88 | let e = b64_decode(exponent)?; 89 | Ok(DecodingKey { 90 | family: AlgorithmFamily::Rsa, 91 | kind: DecodingKeyKind::RsaModulusExponent { n, e }, 92 | }) 93 | } 94 | 95 | /// If you have (n, e) RSA public key components already decoded, use this. 96 | pub fn from_rsa_raw_components(modulus: &[u8], exponent: &[u8]) -> Self { 97 | DecodingKey { 98 | family: AlgorithmFamily::Rsa, 99 | kind: DecodingKeyKind::RsaModulusExponent { n: modulus.to_vec(), e: exponent.to_vec() }, 100 | } 101 | } 102 | 103 | /// If you have a ECDSA public key in PEM format, use this. 104 | /// Only exists if the feature `use_pem` is enabled. 105 | #[cfg(feature = "use_pem")] 106 | pub fn from_ec_pem(key: &[u8]) -> Result { 107 | let pem_key = PemEncodedKey::new(key)?; 108 | let content = pem_key.as_ec_public_key()?; 109 | Ok(DecodingKey { 110 | family: AlgorithmFamily::Ec, 111 | kind: DecodingKeyKind::SecretOrDer(content.to_vec()), 112 | }) 113 | } 114 | 115 | /// If you have (x,y) ECDSA key components 116 | pub fn from_ec_components(x: &str, y: &str) -> Result { 117 | let x_cmp = b64_decode(x)?; 118 | let y_cmp = b64_decode(y)?; 119 | 120 | let mut public_key = Vec::with_capacity(1 + x.len() + y.len()); 121 | public_key.push(0x04); 122 | public_key.extend_from_slice(&x_cmp); 123 | public_key.extend_from_slice(&y_cmp); 124 | 125 | Ok(DecodingKey { 126 | family: AlgorithmFamily::Ec, 127 | kind: DecodingKeyKind::SecretOrDer(public_key), 128 | }) 129 | } 130 | 131 | /// If you have a EdDSA public key in PEM format, use this. 132 | /// Only exists if the feature `use_pem` is enabled. 133 | #[cfg(feature = "use_pem")] 134 | pub fn from_ed_pem(key: &[u8]) -> Result { 135 | let pem_key = PemEncodedKey::new(key)?; 136 | let content = pem_key.as_ed_public_key()?; 137 | Ok(DecodingKey { 138 | family: AlgorithmFamily::Ed, 139 | kind: DecodingKeyKind::SecretOrDer(content.to_vec()), 140 | }) 141 | } 142 | 143 | /// If you know what you're doing and have a RSA DER encoded public key, use this. 144 | pub fn from_rsa_der(der: &[u8]) -> Self { 145 | DecodingKey { 146 | family: AlgorithmFamily::Rsa, 147 | kind: DecodingKeyKind::SecretOrDer(der.to_vec()), 148 | } 149 | } 150 | 151 | /// If you know what you're doing and have a RSA EC encoded public key, use this. 152 | pub fn from_ec_der(der: &[u8]) -> Self { 153 | DecodingKey { 154 | family: AlgorithmFamily::Ec, 155 | kind: DecodingKeyKind::SecretOrDer(der.to_vec()), 156 | } 157 | } 158 | 159 | /// If you know what you're doing and have a Ed DER encoded public key, use this. 160 | pub fn from_ed_der(der: &[u8]) -> Self { 161 | DecodingKey { 162 | family: AlgorithmFamily::Ed, 163 | kind: DecodingKeyKind::SecretOrDer(der.to_vec()), 164 | } 165 | } 166 | 167 | /// From x part (base64 encoded) of the JWK encoding 168 | pub fn from_ed_components(x: &str) -> Result { 169 | let x_decoded = b64_decode(x)?; 170 | Ok(DecodingKey { 171 | family: AlgorithmFamily::Ed, 172 | kind: DecodingKeyKind::SecretOrDer(x_decoded), 173 | }) 174 | } 175 | 176 | /// If you have a key in Jwk format 177 | pub fn from_jwk(jwk: &Jwk) -> Result { 178 | match &jwk.algorithm { 179 | AlgorithmParameters::RSA(params) => { 180 | DecodingKey::from_rsa_components(¶ms.n, ¶ms.e) 181 | } 182 | AlgorithmParameters::EllipticCurve(params) => { 183 | DecodingKey::from_ec_components(¶ms.x, ¶ms.y) 184 | } 185 | AlgorithmParameters::OctetKeyPair(params) => DecodingKey::from_ed_components(¶ms.x), 186 | AlgorithmParameters::OctetKey(params) => { 187 | let out = b64_decode(¶ms.value)?; 188 | Ok(DecodingKey { 189 | family: AlgorithmFamily::Hmac, 190 | kind: DecodingKeyKind::SecretOrDer(out), 191 | }) 192 | } 193 | } 194 | } 195 | 196 | pub(crate) fn as_bytes(&self) -> &[u8] { 197 | match &self.kind { 198 | DecodingKeyKind::SecretOrDer(b) => b, 199 | DecodingKeyKind::RsaModulusExponent { .. } => unreachable!(), 200 | } 201 | } 202 | } 203 | 204 | /// Verify signature of a JWT, and return header object and raw payload 205 | /// 206 | /// If the token or its signature is invalid, it will return an error. 207 | fn verify_signature<'a>( 208 | token: &'a str, 209 | key: &DecodingKey, 210 | validation: &Validation, 211 | ) -> Result<(Header, &'a str)> { 212 | if validation.validate_signature && validation.algorithms.is_empty() { 213 | return Err(new_error(ErrorKind::MissingAlgorithm)); 214 | } 215 | 216 | if validation.validate_signature { 217 | for alg in &validation.algorithms { 218 | if key.family != alg.family() { 219 | return Err(new_error(ErrorKind::InvalidAlgorithm)); 220 | } 221 | } 222 | } 223 | 224 | let (signature, message) = expect_two!(token.rsplitn(2, '.')); 225 | let (payload, header) = expect_two!(message.rsplitn(2, '.')); 226 | let header = Header::from_encoded(header)?; 227 | 228 | if validation.validate_signature && !validation.algorithms.contains(&header.alg) { 229 | return Err(new_error(ErrorKind::InvalidAlgorithm)); 230 | } 231 | 232 | if validation.validate_signature && !verify(signature, message.as_bytes(), key, header.alg)? { 233 | return Err(new_error(ErrorKind::InvalidSignature)); 234 | } 235 | 236 | Ok((header, payload)) 237 | } 238 | 239 | /// Decode and validate a JWT 240 | /// 241 | /// If the token or its signature is invalid or the claims fail validation, it will return an error. 242 | /// 243 | /// ```rust 244 | /// use serde::{Deserialize, Serialize}; 245 | /// use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm}; 246 | /// 247 | /// #[derive(Debug, Serialize, Deserialize)] 248 | /// struct Claims { 249 | /// sub: String, 250 | /// company: String 251 | /// } 252 | /// 253 | /// let token = "a.jwt.token".to_string(); 254 | /// // Claims is a struct that implements Deserialize 255 | /// let token_message = decode::(&token, &DecodingKey::from_secret("secret".as_ref()), &Validation::new(Algorithm::HS256)); 256 | /// ``` 257 | pub fn decode( 258 | token: &str, 259 | key: &DecodingKey, 260 | validation: &Validation, 261 | ) -> Result> { 262 | match verify_signature(token, key, validation) { 263 | Err(e) => Err(e), 264 | Ok((header, claims)) => { 265 | let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?; 266 | let claims = decoded_claims.deserialize()?; 267 | validate(decoded_claims.deserialize()?, validation)?; 268 | 269 | Ok(TokenData { header, claims }) 270 | } 271 | } 272 | } 273 | 274 | /// Decode a JWT without any signature verification/validations and return its [Header](struct.Header.html). 275 | /// 276 | /// If the token has an invalid format (ie 3 parts separated by a `.`), it will return an error. 277 | /// 278 | /// ```rust 279 | /// use jsonwebtoken::decode_header; 280 | /// 281 | /// let token = "a.jwt.token".to_string(); 282 | /// let header = decode_header(&token); 283 | /// ``` 284 | pub fn decode_header(token: &str) -> Result
{ 285 | let (_, message) = expect_two!(token.rsplitn(2, '.')); 286 | let (_, header) = expect_two!(message.rsplitn(2, '.')); 287 | Header::from_encoded(header) 288 | } 289 | -------------------------------------------------------------------------------- /src/encoding.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine::general_purpose::STANDARD, Engine}; 2 | use serde::ser::Serialize; 3 | 4 | use crate::algorithms::AlgorithmFamily; 5 | use crate::crypto; 6 | use crate::errors::{new_error, ErrorKind, Result}; 7 | use crate::header::Header; 8 | #[cfg(feature = "use_pem")] 9 | use crate::pem::decoder::PemEncodedKey; 10 | use crate::serialization::b64_encode_part; 11 | 12 | /// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. 13 | /// This key can be re-used so make sure you only initialize it once if you can for better performance. 14 | #[derive(Clone)] 15 | pub struct EncodingKey { 16 | pub(crate) family: AlgorithmFamily, 17 | content: Vec, 18 | } 19 | 20 | impl EncodingKey { 21 | /// If you're using a HMAC secret that is not base64, use that. 22 | pub fn from_secret(secret: &[u8]) -> Self { 23 | EncodingKey { family: AlgorithmFamily::Hmac, content: secret.to_vec() } 24 | } 25 | 26 | /// If you have a base64 HMAC secret, use that. 27 | pub fn from_base64_secret(secret: &str) -> Result { 28 | let out = STANDARD.decode(secret)?; 29 | Ok(EncodingKey { family: AlgorithmFamily::Hmac, content: out }) 30 | } 31 | 32 | /// If you are loading a RSA key from a .pem file. 33 | /// This errors if the key is not a valid RSA key. 34 | /// Only exists if the feature `use_pem` is enabled. 35 | /// 36 | /// # NOTE 37 | /// 38 | /// According to the [ring doc](https://docs.rs/ring/latest/ring/signature/struct.RsaKeyPair.html#method.from_pkcs8), 39 | /// the key should be at least 2047 bits. 40 | /// 41 | #[cfg(feature = "use_pem")] 42 | pub fn from_rsa_pem(key: &[u8]) -> Result { 43 | let pem_key = PemEncodedKey::new(key)?; 44 | let content = pem_key.as_rsa_key()?; 45 | Ok(EncodingKey { family: AlgorithmFamily::Rsa, content: content.to_vec() }) 46 | } 47 | 48 | /// If you are loading a ECDSA key from a .pem file 49 | /// This errors if the key is not a valid private EC key 50 | /// Only exists if the feature `use_pem` is enabled. 51 | /// 52 | /// # NOTE 53 | /// 54 | /// The key should be in PKCS#8 form. 55 | /// 56 | /// You can generate a key with the following: 57 | /// 58 | /// ```sh 59 | /// openssl ecparam -genkey -noout -name prime256v1 \ 60 | /// | openssl pkcs8 -topk8 -nocrypt -out ec-private.pem 61 | /// ``` 62 | #[cfg(feature = "use_pem")] 63 | pub fn from_ec_pem(key: &[u8]) -> Result { 64 | let pem_key = PemEncodedKey::new(key)?; 65 | let content = pem_key.as_ec_private_key()?; 66 | Ok(EncodingKey { family: AlgorithmFamily::Ec, content: content.to_vec() }) 67 | } 68 | 69 | /// If you are loading a EdDSA key from a .pem file 70 | /// This errors if the key is not a valid private Ed key 71 | /// Only exists if the feature `use_pem` is enabled. 72 | #[cfg(feature = "use_pem")] 73 | pub fn from_ed_pem(key: &[u8]) -> Result { 74 | let pem_key = PemEncodedKey::new(key)?; 75 | let content = pem_key.as_ed_private_key()?; 76 | Ok(EncodingKey { family: AlgorithmFamily::Ed, content: content.to_vec() }) 77 | } 78 | 79 | /// If you know what you're doing and have the DER-encoded key, for RSA only 80 | pub fn from_rsa_der(der: &[u8]) -> Self { 81 | EncodingKey { family: AlgorithmFamily::Rsa, content: der.to_vec() } 82 | } 83 | 84 | /// If you know what you're doing and have the DER-encoded key, for ECDSA 85 | pub fn from_ec_der(der: &[u8]) -> Self { 86 | EncodingKey { family: AlgorithmFamily::Ec, content: der.to_vec() } 87 | } 88 | 89 | /// If you know what you're doing and have the DER-encoded key, for EdDSA 90 | pub fn from_ed_der(der: &[u8]) -> Self { 91 | EncodingKey { family: AlgorithmFamily::Ed, content: der.to_vec() } 92 | } 93 | 94 | pub(crate) fn inner(&self) -> &[u8] { 95 | &self.content 96 | } 97 | } 98 | 99 | /// Encode the header and claims given and sign the payload using the algorithm from the header and the key. 100 | /// If the algorithm given is RSA or EC, the key needs to be in the PEM format. 101 | /// 102 | /// ```rust 103 | /// use serde::{Deserialize, Serialize}; 104 | /// use jsonwebtoken::{encode, Algorithm, Header, EncodingKey}; 105 | /// 106 | /// #[derive(Debug, Serialize, Deserialize)] 107 | /// struct Claims { 108 | /// sub: String, 109 | /// company: String 110 | /// } 111 | /// 112 | /// let my_claims = Claims { 113 | /// sub: "b@b.com".to_owned(), 114 | /// company: "ACME".to_owned() 115 | /// }; 116 | /// 117 | /// // my_claims is a struct that implements Serialize 118 | /// // This will create a JWT using HS256 as algorithm 119 | /// let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref())).unwrap(); 120 | /// ``` 121 | pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> Result { 122 | if key.family != header.alg.family() { 123 | return Err(new_error(ErrorKind::InvalidAlgorithm)); 124 | } 125 | let encoded_header = b64_encode_part(header)?; 126 | let encoded_claims = b64_encode_part(claims)?; 127 | let message = [encoded_header, encoded_claims].join("."); 128 | let signature = crypto::sign(message.as_bytes(), key, header.alg)?; 129 | 130 | Ok([message, signature].join(".")) 131 | } 132 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | use std::result; 4 | use std::sync::Arc; 5 | 6 | /// A crate private constructor for `Error`. 7 | pub(crate) fn new_error(kind: ErrorKind) -> Error { 8 | Error(Box::new(kind)) 9 | } 10 | 11 | /// A type alias for `Result`. 12 | pub type Result = result::Result; 13 | 14 | /// An error that can occur when encoding/decoding JWTs 15 | #[derive(Clone, Debug, Eq, PartialEq)] 16 | pub struct Error(Box); 17 | 18 | impl Error { 19 | /// Return the specific type of this error. 20 | pub fn kind(&self) -> &ErrorKind { 21 | &self.0 22 | } 23 | 24 | /// Unwrap this error into its underlying type. 25 | pub fn into_kind(self) -> ErrorKind { 26 | *self.0 27 | } 28 | } 29 | 30 | /// The specific type of an error. 31 | /// 32 | /// This enum may grow additional variants, the `#[non_exhaustive]` 33 | /// attribute makes sure clients don't count on exhaustive matching. 34 | /// (Otherwise, adding a new variant could break existing code.) 35 | #[non_exhaustive] 36 | #[derive(Clone, Debug)] 37 | pub enum ErrorKind { 38 | /// When a token doesn't have a valid JWT shape 39 | InvalidToken, 40 | /// When the signature doesn't match 41 | InvalidSignature, 42 | /// When the secret given is not a valid ECDSA key 43 | InvalidEcdsaKey, 44 | /// When the secret given is not a valid RSA key 45 | InvalidRsaKey(String), 46 | /// We could not sign with the given key 47 | RsaFailedSigning, 48 | /// When the algorithm from string doesn't match the one passed to `from_str` 49 | InvalidAlgorithmName, 50 | /// When a key is provided with an invalid format 51 | InvalidKeyFormat, 52 | 53 | // Validation errors 54 | /// When a claim required by the validation is not present 55 | MissingRequiredClaim(String), 56 | /// When a token’s `exp` claim indicates that it has expired 57 | ExpiredSignature, 58 | /// When a token’s `iss` claim does not match the expected issuer 59 | InvalidIssuer, 60 | /// When a token’s `aud` claim does not match one of the expected audience values 61 | InvalidAudience, 62 | /// When a token’s `sub` claim does not match one of the expected subject values 63 | InvalidSubject, 64 | /// When a token’s `nbf` claim represents a time in the future 65 | ImmatureSignature, 66 | /// When the algorithm in the header doesn't match the one passed to `decode` or the encoding/decoding key 67 | /// used doesn't match the alg requested 68 | InvalidAlgorithm, 69 | /// When the Validation struct does not contain at least 1 algorithm 70 | MissingAlgorithm, 71 | 72 | // 3rd party errors 73 | /// An error happened when decoding some base64 text 74 | Base64(base64::DecodeError), 75 | /// An error happened while serializing/deserializing JSON 76 | Json(Arc), 77 | /// Some of the text was invalid UTF-8 78 | Utf8(::std::string::FromUtf8Error), 79 | /// Something unspecified went wrong with crypto 80 | Crypto(::ring::error::Unspecified), 81 | } 82 | 83 | impl StdError for Error { 84 | fn cause(&self) -> Option<&dyn StdError> { 85 | match &*self.0 { 86 | ErrorKind::InvalidToken => None, 87 | ErrorKind::InvalidSignature => None, 88 | ErrorKind::InvalidEcdsaKey => None, 89 | ErrorKind::RsaFailedSigning => None, 90 | ErrorKind::InvalidRsaKey(_) => None, 91 | ErrorKind::ExpiredSignature => None, 92 | ErrorKind::MissingAlgorithm => None, 93 | ErrorKind::MissingRequiredClaim(_) => None, 94 | ErrorKind::InvalidIssuer => None, 95 | ErrorKind::InvalidAudience => None, 96 | ErrorKind::InvalidSubject => None, 97 | ErrorKind::ImmatureSignature => None, 98 | ErrorKind::InvalidAlgorithm => None, 99 | ErrorKind::InvalidAlgorithmName => None, 100 | ErrorKind::InvalidKeyFormat => None, 101 | ErrorKind::Base64(err) => Some(err), 102 | ErrorKind::Json(err) => Some(err.as_ref()), 103 | ErrorKind::Utf8(err) => Some(err), 104 | ErrorKind::Crypto(err) => Some(err), 105 | } 106 | } 107 | } 108 | 109 | impl fmt::Display for Error { 110 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 111 | match &*self.0 { 112 | ErrorKind::InvalidToken 113 | | ErrorKind::InvalidSignature 114 | | ErrorKind::InvalidEcdsaKey 115 | | ErrorKind::ExpiredSignature 116 | | ErrorKind::RsaFailedSigning 117 | | ErrorKind::MissingAlgorithm 118 | | ErrorKind::InvalidIssuer 119 | | ErrorKind::InvalidAudience 120 | | ErrorKind::InvalidSubject 121 | | ErrorKind::ImmatureSignature 122 | | ErrorKind::InvalidAlgorithm 123 | | ErrorKind::InvalidKeyFormat 124 | | ErrorKind::InvalidAlgorithmName => write!(f, "{:?}", self.0), 125 | ErrorKind::MissingRequiredClaim(c) => write!(f, "Missing required claim: {}", c), 126 | ErrorKind::InvalidRsaKey(msg) => write!(f, "RSA key invalid: {}", msg), 127 | ErrorKind::Json(err) => write!(f, "JSON error: {}", err), 128 | ErrorKind::Utf8(err) => write!(f, "UTF-8 error: {}", err), 129 | ErrorKind::Crypto(err) => write!(f, "Crypto error: {}", err), 130 | ErrorKind::Base64(err) => write!(f, "Base64 error: {}", err), 131 | } 132 | } 133 | } 134 | 135 | impl PartialEq for ErrorKind { 136 | fn eq(&self, other: &Self) -> bool { 137 | format!("{:?}", self) == format!("{:?}", other) 138 | } 139 | } 140 | 141 | // Equality of ErrorKind is an equivalence relation: it is reflexive, symmetric and transitive. 142 | impl Eq for ErrorKind {} 143 | 144 | impl From for Error { 145 | fn from(err: base64::DecodeError) -> Error { 146 | new_error(ErrorKind::Base64(err)) 147 | } 148 | } 149 | 150 | impl From for Error { 151 | fn from(err: serde_json::Error) -> Error { 152 | new_error(ErrorKind::Json(Arc::new(err))) 153 | } 154 | } 155 | 156 | impl From<::std::string::FromUtf8Error> for Error { 157 | fn from(err: ::std::string::FromUtf8Error) -> Error { 158 | new_error(ErrorKind::Utf8(err)) 159 | } 160 | } 161 | 162 | impl From<::ring::error::Unspecified> for Error { 163 | fn from(err: ::ring::error::Unspecified) -> Error { 164 | new_error(ErrorKind::Crypto(err)) 165 | } 166 | } 167 | 168 | impl From<::ring::error::KeyRejected> for Error { 169 | fn from(_err: ::ring::error::KeyRejected) -> Error { 170 | new_error(ErrorKind::InvalidEcdsaKey) 171 | } 172 | } 173 | 174 | impl From for Error { 175 | fn from(kind: ErrorKind) -> Error { 176 | new_error(kind) 177 | } 178 | } 179 | 180 | #[cfg(test)] 181 | mod tests { 182 | use wasm_bindgen_test::wasm_bindgen_test; 183 | 184 | use super::*; 185 | 186 | #[test] 187 | #[wasm_bindgen_test] 188 | fn test_error_rendering() { 189 | assert_eq!( 190 | "InvalidAlgorithmName", 191 | Error::from(ErrorKind::InvalidAlgorithmName).to_string() 192 | ); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::result; 3 | 4 | use base64::{engine::general_purpose::STANDARD, Engine}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::algorithms::Algorithm; 8 | use crate::errors::Result; 9 | use crate::jwk::Jwk; 10 | use crate::serialization::b64_decode; 11 | 12 | /// A basic JWT header, the alg defaults to HS256 and typ is automatically 13 | /// set to `JWT`. All the other fields are optional. 14 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 15 | pub struct Header { 16 | /// The type of JWS: it can only be "JWT" here 17 | /// 18 | /// Defined in [RFC7515#4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9). 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub typ: Option, 21 | /// The algorithm used 22 | /// 23 | /// Defined in [RFC7515#4.1.1](https://tools.ietf.org/html/rfc7515#section-4.1.1). 24 | pub alg: Algorithm, 25 | /// Content type 26 | /// 27 | /// Defined in [RFC7519#5.2](https://tools.ietf.org/html/rfc7519#section-5.2). 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | pub cty: Option, 30 | /// JSON Key URL 31 | /// 32 | /// Defined in [RFC7515#4.1.2](https://tools.ietf.org/html/rfc7515#section-4.1.2). 33 | #[serde(skip_serializing_if = "Option::is_none")] 34 | pub jku: Option, 35 | /// JSON Web Key 36 | /// 37 | /// Defined in [RFC7515#4.1.3](https://tools.ietf.org/html/rfc7515#section-4.1.3). 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | pub jwk: Option, 40 | /// Key ID 41 | /// 42 | /// Defined in [RFC7515#4.1.4](https://tools.ietf.org/html/rfc7515#section-4.1.4). 43 | #[serde(skip_serializing_if = "Option::is_none")] 44 | pub kid: Option, 45 | /// X.509 URL 46 | /// 47 | /// Defined in [RFC7515#4.1.5](https://tools.ietf.org/html/rfc7515#section-4.1.5). 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub x5u: Option, 50 | /// X.509 certificate chain. A Vec of base64 encoded ASN.1 DER certificates. 51 | /// 52 | /// Defined in [RFC7515#4.1.6](https://tools.ietf.org/html/rfc7515#section-4.1.6). 53 | #[serde(skip_serializing_if = "Option::is_none")] 54 | pub x5c: Option>, 55 | /// X.509 SHA1 certificate thumbprint 56 | /// 57 | /// Defined in [RFC7515#4.1.7](https://tools.ietf.org/html/rfc7515#section-4.1.7). 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub x5t: Option, 60 | /// X.509 SHA256 certificate thumbprint 61 | /// 62 | /// Defined in [RFC7515#4.1.8](https://tools.ietf.org/html/rfc7515#section-4.1.8). 63 | /// 64 | /// This will be serialized/deserialized as "x5t#S256", as defined by the RFC. 65 | #[serde(skip_serializing_if = "Option::is_none")] 66 | #[serde(rename = "x5t#S256")] 67 | pub x5t_s256: Option, 68 | 69 | /// Any additional non-standard headers not defined in [RFC7515#4.1](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1). 70 | /// Once serialized, all keys will be converted to fields at the root level of the header payload 71 | /// Ex: Dict("custom" -> "header") will be converted to "{"typ": "JWT", ..., "custom": "header"}" 72 | #[serde(flatten)] 73 | pub extras: HashMap, 74 | } 75 | 76 | impl Header { 77 | /// Returns a JWT header with the algorithm given 78 | pub fn new(algorithm: Algorithm) -> Self { 79 | Header { 80 | typ: Some("JWT".to_string()), 81 | alg: algorithm, 82 | cty: None, 83 | jku: None, 84 | jwk: None, 85 | kid: None, 86 | x5u: None, 87 | x5c: None, 88 | x5t: None, 89 | x5t_s256: None, 90 | extras: Default::default(), 91 | } 92 | } 93 | 94 | /// Converts an encoded part into the Header struct if possible 95 | pub(crate) fn from_encoded>(encoded_part: T) -> Result { 96 | let decoded = b64_decode(encoded_part)?; 97 | Ok(serde_json::from_slice(&decoded)?) 98 | } 99 | 100 | /// Decodes the X.509 certificate chain into ASN.1 DER format. 101 | pub fn x5c_der(&self) -> Result>>> { 102 | Ok(self 103 | .x5c 104 | .as_ref() 105 | .map(|b64_certs| { 106 | b64_certs.iter().map(|x| STANDARD.decode(x)).collect::>() 107 | }) 108 | .transpose()?) 109 | } 110 | } 111 | 112 | impl Default for Header { 113 | /// Returns a JWT header using the default Algorithm, HS256 114 | fn default() -> Self { 115 | Header::new(Algorithm::default()) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/jwk.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | //! This crate contains types only for working JWK and JWK Sets 3 | //! This is only meant to be used to deal with public JWK, not generate ones. 4 | //! Most of the code in this file is taken from https://github.com/lawliet89/biscuit but 5 | //! tweaked to remove the private bits as it's not the goal for this crate currently. 6 | 7 | use crate::{ 8 | errors::{self, Error, ErrorKind}, 9 | Algorithm, 10 | }; 11 | use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; 12 | use std::{fmt, str::FromStr}; 13 | 14 | /// The intended usage of the public `KeyType`. This enum is serialized `untagged` 15 | #[derive(Clone, Debug, Eq, PartialEq, Hash)] 16 | pub enum PublicKeyUse { 17 | /// Indicates a public key is meant for signature verification 18 | Signature, 19 | /// Indicates a public key is meant for encryption 20 | Encryption, 21 | /// Other usage 22 | Other(String), 23 | } 24 | 25 | impl Serialize for PublicKeyUse { 26 | fn serialize(&self, serializer: S) -> Result 27 | where 28 | S: Serializer, 29 | { 30 | let string = match self { 31 | PublicKeyUse::Signature => "sig", 32 | PublicKeyUse::Encryption => "enc", 33 | PublicKeyUse::Other(other) => other, 34 | }; 35 | 36 | serializer.serialize_str(string) 37 | } 38 | } 39 | 40 | impl<'de> Deserialize<'de> for PublicKeyUse { 41 | fn deserialize(deserializer: D) -> Result 42 | where 43 | D: Deserializer<'de>, 44 | { 45 | struct PublicKeyUseVisitor; 46 | impl de::Visitor<'_> for PublicKeyUseVisitor { 47 | type Value = PublicKeyUse; 48 | 49 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | write!(formatter, "a string") 51 | } 52 | 53 | fn visit_str(self, v: &str) -> Result 54 | where 55 | E: de::Error, 56 | { 57 | Ok(match v { 58 | "sig" => PublicKeyUse::Signature, 59 | "enc" => PublicKeyUse::Encryption, 60 | other => PublicKeyUse::Other(other.to_string()), 61 | }) 62 | } 63 | } 64 | 65 | deserializer.deserialize_string(PublicKeyUseVisitor) 66 | } 67 | } 68 | 69 | /// Operations that the key is intended to be used for. This enum is serialized `untagged` 70 | #[derive(Clone, Debug, Eq, PartialEq, Hash)] 71 | pub enum KeyOperations { 72 | /// Computer digital signature or MAC 73 | Sign, 74 | /// Verify digital signature or MAC 75 | Verify, 76 | /// Encrypt content 77 | Encrypt, 78 | /// Decrypt content and validate decryption, if applicable 79 | Decrypt, 80 | /// Encrypt key 81 | WrapKey, 82 | /// Decrypt key and validate decryption, if applicable 83 | UnwrapKey, 84 | /// Derive key 85 | DeriveKey, 86 | /// Derive bits not to be used as a key 87 | DeriveBits, 88 | /// Other operation 89 | Other(String), 90 | } 91 | 92 | impl Serialize for KeyOperations { 93 | fn serialize(&self, serializer: S) -> Result 94 | where 95 | S: Serializer, 96 | { 97 | let string = match self { 98 | KeyOperations::Sign => "sign", 99 | KeyOperations::Verify => "verify", 100 | KeyOperations::Encrypt => "encrypt", 101 | KeyOperations::Decrypt => "decrypt", 102 | KeyOperations::WrapKey => "wrapKey", 103 | KeyOperations::UnwrapKey => "unwrapKey", 104 | KeyOperations::DeriveKey => "deriveKey", 105 | KeyOperations::DeriveBits => "deriveBits", 106 | KeyOperations::Other(other) => other, 107 | }; 108 | 109 | serializer.serialize_str(string) 110 | } 111 | } 112 | 113 | impl<'de> Deserialize<'de> for KeyOperations { 114 | fn deserialize(deserializer: D) -> Result 115 | where 116 | D: Deserializer<'de>, 117 | { 118 | struct KeyOperationsVisitor; 119 | impl de::Visitor<'_> for KeyOperationsVisitor { 120 | type Value = KeyOperations; 121 | 122 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 123 | write!(formatter, "a string") 124 | } 125 | 126 | fn visit_str(self, v: &str) -> Result 127 | where 128 | E: de::Error, 129 | { 130 | Ok(match v { 131 | "sign" => KeyOperations::Sign, 132 | "verify" => KeyOperations::Verify, 133 | "encrypt" => KeyOperations::Encrypt, 134 | "decrypt" => KeyOperations::Decrypt, 135 | "wrapKey" => KeyOperations::WrapKey, 136 | "unwrapKey" => KeyOperations::UnwrapKey, 137 | "deriveKey" => KeyOperations::DeriveKey, 138 | "deriveBits" => KeyOperations::DeriveBits, 139 | other => KeyOperations::Other(other.to_string()), 140 | }) 141 | } 142 | } 143 | 144 | deserializer.deserialize_string(KeyOperationsVisitor) 145 | } 146 | } 147 | 148 | /// The algorithms of the keys 149 | #[allow(non_camel_case_types, clippy::upper_case_acronyms)] 150 | #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] 151 | pub enum KeyAlgorithm { 152 | /// HMAC using SHA-256 153 | HS256, 154 | /// HMAC using SHA-384 155 | HS384, 156 | /// HMAC using SHA-512 157 | HS512, 158 | 159 | /// ECDSA using SHA-256 160 | ES256, 161 | /// ECDSA using SHA-384 162 | ES384, 163 | 164 | /// RSASSA-PKCS1-v1_5 using SHA-256 165 | RS256, 166 | /// RSASSA-PKCS1-v1_5 using SHA-384 167 | RS384, 168 | /// RSASSA-PKCS1-v1_5 using SHA-512 169 | RS512, 170 | 171 | /// RSASSA-PSS using SHA-256 172 | PS256, 173 | /// RSASSA-PSS using SHA-384 174 | PS384, 175 | /// RSASSA-PSS using SHA-512 176 | PS512, 177 | 178 | /// Edwards-curve Digital Signature Algorithm (EdDSA) 179 | EdDSA, 180 | 181 | /// RSAES-PKCS1-V1_5 182 | RSA1_5, 183 | 184 | /// RSAES-OAEP using SHA-1 185 | #[serde(rename = "RSA-OAEP")] 186 | RSA_OAEP, 187 | 188 | /// RSAES-OAEP-256 using SHA-2 189 | #[serde(rename = "RSA-OAEP-256")] 190 | RSA_OAEP_256, 191 | } 192 | 193 | impl FromStr for KeyAlgorithm { 194 | type Err = Error; 195 | fn from_str(s: &str) -> errors::Result { 196 | match s { 197 | "HS256" => Ok(KeyAlgorithm::HS256), 198 | "HS384" => Ok(KeyAlgorithm::HS384), 199 | "HS512" => Ok(KeyAlgorithm::HS512), 200 | "ES256" => Ok(KeyAlgorithm::ES256), 201 | "ES384" => Ok(KeyAlgorithm::ES384), 202 | "RS256" => Ok(KeyAlgorithm::RS256), 203 | "RS384" => Ok(KeyAlgorithm::RS384), 204 | "PS256" => Ok(KeyAlgorithm::PS256), 205 | "PS384" => Ok(KeyAlgorithm::PS384), 206 | "PS512" => Ok(KeyAlgorithm::PS512), 207 | "RS512" => Ok(KeyAlgorithm::RS512), 208 | "EdDSA" => Ok(KeyAlgorithm::EdDSA), 209 | "RSA1_5" => Ok(KeyAlgorithm::RSA1_5), 210 | "RSA-OAEP" => Ok(KeyAlgorithm::RSA_OAEP), 211 | "RSA-OAEP-256" => Ok(KeyAlgorithm::RSA_OAEP_256), 212 | _ => Err(ErrorKind::InvalidAlgorithmName.into()), 213 | } 214 | } 215 | } 216 | 217 | impl fmt::Display for KeyAlgorithm { 218 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 219 | write!(f, "{:?}", self) 220 | } 221 | } 222 | 223 | impl KeyAlgorithm { 224 | fn to_algorithm(self) -> errors::Result { 225 | Algorithm::from_str(self.to_string().as_str()) 226 | } 227 | } 228 | 229 | /// Common JWK parameters 230 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default, Hash)] 231 | pub struct CommonParameters { 232 | /// The intended use of the public key. Should not be specified with `key_operations`. 233 | /// See sections 4.2 and 4.3 of [RFC7517](https://tools.ietf.org/html/rfc7517). 234 | #[serde(rename = "use", skip_serializing_if = "Option::is_none", default)] 235 | pub public_key_use: Option, 236 | 237 | /// The "key_ops" (key operations) parameter identifies the operation(s) 238 | /// for which the key is intended to be used. The "key_ops" parameter is 239 | /// intended for use cases in which public, private, or symmetric keys 240 | /// may be present. 241 | /// Should not be specified with `public_key_use`. 242 | /// See sections 4.2 and 4.3 of [RFC7517](https://tools.ietf.org/html/rfc7517). 243 | #[serde(rename = "key_ops", skip_serializing_if = "Option::is_none", default)] 244 | pub key_operations: Option>, 245 | 246 | /// The algorithm keys intended for use with the key. 247 | #[serde(rename = "alg", skip_serializing_if = "Option::is_none", default)] 248 | pub key_algorithm: Option, 249 | 250 | /// The case sensitive Key ID for the key 251 | #[serde(rename = "kid", skip_serializing_if = "Option::is_none", default)] 252 | pub key_id: Option, 253 | 254 | /// X.509 Public key certificate URL. This is currently not implemented (correctly). 255 | /// 256 | /// Serialized to `x5u`. 257 | #[serde(rename = "x5u", skip_serializing_if = "Option::is_none")] 258 | pub x509_url: Option, 259 | 260 | /// X.509 public key certificate chain. This is currently not implemented (correctly). 261 | /// 262 | /// Serialized to `x5c`. 263 | #[serde(rename = "x5c", skip_serializing_if = "Option::is_none")] 264 | pub x509_chain: Option>, 265 | 266 | /// X.509 Certificate SHA1 thumbprint. This is currently not implemented (correctly). 267 | /// 268 | /// Serialized to `x5t`. 269 | #[serde(rename = "x5t", skip_serializing_if = "Option::is_none")] 270 | pub x509_sha1_fingerprint: Option, 271 | 272 | /// X.509 Certificate SHA256 thumbprint. This is currently not implemented (correctly). 273 | /// 274 | /// Serialized to `x5t#S256`. 275 | #[serde(rename = "x5t#S256", skip_serializing_if = "Option::is_none")] 276 | pub x509_sha256_fingerprint: Option, 277 | } 278 | 279 | /// Key type value for an Elliptic Curve Key. 280 | /// This single value enum is a workaround for Rust not supporting associated constants. 281 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] 282 | pub enum EllipticCurveKeyType { 283 | /// Key type value for an Elliptic Curve Key. 284 | #[default] 285 | EC, 286 | } 287 | 288 | /// Type of cryptographic curve used by a key. This is defined in 289 | /// [RFC 7518 #7.6](https://tools.ietf.org/html/rfc7518#section-7.6) 290 | #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] 291 | pub enum EllipticCurve { 292 | /// P-256 curve 293 | #[serde(rename = "P-256")] 294 | #[default] 295 | P256, 296 | /// P-384 curve 297 | #[serde(rename = "P-384")] 298 | P384, 299 | /// P-521 curve -- unsupported by `ring`. 300 | #[serde(rename = "P-521")] 301 | P521, 302 | /// Ed25519 curve 303 | #[serde(rename = "Ed25519")] 304 | Ed25519, 305 | } 306 | 307 | /// Parameters for an Elliptic Curve Key 308 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)] 309 | pub struct EllipticCurveKeyParameters { 310 | /// Key type value for an Elliptic Curve Key. 311 | #[serde(rename = "kty")] 312 | pub key_type: EllipticCurveKeyType, 313 | /// The "crv" (curve) parameter identifies the cryptographic curve used 314 | /// with the key. 315 | #[serde(rename = "crv")] 316 | pub curve: EllipticCurve, 317 | /// The "x" (x coordinate) parameter contains the x coordinate for the 318 | /// Elliptic Curve point. 319 | pub x: String, 320 | /// The "y" (y coordinate) parameter contains the y coordinate for the 321 | /// Elliptic Curve point. 322 | pub y: String, 323 | } 324 | 325 | /// Key type value for an RSA Key. 326 | /// This single value enum is a workaround for Rust not supporting associated constants. 327 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] 328 | pub enum RSAKeyType { 329 | /// Key type value for an RSA Key. 330 | #[default] 331 | RSA, 332 | } 333 | 334 | /// Parameters for a RSA Key 335 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)] 336 | pub struct RSAKeyParameters { 337 | /// Key type value for a RSA Key 338 | #[serde(rename = "kty")] 339 | pub key_type: RSAKeyType, 340 | 341 | /// The "n" (modulus) parameter contains the modulus value for the RSA 342 | /// public key. 343 | pub n: String, 344 | 345 | /// The "e" (exponent) parameter contains the exponent value for the RSA 346 | /// public key. 347 | pub e: String, 348 | } 349 | 350 | /// Key type value for an Octet symmetric key. 351 | /// This single value enum is a workaround for Rust not supporting associated constants. 352 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] 353 | pub enum OctetKeyType { 354 | /// Key type value for an Octet symmetric key. 355 | #[serde(rename = "oct")] 356 | #[default] 357 | Octet, 358 | } 359 | 360 | /// Parameters for an Octet Key 361 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)] 362 | pub struct OctetKeyParameters { 363 | /// Key type value for an Octet Key 364 | #[serde(rename = "kty")] 365 | pub key_type: OctetKeyType, 366 | /// The octet key value 367 | #[serde(rename = "k")] 368 | pub value: String, 369 | } 370 | 371 | /// Key type value for an Octet Key Pair. 372 | /// This single value enum is a workaround for Rust not supporting associated constants. 373 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] 374 | pub enum OctetKeyPairType { 375 | /// Key type value for an Octet Key Pair. 376 | #[serde(rename = "OKP")] 377 | #[default] 378 | OctetKeyPair, 379 | } 380 | 381 | /// Parameters for an Octet Key Pair 382 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)] 383 | pub struct OctetKeyPairParameters { 384 | /// Key type value for an Octet Key Pair 385 | #[serde(rename = "kty")] 386 | pub key_type: OctetKeyPairType, 387 | /// The "crv" (curve) parameter identifies the cryptographic curve used 388 | /// with the key. 389 | #[serde(rename = "crv")] 390 | pub curve: EllipticCurve, 391 | /// The "x" parameter contains the base64 encoded public key 392 | pub x: String, 393 | } 394 | 395 | /// Algorithm specific parameters 396 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] 397 | #[serde(untagged)] 398 | pub enum AlgorithmParameters { 399 | EllipticCurve(EllipticCurveKeyParameters), 400 | RSA(RSAKeyParameters), 401 | OctetKey(OctetKeyParameters), 402 | OctetKeyPair(OctetKeyPairParameters), 403 | } 404 | 405 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] 406 | pub struct Jwk { 407 | #[serde(flatten)] 408 | pub common: CommonParameters, 409 | /// Key algorithm specific parameters 410 | #[serde(flatten)] 411 | pub algorithm: AlgorithmParameters, 412 | } 413 | 414 | impl Jwk { 415 | /// Find whether the Algorithm is implemented and supported 416 | pub fn is_supported(&self) -> bool { 417 | match self.common.key_algorithm { 418 | Some(alg) => alg.to_algorithm().is_ok(), 419 | _ => false, 420 | } 421 | } 422 | } 423 | 424 | /// A JWK set 425 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 426 | pub struct JwkSet { 427 | pub keys: Vec, 428 | } 429 | 430 | impl JwkSet { 431 | /// Find the key in the set that matches the given key id, if any. 432 | pub fn find(&self, kid: &str) -> Option<&Jwk> { 433 | self.keys 434 | .iter() 435 | .find(|jwk| jwk.common.key_id.is_some() && jwk.common.key_id.as_ref().unwrap() == kid) 436 | } 437 | } 438 | 439 | #[cfg(test)] 440 | mod tests { 441 | use crate::jwk::{AlgorithmParameters, JwkSet, OctetKeyType}; 442 | use crate::serialization::b64_encode; 443 | use crate::Algorithm; 444 | use serde_json::json; 445 | use wasm_bindgen_test::wasm_bindgen_test; 446 | 447 | #[test] 448 | #[wasm_bindgen_test] 449 | fn check_hs256() { 450 | let key = b64_encode("abcdefghijklmnopqrstuvwxyz012345"); 451 | let jwks_json = json!({ 452 | "keys": [ 453 | { 454 | "kty": "oct", 455 | "alg": "HS256", 456 | "kid": "abc123", 457 | "k": key 458 | } 459 | ] 460 | }); 461 | 462 | let set: JwkSet = serde_json::from_value(jwks_json).expect("Failed HS256 check"); 463 | assert_eq!(set.keys.len(), 1); 464 | let key = &set.keys[0]; 465 | assert_eq!(key.common.key_id, Some("abc123".to_string())); 466 | let algorithm = key.common.key_algorithm.unwrap().to_algorithm().unwrap(); 467 | assert_eq!(algorithm, Algorithm::HS256); 468 | 469 | match &key.algorithm { 470 | AlgorithmParameters::OctetKey(key) => { 471 | assert_eq!(key.key_type, OctetKeyType::Octet); 472 | assert_eq!(key.value, key.value) 473 | } 474 | _ => panic!("Unexpected key algorithm"), 475 | } 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Create and parses JWT (JSON Web Tokens) 2 | //! 3 | //! Documentation: [stable](https://docs.rs/jsonwebtoken/) 4 | #![deny(missing_docs)] 5 | 6 | mod algorithms; 7 | /// Lower level functions, if you want to do something other than JWTs 8 | pub mod crypto; 9 | mod decoding; 10 | mod encoding; 11 | /// All the errors that can be encountered while encoding/decoding JWTs 12 | pub mod errors; 13 | mod header; 14 | pub mod jwk; 15 | #[cfg(feature = "use_pem")] 16 | mod pem; 17 | mod serialization; 18 | mod validation; 19 | 20 | pub use algorithms::Algorithm; 21 | pub use decoding::{decode, decode_header, DecodingKey, TokenData}; 22 | pub use encoding::{encode, EncodingKey}; 23 | pub use header::Header; 24 | pub use validation::{get_current_timestamp, Validation}; 25 | -------------------------------------------------------------------------------- /src/pem/decoder.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{ErrorKind, Result}; 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 | EdPublic, 11 | EdPrivate, 12 | } 13 | 14 | #[derive(Debug, PartialEq)] 15 | enum Standard { 16 | // Only for RSA 17 | Pkcs1, 18 | // RSA/EC 19 | Pkcs8, 20 | } 21 | 22 | #[derive(Debug, PartialEq)] 23 | enum Classification { 24 | Ec, 25 | Ed, 26 | Rsa, 27 | } 28 | 29 | /// The return type of a successful PEM encoded key with `decode_pem` 30 | /// 31 | /// This struct gives a way to parse a string to a key for use in jsonwebtoken. 32 | /// A struct is necessary as it provides the lifetime of the key 33 | /// 34 | /// PEM public private keys are encoded PKCS#1 or PKCS#8 35 | /// You will find that with PKCS#8 RSA keys that the PKCS#1 content 36 | /// is embedded inside. This is what is provided to ring via `Key::Der` 37 | /// For EC keys, they are always PKCS#8 on the outside but like RSA keys 38 | /// EC keys contain a section within that ultimately has the configuration 39 | /// that ring uses. 40 | /// Documentation about these formats is at 41 | /// PKCS#1: https://tools.ietf.org/html/rfc8017 42 | /// PKCS#8: https://tools.ietf.org/html/rfc5958 43 | #[derive(Debug)] 44 | pub(crate) struct PemEncodedKey { 45 | content: Vec, 46 | asn1: Vec, 47 | pem_type: PemType, 48 | standard: Standard, 49 | } 50 | 51 | impl PemEncodedKey { 52 | /// Read the PEM file for later key use 53 | pub fn new(input: &[u8]) -> Result { 54 | match pem::parse(input) { 55 | Ok(content) => { 56 | let asn1_content = match simple_asn1::from_der(content.contents()) { 57 | Ok(asn1) => asn1, 58 | Err(_) => return Err(ErrorKind::InvalidKeyFormat.into()), 59 | }; 60 | 61 | match content.tag() { 62 | // This handles a PKCS#1 RSA Private key 63 | "RSA PRIVATE KEY" => Ok(PemEncodedKey { 64 | content: content.into_contents(), 65 | asn1: asn1_content, 66 | pem_type: PemType::RsaPrivate, 67 | standard: Standard::Pkcs1, 68 | }), 69 | "RSA PUBLIC KEY" => Ok(PemEncodedKey { 70 | content: content.into_contents(), 71 | asn1: asn1_content, 72 | pem_type: PemType::RsaPublic, 73 | standard: Standard::Pkcs1, 74 | }), 75 | 76 | // No "EC PRIVATE KEY" 77 | // https://security.stackexchange.com/questions/84327/converting-ecc-private-key-to-pkcs1-format 78 | // "there is no such thing as a "PKCS#1 format" for elliptic curve (EC) keys" 79 | 80 | // This handles PKCS#8 certificates and public & private keys 81 | tag @ "PRIVATE KEY" | tag @ "PUBLIC KEY" | tag @ "CERTIFICATE" => { 82 | match classify_pem(&asn1_content) { 83 | Some(c) => { 84 | let is_private = tag == "PRIVATE KEY"; 85 | let pem_type = match c { 86 | Classification::Ec => { 87 | if is_private { 88 | PemType::EcPrivate 89 | } else { 90 | PemType::EcPublic 91 | } 92 | } 93 | Classification::Ed => { 94 | if is_private { 95 | PemType::EdPrivate 96 | } else { 97 | PemType::EdPublic 98 | } 99 | } 100 | Classification::Rsa => { 101 | if is_private { 102 | PemType::RsaPrivate 103 | } else { 104 | PemType::RsaPublic 105 | } 106 | } 107 | }; 108 | Ok(PemEncodedKey { 109 | content: content.into_contents(), 110 | asn1: asn1_content, 111 | pem_type, 112 | standard: Standard::Pkcs8, 113 | }) 114 | } 115 | None => Err(ErrorKind::InvalidKeyFormat.into()), 116 | } 117 | } 118 | 119 | // Unknown/unsupported type 120 | _ => Err(ErrorKind::InvalidKeyFormat.into()), 121 | } 122 | } 123 | Err(_) => Err(ErrorKind::InvalidKeyFormat.into()), 124 | } 125 | } 126 | 127 | /// Can only be PKCS8 128 | pub fn as_ec_private_key(&self) -> Result<&[u8]> { 129 | match self.standard { 130 | Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()), 131 | Standard::Pkcs8 => match self.pem_type { 132 | PemType::EcPrivate => Ok(self.content.as_slice()), 133 | _ => Err(ErrorKind::InvalidKeyFormat.into()), 134 | }, 135 | } 136 | } 137 | 138 | /// Can only be PKCS8 139 | pub fn as_ec_public_key(&self) -> Result<&[u8]> { 140 | match self.standard { 141 | Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()), 142 | Standard::Pkcs8 => match self.pem_type { 143 | PemType::EcPublic => extract_first_bitstring(&self.asn1), 144 | _ => Err(ErrorKind::InvalidKeyFormat.into()), 145 | }, 146 | } 147 | } 148 | 149 | /// Can only be PKCS8 150 | pub fn as_ed_private_key(&self) -> Result<&[u8]> { 151 | match self.standard { 152 | Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()), 153 | Standard::Pkcs8 => match self.pem_type { 154 | PemType::EdPrivate => Ok(self.content.as_slice()), 155 | _ => Err(ErrorKind::InvalidKeyFormat.into()), 156 | }, 157 | } 158 | } 159 | 160 | /// Can only be PKCS8 161 | pub fn as_ed_public_key(&self) -> Result<&[u8]> { 162 | match self.standard { 163 | Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()), 164 | Standard::Pkcs8 => match self.pem_type { 165 | PemType::EdPublic => extract_first_bitstring(&self.asn1), 166 | _ => Err(ErrorKind::InvalidKeyFormat.into()), 167 | }, 168 | } 169 | } 170 | 171 | /// Can be PKCS1 or PKCS8 172 | pub fn as_rsa_key(&self) -> Result<&[u8]> { 173 | match self.standard { 174 | Standard::Pkcs1 => Ok(self.content.as_slice()), 175 | Standard::Pkcs8 => match self.pem_type { 176 | PemType::RsaPrivate => extract_first_bitstring(&self.asn1), 177 | PemType::RsaPublic => extract_first_bitstring(&self.asn1), 178 | _ => Err(ErrorKind::InvalidKeyFormat.into()), 179 | }, 180 | } 181 | } 182 | } 183 | 184 | // This really just finds and returns the first bitstring or octet string 185 | // Which is the x coordinate for EC public keys 186 | // And the DER contents of an RSA key 187 | // Though PKCS#11 keys shouldn't have anything else. 188 | // It will get confusing with certificates. 189 | fn extract_first_bitstring(asn1: &[simple_asn1::ASN1Block]) -> Result<&[u8]> { 190 | for asn1_entry in asn1.iter() { 191 | match asn1_entry { 192 | simple_asn1::ASN1Block::Sequence(_, entries) => { 193 | if let Ok(result) = extract_first_bitstring(entries) { 194 | return Ok(result); 195 | } 196 | } 197 | simple_asn1::ASN1Block::BitString(_, _, value) => { 198 | return Ok(value.as_ref()); 199 | } 200 | simple_asn1::ASN1Block::OctetString(_, value) => { 201 | return Ok(value.as_ref()); 202 | } 203 | _ => (), 204 | } 205 | } 206 | 207 | Err(ErrorKind::InvalidEcdsaKey.into()) 208 | } 209 | 210 | /// Find whether this is EC, RSA, or Ed 211 | fn classify_pem(asn1: &[simple_asn1::ASN1Block]) -> Option { 212 | // These should be constant but the macro requires 213 | // #![feature(const_vec_new)] 214 | let ec_public_key_oid = simple_asn1::oid!(1, 2, 840, 10_045, 2, 1); 215 | let rsa_public_key_oid = simple_asn1::oid!(1, 2, 840, 113_549, 1, 1, 1); 216 | let ed25519_oid = simple_asn1::oid!(1, 3, 101, 112); 217 | 218 | for asn1_entry in asn1.iter() { 219 | match asn1_entry { 220 | simple_asn1::ASN1Block::Sequence(_, entries) => { 221 | if let Some(classification) = classify_pem(entries) { 222 | return Some(classification); 223 | } 224 | } 225 | simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => { 226 | if oid == ec_public_key_oid { 227 | return Some(Classification::Ec); 228 | } 229 | if oid == rsa_public_key_oid { 230 | return Some(Classification::Rsa); 231 | } 232 | if oid == ed25519_oid { 233 | return Some(Classification::Ed); 234 | } 235 | } 236 | _ => {} 237 | } 238 | } 239 | None 240 | } 241 | -------------------------------------------------------------------------------- /src/pem/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod decoder; 2 | -------------------------------------------------------------------------------- /src/serialization.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::errors::Result; 5 | 6 | #[inline] 7 | pub(crate) fn b64_encode>(input: T) -> String { 8 | URL_SAFE_NO_PAD.encode(input) 9 | } 10 | 11 | #[inline] 12 | pub(crate) fn b64_decode>(input: T) -> Result> { 13 | URL_SAFE_NO_PAD.decode(input).map_err(|e| e.into()) 14 | } 15 | 16 | /// Serializes a struct to JSON and encodes it in base64 17 | pub(crate) fn b64_encode_part(input: &T) -> Result { 18 | let json = serde_json::to_vec(input)?; 19 | Ok(b64_encode(json)) 20 | } 21 | 22 | /// This is used to decode from base64 then deserialize from JSON to several structs: 23 | /// - The user-provided struct 24 | /// - The ClaimsForValidation struct from this crate to run validation on 25 | pub(crate) struct DecodedJwtPartClaims { 26 | b64_decoded: Vec, 27 | } 28 | 29 | impl DecodedJwtPartClaims { 30 | pub fn from_jwt_part_claims(encoded_jwt_part_claims: impl AsRef<[u8]>) -> Result { 31 | Ok(Self { b64_decoded: b64_decode(encoded_jwt_part_claims)? }) 32 | } 33 | 34 | pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result { 35 | Ok(serde_json::from_slice(&self.b64_decoded)?) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/validation.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::HashSet; 3 | use std::fmt; 4 | use std::marker::PhantomData; 5 | 6 | use serde::de::{self, Visitor}; 7 | use serde::{Deserialize, Deserializer}; 8 | 9 | use crate::algorithms::Algorithm; 10 | use crate::errors::{new_error, ErrorKind, Result}; 11 | 12 | /// Contains the various validations that are applied after decoding a JWT. 13 | /// 14 | /// All time validation happen on UTC timestamps as seconds. 15 | /// 16 | /// ```rust 17 | /// use jsonwebtoken::{Validation, Algorithm}; 18 | /// 19 | /// let mut validation = Validation::new(Algorithm::HS256); 20 | /// validation.leeway = 5; 21 | /// // Setting audience 22 | /// validation.set_audience(&["Me"]); // a single string 23 | /// validation.set_audience(&["Me", "You"]); // array of strings 24 | /// // or issuer 25 | /// validation.set_issuer(&["Me"]); // a single string 26 | /// validation.set_issuer(&["Me", "You"]); // array of strings 27 | /// // Setting required claims 28 | /// validation.set_required_spec_claims(&["exp", "iss", "aud"]); 29 | /// ``` 30 | #[derive(Debug, Clone, PartialEq, Eq)] 31 | pub struct Validation { 32 | /// Which claims are required to be present before starting the validation. 33 | /// This does not interact with the various `validate_*`. If you remove `exp` from that list, you still need 34 | /// to set `validate_exp` to `false`. 35 | /// The only value that will be used are "exp", "nbf", "aud", "iss", "sub". Anything else will be ignored. 36 | /// 37 | /// Defaults to `{"exp"}` 38 | pub required_spec_claims: HashSet, 39 | /// Add some leeway (in seconds) to the `exp` and `nbf` validation to 40 | /// account for clock skew. 41 | /// 42 | /// Defaults to `60`. 43 | pub leeway: u64, 44 | /// Reject a token some time (in seconds) before the `exp` to prevent 45 | /// expiration in transit over the network. 46 | /// 47 | /// The value is the inverse of `leeway`, subtracting from the validation time. 48 | /// 49 | /// Defaults to `0`. 50 | pub reject_tokens_expiring_in_less_than: u64, 51 | /// Whether to validate the `exp` field. 52 | /// 53 | /// It will return an error if the time in the `exp` field is past. 54 | /// 55 | /// Defaults to `true`. 56 | pub validate_exp: bool, 57 | /// Whether to validate the `nbf` field. 58 | /// 59 | /// It will return an error if the current timestamp is before the time in the `nbf` field. 60 | /// 61 | /// Validation only happens if `nbf` claim is present in the token. 62 | /// Adding `nbf` to `required_spec_claims` will make it required. 63 | /// 64 | /// Defaults to `false`. 65 | pub validate_nbf: bool, 66 | /// Whether to validate the `aud` field. 67 | /// 68 | /// It will return an error if the `aud` field is not a member of the audience provided. 69 | /// 70 | /// Validation only happens if `aud` claim is present in the token. 71 | /// Adding `aud` to `required_spec_claims` will make it required. 72 | /// 73 | /// Defaults to `true`. Very insecure to turn this off. Only do this if you know what you are doing. 74 | pub validate_aud: bool, 75 | /// Validation will check that the `aud` field is a member of the 76 | /// audience provided and will error otherwise. 77 | /// Use `set_audience` to set it 78 | /// 79 | /// Validation only happens if `aud` claim is present in the token. 80 | /// Adding `aud` to `required_spec_claims` will make it required. 81 | /// 82 | /// Defaults to `None`. 83 | pub aud: Option>, 84 | /// If it contains a value, the validation will check that the `iss` field is a member of the 85 | /// iss provided and will error otherwise. 86 | /// Use `set_issuer` to set it 87 | /// 88 | /// Validation only happens if `iss` claim is present in the token. 89 | /// Adding `iss` to `required_spec_claims` will make it required. 90 | /// 91 | /// Defaults to `None`. 92 | pub iss: Option>, 93 | /// If it contains a value, the validation will check that the `sub` field is the same as the 94 | /// one provided and will error otherwise. 95 | /// 96 | /// Validation only happens if `sub` claim is present in the token. 97 | /// Adding `sub` to `required_spec_claims` will make it required. 98 | /// 99 | /// Defaults to `None`. 100 | pub sub: Option, 101 | /// The validation will check that the `alg` of the header is contained 102 | /// in the ones provided and will error otherwise. Will error if it is empty. 103 | /// 104 | /// Defaults to `vec![Algorithm::HS256]`. 105 | pub algorithms: Vec, 106 | 107 | /// Whether to validate the JWT signature. Very insecure to turn that off 108 | pub(crate) validate_signature: bool, 109 | } 110 | 111 | impl Validation { 112 | /// Create a default validation setup allowing the given alg 113 | pub fn new(alg: Algorithm) -> Validation { 114 | let mut required_claims = HashSet::with_capacity(1); 115 | required_claims.insert("exp".to_owned()); 116 | 117 | Validation { 118 | required_spec_claims: required_claims, 119 | algorithms: vec![alg], 120 | leeway: 60, 121 | reject_tokens_expiring_in_less_than: 0, 122 | 123 | validate_exp: true, 124 | validate_nbf: false, 125 | validate_aud: true, 126 | 127 | iss: None, 128 | sub: None, 129 | aud: None, 130 | 131 | validate_signature: true, 132 | } 133 | } 134 | 135 | /// `aud` is a collection of one or more acceptable audience members 136 | /// The simple usage is `set_audience(&["some aud name"])` 137 | pub fn set_audience(&mut self, items: &[T]) { 138 | self.aud = Some(items.iter().map(|x| x.to_string()).collect()) 139 | } 140 | 141 | /// `iss` is a collection of one or more acceptable issuers members 142 | /// The simple usage is `set_issuer(&["some iss name"])` 143 | pub fn set_issuer(&mut self, items: &[T]) { 144 | self.iss = Some(items.iter().map(|x| x.to_string()).collect()) 145 | } 146 | 147 | /// Which claims are required to be present for this JWT to be considered valid. 148 | /// The only values that will be considered are "exp", "nbf", "aud", "iss", "sub". 149 | /// The simple usage is `set_required_spec_claims(&["exp", "nbf"])`. 150 | /// If you want to have an empty set, do not use this function - set an empty set on the struct 151 | /// param directly. 152 | pub fn set_required_spec_claims(&mut self, items: &[T]) { 153 | self.required_spec_claims = items.iter().map(|x| x.to_string()).collect(); 154 | } 155 | 156 | /// Whether to validate the JWT cryptographic signature. 157 | /// Disabling validation is dangerous, only do it if you know what you're doing. 158 | /// With validation disabled you should not trust any of the values of the claims. 159 | pub fn insecure_disable_signature_validation(&mut self) { 160 | self.validate_signature = false; 161 | } 162 | } 163 | 164 | impl Default for Validation { 165 | fn default() -> Self { 166 | Self::new(Algorithm::HS256) 167 | } 168 | } 169 | 170 | /// Gets the current timestamp in the format expected by JWTs. 171 | #[cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))] 172 | #[must_use] 173 | pub fn get_current_timestamp() -> u64 { 174 | let start = std::time::SystemTime::now(); 175 | start.duration_since(std::time::UNIX_EPOCH).expect("Time went backwards").as_secs() 176 | } 177 | 178 | /// Gets the current timestamp in the format expected by JWTs. 179 | #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] 180 | #[must_use] 181 | pub fn get_current_timestamp() -> u64 { 182 | js_sys::Date::new_0().get_time() as u64 / 1000 183 | } 184 | 185 | #[derive(Deserialize)] 186 | pub(crate) struct ClaimsForValidation<'a> { 187 | #[serde(deserialize_with = "numeric_type", default)] 188 | exp: TryParse, 189 | #[serde(deserialize_with = "numeric_type", default)] 190 | nbf: TryParse, 191 | #[serde(borrow)] 192 | sub: TryParse>, 193 | #[serde(borrow)] 194 | iss: TryParse>, 195 | #[serde(borrow)] 196 | aud: TryParse>, 197 | } 198 | #[derive(Debug)] 199 | enum TryParse { 200 | Parsed(T), 201 | FailedToParse, 202 | NotPresent, 203 | } 204 | impl<'de, T: Deserialize<'de>> Deserialize<'de> for TryParse { 205 | fn deserialize>( 206 | deserializer: D, 207 | ) -> std::result::Result { 208 | Ok(match Option::::deserialize(deserializer) { 209 | Ok(Some(value)) => TryParse::Parsed(value), 210 | Ok(None) => TryParse::NotPresent, 211 | Err(_) => TryParse::FailedToParse, 212 | }) 213 | } 214 | } 215 | impl Default for TryParse { 216 | fn default() -> Self { 217 | Self::NotPresent 218 | } 219 | } 220 | 221 | #[derive(Deserialize)] 222 | #[serde(untagged)] 223 | enum Audience<'a> { 224 | Single(#[serde(borrow)] Cow<'a, str>), 225 | Multiple(#[serde(borrow)] HashSet>), 226 | } 227 | 228 | #[derive(Deserialize)] 229 | #[serde(untagged)] 230 | enum Issuer<'a> { 231 | Single(#[serde(borrow)] Cow<'a, str>), 232 | Multiple(#[serde(borrow)] HashSet>), 233 | } 234 | 235 | /// Usually #[serde(borrow)] on `Cow` enables deserializing with no allocations where 236 | /// possible (no escapes in the original str) but it does not work on e.g. `HashSet>` 237 | /// We use this struct in this case. 238 | #[derive(Deserialize, PartialEq, Eq, Hash)] 239 | struct BorrowedCowIfPossible<'a>(#[serde(borrow)] Cow<'a, str>); 240 | impl std::borrow::Borrow for BorrowedCowIfPossible<'_> { 241 | fn borrow(&self) -> &str { 242 | &self.0 243 | } 244 | } 245 | 246 | fn is_subset(reference: &HashSet, given: &HashSet>) -> bool { 247 | // Check that intersection is non-empty, favoring iterating on smallest 248 | if reference.len() < given.len() { 249 | reference.iter().any(|a| given.contains(&**a)) 250 | } else { 251 | given.iter().any(|a| reference.contains(&*a.0)) 252 | } 253 | } 254 | 255 | pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Result<()> { 256 | for required_claim in &options.required_spec_claims { 257 | let present = match required_claim.as_str() { 258 | "exp" => matches!(claims.exp, TryParse::Parsed(_)), 259 | "sub" => matches!(claims.sub, TryParse::Parsed(_)), 260 | "iss" => matches!(claims.iss, TryParse::Parsed(_)), 261 | "aud" => matches!(claims.aud, TryParse::Parsed(_)), 262 | "nbf" => matches!(claims.nbf, TryParse::Parsed(_)), 263 | _ => continue, 264 | }; 265 | 266 | if !present { 267 | return Err(new_error(ErrorKind::MissingRequiredClaim(required_claim.clone()))); 268 | } 269 | } 270 | 271 | if options.validate_exp || options.validate_nbf { 272 | let now = get_current_timestamp(); 273 | 274 | if matches!(claims.exp, TryParse::Parsed(exp) if options.validate_exp 275 | && exp - options.reject_tokens_expiring_in_less_than < now - options.leeway ) 276 | { 277 | return Err(new_error(ErrorKind::ExpiredSignature)); 278 | } 279 | 280 | if matches!(claims.nbf, TryParse::Parsed(nbf) if options.validate_nbf && nbf > now + options.leeway) 281 | { 282 | return Err(new_error(ErrorKind::ImmatureSignature)); 283 | } 284 | } 285 | 286 | if let (TryParse::Parsed(sub), Some(correct_sub)) = (claims.sub, options.sub.as_deref()) { 287 | if sub != correct_sub { 288 | return Err(new_error(ErrorKind::InvalidSubject)); 289 | } 290 | } 291 | 292 | match (claims.iss, options.iss.as_ref()) { 293 | (TryParse::Parsed(Issuer::Single(iss)), Some(correct_iss)) => { 294 | if !correct_iss.contains(&*iss) { 295 | return Err(new_error(ErrorKind::InvalidIssuer)); 296 | } 297 | } 298 | (TryParse::Parsed(Issuer::Multiple(iss)), Some(correct_iss)) => { 299 | if !is_subset(correct_iss, &iss) { 300 | return Err(new_error(ErrorKind::InvalidIssuer)); 301 | } 302 | } 303 | _ => {} 304 | } 305 | 306 | if !options.validate_aud { 307 | return Ok(()); 308 | } 309 | match (claims.aud, options.aud.as_ref()) { 310 | // Each principal intended to process the JWT MUST 311 | // identify itself with a value in the audience claim. If the principal 312 | // processing the claim does not identify itself with a value in the 313 | // "aud" claim when this claim is present, then the JWT MUST be 314 | // rejected. 315 | (TryParse::Parsed(_), None) => { 316 | return Err(new_error(ErrorKind::InvalidAudience)); 317 | } 318 | (TryParse::Parsed(Audience::Single(aud)), Some(correct_aud)) => { 319 | if !correct_aud.contains(&*aud) { 320 | return Err(new_error(ErrorKind::InvalidAudience)); 321 | } 322 | } 323 | (TryParse::Parsed(Audience::Multiple(aud)), Some(correct_aud)) => { 324 | if !is_subset(correct_aud, &aud) { 325 | return Err(new_error(ErrorKind::InvalidAudience)); 326 | } 327 | } 328 | _ => {} 329 | } 330 | 331 | Ok(()) 332 | } 333 | 334 | fn numeric_type<'de, D>(deserializer: D) -> std::result::Result, D::Error> 335 | where 336 | D: Deserializer<'de>, 337 | { 338 | struct NumericType(PhantomData TryParse>); 339 | 340 | impl Visitor<'_> for NumericType { 341 | type Value = TryParse; 342 | 343 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 344 | formatter.write_str("A NumericType that can be reasonably coerced into a u64") 345 | } 346 | 347 | fn visit_u64(self, value: u64) -> std::result::Result 348 | where 349 | E: de::Error, 350 | { 351 | Ok(TryParse::Parsed(value)) 352 | } 353 | 354 | fn visit_f64(self, value: f64) -> std::result::Result 355 | where 356 | E: de::Error, 357 | { 358 | if value.is_finite() && value >= 0.0 && value < (u64::MAX as f64) { 359 | Ok(TryParse::Parsed(value.round() as u64)) 360 | } else { 361 | Err(serde::de::Error::custom("NumericType must be representable as a u64")) 362 | } 363 | } 364 | } 365 | 366 | match deserializer.deserialize_any(NumericType(PhantomData)) { 367 | Ok(ok) => Ok(ok), 368 | Err(_) => Ok(TryParse::FailedToParse), 369 | } 370 | } 371 | 372 | #[cfg(test)] 373 | mod tests { 374 | use serde_json::json; 375 | use wasm_bindgen_test::wasm_bindgen_test; 376 | 377 | use super::{get_current_timestamp, validate, ClaimsForValidation, Validation}; 378 | 379 | use crate::errors::ErrorKind; 380 | use crate::Algorithm; 381 | use std::collections::HashSet; 382 | 383 | fn deserialize_claims(claims: &serde_json::Value) -> ClaimsForValidation { 384 | serde::Deserialize::deserialize(claims).unwrap() 385 | } 386 | 387 | #[test] 388 | #[wasm_bindgen_test] 389 | fn exp_in_future_ok() { 390 | let claims = json!({ "exp": get_current_timestamp() + 10000 }); 391 | let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); 392 | assert!(res.is_ok()); 393 | } 394 | 395 | #[test] 396 | #[wasm_bindgen_test] 397 | fn exp_in_future_but_in_rejection_period_fails() { 398 | let claims = json!({ "exp": get_current_timestamp() + 500 }); 399 | let mut validation = Validation::new(Algorithm::HS256); 400 | validation.leeway = 0; 401 | validation.reject_tokens_expiring_in_less_than = 501; 402 | let res = validate(deserialize_claims(&claims), &validation); 403 | assert!(res.is_err()); 404 | } 405 | 406 | #[test] 407 | #[wasm_bindgen_test] 408 | fn exp_float_in_future_ok() { 409 | let claims = json!({ "exp": (get_current_timestamp() as f64) + 10000.123 }); 410 | let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); 411 | assert!(res.is_ok()); 412 | } 413 | 414 | #[test] 415 | #[wasm_bindgen_test] 416 | fn exp_float_in_future_but_in_rejection_period_fails() { 417 | let claims = json!({ "exp": (get_current_timestamp() as f64) + 500.123 }); 418 | let mut validation = Validation::new(Algorithm::HS256); 419 | validation.leeway = 0; 420 | validation.reject_tokens_expiring_in_less_than = 501; 421 | let res = validate(deserialize_claims(&claims), &validation); 422 | assert!(res.is_err()); 423 | } 424 | 425 | #[test] 426 | #[wasm_bindgen_test] 427 | fn exp_in_past_fails() { 428 | let claims = json!({ "exp": get_current_timestamp() - 100000 }); 429 | let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); 430 | assert!(res.is_err()); 431 | 432 | match res.unwrap_err().kind() { 433 | ErrorKind::ExpiredSignature => (), 434 | _ => unreachable!(), 435 | }; 436 | } 437 | 438 | #[test] 439 | #[wasm_bindgen_test] 440 | fn exp_float_in_past_fails() { 441 | let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 }); 442 | let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256)); 443 | assert!(res.is_err()); 444 | 445 | match res.unwrap_err().kind() { 446 | ErrorKind::ExpiredSignature => (), 447 | _ => unreachable!(), 448 | }; 449 | } 450 | 451 | #[test] 452 | #[wasm_bindgen_test] 453 | fn exp_in_past_but_in_leeway_ok() { 454 | let claims = json!({ "exp": get_current_timestamp() - 500 }); 455 | let mut validation = Validation::new(Algorithm::HS256); 456 | validation.leeway = 1000 * 60; 457 | let res = validate(deserialize_claims(&claims), &validation); 458 | assert!(res.is_ok()); 459 | } 460 | 461 | // https://github.com/Keats/jsonwebtoken/issues/51 462 | #[test] 463 | #[wasm_bindgen_test] 464 | fn validate_required_fields_are_present() { 465 | for spec_claim in ["exp", "nbf", "aud", "iss", "sub"] { 466 | let claims = json!({}); 467 | let mut validation = Validation::new(Algorithm::HS256); 468 | validation.set_required_spec_claims(&[spec_claim]); 469 | let res = validate(deserialize_claims(&claims), &validation).unwrap_err(); 470 | assert_eq!(res.kind(), &ErrorKind::MissingRequiredClaim(spec_claim.to_owned())); 471 | } 472 | } 473 | 474 | #[test] 475 | #[wasm_bindgen_test] 476 | fn exp_validated_but_not_required_ok() { 477 | let claims = json!({}); 478 | let mut validation = Validation::new(Algorithm::HS256); 479 | validation.required_spec_claims = HashSet::new(); 480 | validation.validate_exp = true; 481 | let res = validate(deserialize_claims(&claims), &validation); 482 | assert!(res.is_ok()); 483 | } 484 | 485 | #[test] 486 | #[wasm_bindgen_test] 487 | fn exp_validated_but_not_required_fails() { 488 | let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 }); 489 | let mut validation = Validation::new(Algorithm::HS256); 490 | validation.required_spec_claims = HashSet::new(); 491 | validation.validate_exp = true; 492 | let res = validate(deserialize_claims(&claims), &validation); 493 | assert!(res.is_err()); 494 | } 495 | 496 | #[test] 497 | #[wasm_bindgen_test] 498 | fn exp_required_but_not_validated_ok() { 499 | let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 }); 500 | let mut validation = Validation::new(Algorithm::HS256); 501 | validation.set_required_spec_claims(&["exp"]); 502 | validation.validate_exp = false; 503 | let res = validate(deserialize_claims(&claims), &validation); 504 | assert!(res.is_ok()); 505 | } 506 | 507 | #[test] 508 | #[wasm_bindgen_test] 509 | fn exp_required_but_not_validated_fails() { 510 | let claims = json!({}); 511 | let mut validation = Validation::new(Algorithm::HS256); 512 | validation.set_required_spec_claims(&["exp"]); 513 | validation.validate_exp = false; 514 | let res = validate(deserialize_claims(&claims), &validation); 515 | assert!(res.is_err()); 516 | } 517 | 518 | #[test] 519 | #[wasm_bindgen_test] 520 | fn nbf_in_past_ok() { 521 | let claims = json!({ "nbf": get_current_timestamp() - 10000 }); 522 | let mut validation = Validation::new(Algorithm::HS256); 523 | validation.required_spec_claims = HashSet::new(); 524 | validation.validate_exp = false; 525 | validation.validate_nbf = true; 526 | let res = validate(deserialize_claims(&claims), &validation); 527 | assert!(res.is_ok()); 528 | } 529 | 530 | #[test] 531 | #[wasm_bindgen_test] 532 | fn nbf_float_in_past_ok() { 533 | let claims = json!({ "nbf": (get_current_timestamp() as f64) - 10000.1234 }); 534 | let mut validation = Validation::new(Algorithm::HS256); 535 | validation.required_spec_claims = HashSet::new(); 536 | validation.validate_exp = false; 537 | validation.validate_nbf = true; 538 | let res = validate(deserialize_claims(&claims), &validation); 539 | assert!(res.is_ok()); 540 | } 541 | 542 | #[test] 543 | #[wasm_bindgen_test] 544 | fn nbf_in_future_fails() { 545 | let claims = json!({ "nbf": get_current_timestamp() + 100000 }); 546 | let mut validation = Validation::new(Algorithm::HS256); 547 | validation.required_spec_claims = HashSet::new(); 548 | validation.validate_exp = false; 549 | validation.validate_nbf = true; 550 | let res = validate(deserialize_claims(&claims), &validation); 551 | assert!(res.is_err()); 552 | 553 | match res.unwrap_err().kind() { 554 | ErrorKind::ImmatureSignature => (), 555 | _ => unreachable!(), 556 | }; 557 | } 558 | 559 | #[test] 560 | #[wasm_bindgen_test] 561 | fn nbf_in_future_but_in_leeway_ok() { 562 | let claims = json!({ "nbf": get_current_timestamp() + 500 }); 563 | let mut validation = Validation::new(Algorithm::HS256); 564 | validation.required_spec_claims = HashSet::new(); 565 | validation.validate_exp = false; 566 | validation.validate_nbf = true; 567 | validation.leeway = 1000 * 60; 568 | let res = validate(deserialize_claims(&claims), &validation); 569 | assert!(res.is_ok()); 570 | } 571 | 572 | #[test] 573 | #[wasm_bindgen_test] 574 | fn iss_string_ok() { 575 | let claims = json!({"iss": ["Keats"]}); 576 | let mut validation = Validation::new(Algorithm::HS256); 577 | validation.required_spec_claims = HashSet::new(); 578 | validation.validate_exp = false; 579 | validation.set_issuer(&["Keats"]); 580 | let res = validate(deserialize_claims(&claims), &validation); 581 | assert!(res.is_ok()); 582 | } 583 | 584 | #[test] 585 | #[wasm_bindgen_test] 586 | fn iss_array_of_string_ok() { 587 | let claims = json!({"iss": ["UserA", "UserB"]}); 588 | let mut validation = Validation::new(Algorithm::HS256); 589 | validation.required_spec_claims = HashSet::new(); 590 | validation.validate_exp = false; 591 | validation.set_issuer(&["UserA", "UserB"]); 592 | let res = validate(deserialize_claims(&claims), &validation); 593 | assert!(res.is_ok()); 594 | } 595 | 596 | #[test] 597 | #[wasm_bindgen_test] 598 | fn iss_not_matching_fails() { 599 | let claims = json!({"iss": "Hacked"}); 600 | 601 | let mut validation = Validation::new(Algorithm::HS256); 602 | validation.required_spec_claims = HashSet::new(); 603 | validation.validate_exp = false; 604 | validation.set_issuer(&["Keats"]); 605 | let res = validate(deserialize_claims(&claims), &validation); 606 | assert!(res.is_err()); 607 | 608 | match res.unwrap_err().kind() { 609 | ErrorKind::InvalidIssuer => (), 610 | _ => unreachable!(), 611 | }; 612 | } 613 | 614 | #[test] 615 | #[wasm_bindgen_test] 616 | fn iss_missing_fails() { 617 | let claims = json!({}); 618 | 619 | let mut validation = Validation::new(Algorithm::HS256); 620 | validation.set_required_spec_claims(&["iss"]); 621 | validation.validate_exp = false; 622 | validation.set_issuer(&["Keats"]); 623 | let res = validate(deserialize_claims(&claims), &validation); 624 | 625 | match res.unwrap_err().kind() { 626 | ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "iss"), 627 | _ => unreachable!(), 628 | }; 629 | } 630 | 631 | #[test] 632 | #[wasm_bindgen_test] 633 | fn sub_ok() { 634 | let claims = json!({"sub": "Keats"}); 635 | let mut validation = Validation::new(Algorithm::HS256); 636 | validation.required_spec_claims = HashSet::new(); 637 | validation.validate_exp = false; 638 | validation.sub = Some("Keats".to_owned()); 639 | let res = validate(deserialize_claims(&claims), &validation); 640 | assert!(res.is_ok()); 641 | } 642 | 643 | #[test] 644 | #[wasm_bindgen_test] 645 | fn sub_not_matching_fails() { 646 | let claims = json!({"sub": "Hacked"}); 647 | let mut validation = Validation::new(Algorithm::HS256); 648 | validation.required_spec_claims = HashSet::new(); 649 | validation.validate_exp = false; 650 | validation.sub = Some("Keats".to_owned()); 651 | let res = validate(deserialize_claims(&claims), &validation); 652 | assert!(res.is_err()); 653 | 654 | match res.unwrap_err().kind() { 655 | ErrorKind::InvalidSubject => (), 656 | _ => unreachable!(), 657 | }; 658 | } 659 | 660 | #[test] 661 | #[wasm_bindgen_test] 662 | fn sub_missing_fails() { 663 | let claims = json!({}); 664 | let mut validation = Validation::new(Algorithm::HS256); 665 | validation.validate_exp = false; 666 | validation.set_required_spec_claims(&["sub"]); 667 | validation.sub = Some("Keats".to_owned()); 668 | let res = validate(deserialize_claims(&claims), &validation); 669 | assert!(res.is_err()); 670 | 671 | match res.unwrap_err().kind() { 672 | ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "sub"), 673 | _ => unreachable!(), 674 | }; 675 | } 676 | 677 | #[test] 678 | #[wasm_bindgen_test] 679 | fn aud_string_ok() { 680 | let claims = json!({"aud": "Everyone"}); 681 | let mut validation = Validation::new(Algorithm::HS256); 682 | validation.validate_exp = false; 683 | validation.required_spec_claims = HashSet::new(); 684 | validation.set_audience(&["Everyone"]); 685 | let res = validate(deserialize_claims(&claims), &validation); 686 | assert!(res.is_ok()); 687 | } 688 | 689 | #[test] 690 | #[wasm_bindgen_test] 691 | fn aud_array_of_string_ok() { 692 | let claims = json!({"aud": ["UserA", "UserB"]}); 693 | let mut validation = Validation::new(Algorithm::HS256); 694 | validation.validate_exp = false; 695 | validation.required_spec_claims = HashSet::new(); 696 | validation.set_audience(&["UserA", "UserB"]); 697 | let res = validate(deserialize_claims(&claims), &validation); 698 | assert!(res.is_ok()); 699 | } 700 | 701 | #[test] 702 | #[wasm_bindgen_test] 703 | fn aud_type_mismatch_fails() { 704 | let claims = json!({"aud": ["Everyone"]}); 705 | let mut validation = Validation::new(Algorithm::HS256); 706 | validation.validate_exp = false; 707 | validation.required_spec_claims = HashSet::new(); 708 | validation.set_audience(&["UserA", "UserB"]); 709 | let res = validate(deserialize_claims(&claims), &validation); 710 | assert!(res.is_err()); 711 | 712 | match res.unwrap_err().kind() { 713 | ErrorKind::InvalidAudience => (), 714 | _ => unreachable!(), 715 | }; 716 | } 717 | 718 | #[test] 719 | #[wasm_bindgen_test] 720 | fn aud_correct_type_not_matching_fails() { 721 | let claims = json!({"aud": ["Everyone"]}); 722 | let mut validation = Validation::new(Algorithm::HS256); 723 | validation.validate_exp = false; 724 | validation.required_spec_claims = HashSet::new(); 725 | validation.set_audience(&["None"]); 726 | let res = validate(deserialize_claims(&claims), &validation); 727 | assert!(res.is_err()); 728 | 729 | match res.unwrap_err().kind() { 730 | ErrorKind::InvalidAudience => (), 731 | _ => unreachable!(), 732 | }; 733 | } 734 | 735 | #[test] 736 | #[wasm_bindgen_test] 737 | fn aud_none_fails() { 738 | let claims = json!({"aud": ["Everyone"]}); 739 | let mut validation = Validation::new(Algorithm::HS256); 740 | validation.validate_exp = false; 741 | validation.required_spec_claims = HashSet::new(); 742 | validation.aud = None; 743 | let res = validate(deserialize_claims(&claims), &validation); 744 | assert!(res.is_err()); 745 | 746 | match res.unwrap_err().kind() { 747 | ErrorKind::InvalidAudience => (), 748 | _ => unreachable!(), 749 | }; 750 | } 751 | 752 | #[test] 753 | #[wasm_bindgen_test] 754 | fn aud_validation_skipped() { 755 | let claims = json!({"aud": ["Everyone"]}); 756 | let mut validation = Validation::new(Algorithm::HS256); 757 | validation.validate_exp = false; 758 | validation.validate_aud = false; 759 | validation.required_spec_claims = HashSet::new(); 760 | validation.aud = None; 761 | let res = validate(deserialize_claims(&claims), &validation); 762 | assert!(res.is_ok()); 763 | } 764 | 765 | #[test] 766 | #[wasm_bindgen_test] 767 | fn aud_missing_fails() { 768 | let claims = json!({}); 769 | let mut validation = Validation::new(Algorithm::HS256); 770 | validation.validate_exp = false; 771 | validation.set_required_spec_claims(&["aud"]); 772 | validation.set_audience(&["None"]); 773 | let res = validate(deserialize_claims(&claims), &validation); 774 | assert!(res.is_err()); 775 | 776 | match res.unwrap_err().kind() { 777 | ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "aud"), 778 | _ => unreachable!(), 779 | }; 780 | } 781 | 782 | // https://github.com/Keats/jsonwebtoken/issues/51 783 | #[test] 784 | #[wasm_bindgen_test] 785 | fn does_validation_in_right_order() { 786 | let claims = json!({ "exp": get_current_timestamp() + 10000 }); 787 | 788 | let mut validation = Validation::new(Algorithm::HS256); 789 | validation.set_required_spec_claims(&["exp", "iss"]); 790 | validation.leeway = 5; 791 | validation.set_issuer(&["iss no check"]); 792 | validation.set_audience(&["iss no check"]); 793 | 794 | let res = validate(deserialize_claims(&claims), &validation); 795 | // It errors because it needs to validate iss/sub which are missing 796 | assert!(res.is_err()); 797 | match res.unwrap_err().kind() { 798 | ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "iss"), 799 | t => panic!("{:?}", t), 800 | }; 801 | } 802 | 803 | // https://github.com/Keats/jsonwebtoken/issues/110 804 | #[test] 805 | #[wasm_bindgen_test] 806 | fn aud_use_validation_struct() { 807 | let claims = json!({"aud": "my-googleclientid1234.apps.googleusercontent.com"}); 808 | 809 | let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string(); 810 | let mut aud_hashset = std::collections::HashSet::new(); 811 | aud_hashset.insert(aud); 812 | let mut validation = Validation::new(Algorithm::HS256); 813 | validation.validate_exp = false; 814 | validation.required_spec_claims = HashSet::new(); 815 | validation.set_audience(&["my-googleclientid1234.apps.googleusercontent.com"]); 816 | 817 | let res = validate(deserialize_claims(&claims), &validation); 818 | assert!(res.is_ok()); 819 | } 820 | } 821 | -------------------------------------------------------------------------------- /tests/ecdsa/mod.rs: -------------------------------------------------------------------------------- 1 | use jsonwebtoken::{ 2 | crypto::{sign, verify}, 3 | Algorithm, DecodingKey, EncodingKey, 4 | }; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[cfg(feature = "use_pem")] 8 | use jsonwebtoken::{decode, encode, Header, Validation}; 9 | #[cfg(feature = "use_pem")] 10 | use time::OffsetDateTime; 11 | use wasm_bindgen_test::wasm_bindgen_test; 12 | 13 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 14 | pub struct Claims { 15 | sub: String, 16 | company: String, 17 | exp: i64, 18 | } 19 | 20 | #[test] 21 | #[wasm_bindgen_test] 22 | fn round_trip_sign_verification_pk8() { 23 | let privkey = include_bytes!("private_ecdsa_key.pk8"); 24 | let pubkey = include_bytes!("public_ecdsa_key.pk8"); 25 | 26 | let encrypted = 27 | sign(b"hello world", &EncodingKey::from_ec_der(privkey), Algorithm::ES256).unwrap(); 28 | let is_valid = 29 | verify(&encrypted, b"hello world", &DecodingKey::from_ec_der(pubkey), Algorithm::ES256) 30 | .unwrap(); 31 | assert!(is_valid); 32 | } 33 | 34 | #[cfg(feature = "use_pem")] 35 | #[test] 36 | #[wasm_bindgen_test] 37 | fn round_trip_sign_verification_pem() { 38 | let privkey_pem = include_bytes!("private_ecdsa_key.pem"); 39 | let pubkey_pem = include_bytes!("public_ecdsa_key.pem"); 40 | let encrypted = 41 | sign(b"hello world", &EncodingKey::from_ec_pem(privkey_pem).unwrap(), Algorithm::ES256) 42 | .unwrap(); 43 | let is_valid = verify( 44 | &encrypted, 45 | b"hello world", 46 | &DecodingKey::from_ec_pem(pubkey_pem).unwrap(), 47 | Algorithm::ES256, 48 | ) 49 | .unwrap(); 50 | assert!(is_valid); 51 | } 52 | 53 | #[cfg(feature = "use_pem")] 54 | #[test] 55 | #[wasm_bindgen_test] 56 | fn round_trip_claim() { 57 | let privkey_pem = include_bytes!("private_ecdsa_key.pem"); 58 | let pubkey_pem = include_bytes!("public_ecdsa_key.pem"); 59 | let my_claims = Claims { 60 | sub: "b@b.com".to_string(), 61 | company: "ACME".to_string(), 62 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 63 | }; 64 | let token = encode( 65 | &Header::new(Algorithm::ES256), 66 | &my_claims, 67 | &EncodingKey::from_ec_pem(privkey_pem).unwrap(), 68 | ) 69 | .unwrap(); 70 | let token_data = decode::( 71 | &token, 72 | &DecodingKey::from_ec_pem(pubkey_pem).unwrap(), 73 | &Validation::new(Algorithm::ES256), 74 | ) 75 | .unwrap(); 76 | assert_eq!(my_claims, token_data.claims); 77 | } 78 | 79 | #[cfg(feature = "use_pem")] 80 | #[test] 81 | #[wasm_bindgen_test] 82 | fn ec_x_y() { 83 | let privkey = include_str!("private_ecdsa_key.pem"); 84 | let my_claims = Claims { 85 | sub: "b@b.com".to_string(), 86 | company: "ACME".to_string(), 87 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 88 | }; 89 | let x = "w7JAoU_gJbZJvV-zCOvU9yFJq0FNC_edCMRM78P8eQQ"; 90 | let y = "wQg1EytcsEmGrM70Gb53oluoDbVhCZ3Uq3hHMslHVb4"; 91 | 92 | let encrypted = encode( 93 | &Header::new(Algorithm::ES256), 94 | &my_claims, 95 | &EncodingKey::from_ec_pem(privkey.as_ref()).unwrap(), 96 | ) 97 | .unwrap(); 98 | let res = decode::( 99 | &encrypted, 100 | &DecodingKey::from_ec_components(x, y).unwrap(), 101 | &Validation::new(Algorithm::ES256), 102 | ); 103 | assert!(res.is_ok()); 104 | } 105 | 106 | #[cfg(feature = "use_pem")] 107 | #[test] 108 | #[wasm_bindgen_test] 109 | fn ed_jwk() { 110 | use jsonwebtoken::jwk::Jwk; 111 | use serde_json::json; 112 | 113 | let privkey = include_str!("private_ecdsa_key.pem"); 114 | let my_claims = Claims { 115 | sub: "b@b.com".to_string(), 116 | company: "ACME".to_string(), 117 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 118 | }; 119 | let jwk: Jwk = serde_json::from_value(json!({ 120 | "kty": "EC", 121 | "crv": "P-256", 122 | "x": "w7JAoU_gJbZJvV-zCOvU9yFJq0FNC_edCMRM78P8eQQ", 123 | "y": "wQg1EytcsEmGrM70Gb53oluoDbVhCZ3Uq3hHMslHVb4", 124 | "kid": "ec01", 125 | "alg": "ES256", 126 | "use": "sig" 127 | })) 128 | .unwrap(); 129 | 130 | let encrypted = encode( 131 | &Header::new(Algorithm::ES256), 132 | &my_claims, 133 | &EncodingKey::from_ec_pem(privkey.as_ref()).unwrap(), 134 | ) 135 | .unwrap(); 136 | let res = decode::( 137 | &encrypted, 138 | &DecodingKey::from_jwk(&jwk).unwrap(), 139 | &Validation::new(Algorithm::ES256), 140 | ); 141 | assert!(res.is_ok()); 142 | } 143 | 144 | // https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken 145 | #[cfg(feature = "use_pem")] 146 | #[test] 147 | #[wasm_bindgen_test] 148 | fn roundtrip_with_jwtio_example() { 149 | // We currently do not support SEC1 so we use the converted PKCS8 formatted 150 | let privkey_pem = include_bytes!("private_jwtio_pkcs8.pem"); 151 | let pubkey_pem = include_bytes!("public_jwtio.pem"); 152 | let my_claims = Claims { 153 | sub: "b@b.com".to_string(), 154 | company: "ACME".to_string(), 155 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 156 | }; 157 | let token = encode( 158 | &Header::new(Algorithm::ES384), 159 | &my_claims, 160 | &EncodingKey::from_ec_pem(privkey_pem).unwrap(), 161 | ) 162 | .unwrap(); 163 | let token_data = decode::( 164 | &token, 165 | &DecodingKey::from_ec_pem(pubkey_pem).unwrap(), 166 | &Validation::new(Algorithm::ES384), 167 | ) 168 | .unwrap(); 169 | assert_eq!(my_claims, token_data.claims); 170 | } 171 | -------------------------------------------------------------------------------- /tests/ecdsa/private_ecdsa_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWTFfCGljY6aw3Hrt 3 | kHmPRiazukxPLb6ilpRAewjW8nihRANCAATDskChT+Altkm9X7MI69T3IUmrQU0L 4 | 950IxEzvw/x5BMEINRMrXLBJhqzO9Bm+d6JbqA21YQmd1Kt4RzLJR1W+ 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/ecdsa/private_ecdsa_key.pk8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keats/jsonwebtoken/5cd1887848f7d30f11adb4ebcec6a01baffd5252/tests/ecdsa/private_ecdsa_key.pk8 -------------------------------------------------------------------------------- /tests/ecdsa/private_jwtio.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/private_jwtio_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/public_ecdsa_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEw7JAoU/gJbZJvV+zCOvU9yFJq0FN 3 | C/edCMRM78P8eQTBCDUTK1ywSYaszvQZvneiW6gNtWEJndSreEcyyUdVvg== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /tests/ecdsa/public_ecdsa_key.pk8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keats/jsonwebtoken/5cd1887848f7d30f11adb4ebcec6a01baffd5252/tests/ecdsa/public_ecdsa_key.pk8 -------------------------------------------------------------------------------- /tests/ecdsa/public_jwtio.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+ 3 | Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii 4 | 1D3jaW6pmGVJFhodzC31cy5sfOYotrzF 5 | -----END PUBLIC KEY----- 6 | -------------------------------------------------------------------------------- /tests/eddsa/mod.rs: -------------------------------------------------------------------------------- 1 | use jsonwebtoken::{ 2 | crypto::{sign, verify}, 3 | Algorithm, DecodingKey, EncodingKey, 4 | }; 5 | use serde::{Deserialize, Serialize}; 6 | use wasm_bindgen_test::wasm_bindgen_test; 7 | 8 | #[cfg(feature = "use_pem")] 9 | use jsonwebtoken::{decode, encode, Header, Validation}; 10 | #[cfg(feature = "use_pem")] 11 | use time::OffsetDateTime; 12 | 13 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 14 | pub struct Claims { 15 | sub: String, 16 | company: String, 17 | exp: i64, 18 | } 19 | 20 | #[test] 21 | #[wasm_bindgen_test] 22 | fn round_trip_sign_verification_pk8() { 23 | let privkey = include_bytes!("private_ed25519_key.pk8"); 24 | let pubkey = include_bytes!("public_ed25519_key.pk8"); 25 | 26 | let encrypted = 27 | sign(b"hello world", &EncodingKey::from_ed_der(privkey), Algorithm::EdDSA).unwrap(); 28 | let is_valid = 29 | verify(&encrypted, b"hello world", &DecodingKey::from_ed_der(pubkey), Algorithm::EdDSA) 30 | .unwrap(); 31 | assert!(is_valid); 32 | } 33 | 34 | #[cfg(feature = "use_pem")] 35 | #[test] 36 | #[wasm_bindgen_test] 37 | fn round_trip_sign_verification_pem() { 38 | let privkey_pem = include_bytes!("private_ed25519_key.pem"); 39 | let pubkey_pem = include_bytes!("public_ed25519_key.pem"); 40 | let encrypted = 41 | sign(b"hello world", &EncodingKey::from_ed_pem(privkey_pem).unwrap(), Algorithm::EdDSA) 42 | .unwrap(); 43 | let is_valid = verify( 44 | &encrypted, 45 | b"hello world", 46 | &DecodingKey::from_ed_pem(pubkey_pem).unwrap(), 47 | Algorithm::EdDSA, 48 | ) 49 | .unwrap(); 50 | assert!(is_valid); 51 | } 52 | 53 | #[cfg(feature = "use_pem")] 54 | #[test] 55 | #[wasm_bindgen_test] 56 | fn round_trip_claim() { 57 | let privkey_pem = include_bytes!("private_ed25519_key.pem"); 58 | let pubkey_pem = include_bytes!("public_ed25519_key.pem"); 59 | let my_claims = Claims { 60 | sub: "b@b.com".to_string(), 61 | company: "ACME".to_string(), 62 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 63 | }; 64 | let token = encode( 65 | &Header::new(Algorithm::EdDSA), 66 | &my_claims, 67 | &EncodingKey::from_ed_pem(privkey_pem).unwrap(), 68 | ) 69 | .unwrap(); 70 | let token_data = decode::( 71 | &token, 72 | &DecodingKey::from_ed_pem(pubkey_pem).unwrap(), 73 | &Validation::new(Algorithm::EdDSA), 74 | ) 75 | .unwrap(); 76 | assert_eq!(my_claims, token_data.claims); 77 | } 78 | 79 | #[cfg(feature = "use_pem")] 80 | #[test] 81 | #[wasm_bindgen_test] 82 | fn ed_x() { 83 | let privkey = include_str!("private_ed25519_key.pem"); 84 | let my_claims = Claims { 85 | sub: "b@b.com".to_string(), 86 | company: "ACME".to_string(), 87 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 88 | }; 89 | let x = "2-Jj2UvNCvQiUPNYRgSi0cJSPiJI6Rs6D0UTeEpQVj8"; 90 | 91 | let encrypted = encode( 92 | &Header::new(Algorithm::EdDSA), 93 | &my_claims, 94 | &EncodingKey::from_ed_pem(privkey.as_ref()).unwrap(), 95 | ) 96 | .unwrap(); 97 | let res = decode::( 98 | &encrypted, 99 | &DecodingKey::from_ed_components(x).unwrap(), 100 | &Validation::new(Algorithm::EdDSA), 101 | ); 102 | assert!(res.is_ok()); 103 | } 104 | 105 | #[cfg(feature = "use_pem")] 106 | #[test] 107 | #[wasm_bindgen_test] 108 | fn ed_jwk() { 109 | use jsonwebtoken::jwk::Jwk; 110 | use serde_json::json; 111 | 112 | let privkey = include_str!("private_ed25519_key.pem"); 113 | let my_claims = Claims { 114 | sub: "b@b.com".to_string(), 115 | company: "ACME".to_string(), 116 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 117 | }; 118 | let jwk: Jwk = serde_json::from_value(json!({ 119 | "kty": "OKP", 120 | "use": "sig", 121 | "crv": "Ed25519", 122 | "x": "2-Jj2UvNCvQiUPNYRgSi0cJSPiJI6Rs6D0UTeEpQVj8", 123 | "kid": "ed01", 124 | "alg": "EdDSA" 125 | })) 126 | .unwrap(); 127 | 128 | let encrypted = encode( 129 | &Header::new(Algorithm::EdDSA), 130 | &my_claims, 131 | &EncodingKey::from_ed_pem(privkey.as_ref()).unwrap(), 132 | ) 133 | .unwrap(); 134 | let res = decode::( 135 | &encrypted, 136 | &DecodingKey::from_jwk(&jwk).unwrap(), 137 | &Validation::new(Algorithm::EdDSA), 138 | ); 139 | assert!(res.is_ok()); 140 | } 141 | -------------------------------------------------------------------------------- /tests/eddsa/private_ed25519_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEIGrD/e7uKYqSY4twDEsRfMMuLSrODf14dpTiTK6K1YI0 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /tests/eddsa/private_ed25519_key.pk8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keats/jsonwebtoken/5cd1887848f7d30f11adb4ebcec6a01baffd5252/tests/eddsa/private_ed25519_key.pk8 -------------------------------------------------------------------------------- /tests/eddsa/public_ed25519_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MCowBQYDK2VwAyEA2+Jj2UvNCvQiUPNYRgSi0cJSPiJI6Rs6D0UTeEpQVj8= 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /tests/eddsa/public_ed25519_key.pk8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keats/jsonwebtoken/5cd1887848f7d30f11adb4ebcec6a01baffd5252/tests/eddsa/public_ed25519_key.pk8 -------------------------------------------------------------------------------- /tests/header/cert_chain.json: -------------------------------------------------------------------------------- 1 | ["MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=","MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==","MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd"] 2 | -------------------------------------------------------------------------------- /tests/header/cert_chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVM 3 | xITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR2 4 | 8gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExM 5 | TYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UE 6 | CBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWR 7 | keS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYW 8 | RkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlc 9 | nRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJ 10 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTt 11 | wY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqV 12 | Tr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aL 13 | GbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo 14 | 7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgW 15 | JCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAw 16 | EAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVH 17 | SMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEA 18 | MDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWR 19 | keS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2 20 | RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVH 21 | SAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j 22 | b20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggE 23 | BANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPI 24 | UyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL 25 | 5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9 26 | p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsx 27 | uxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZ 28 | EjYx8WnM25sgVjOuH0aBsXBTWVU+4= 29 | -----END CERTIFICATE----- 30 | -----BEGIN CERTIFICATE----- 31 | MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Z 32 | hbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIE 33 | luYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb 34 | 24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8x 35 | IDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDY 36 | yMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZS 37 | BHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgM 38 | iBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN 39 | ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XC 40 | APVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux 41 | 6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLO 42 | tXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWo 43 | riMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZ 44 | Eewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ 45 | 4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBu 46 | zEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQK 47 | Ew5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2x 48 | pY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudm 49 | FsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CA 50 | QEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGG 51 | F2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA 52 | 6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybD 53 | BLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZ 54 | mljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjAN 55 | BgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+ 56 | Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgM 57 | QLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j 58 | 09VZw== 59 | -----END CERTIFICATE----- 60 | -----BEGIN CERTIFICATE----- 61 | MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ 62 | 0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNT 63 | AzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0a 64 | G9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkq 65 | hkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE 66 | 5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTm 67 | V0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZ 68 | XJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQD 69 | ExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9 70 | AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5a 71 | vIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zf 72 | N1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwb 73 | P7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQU 74 | AA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQ 75 | C1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMM 76 | j4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd 77 | -----END CERTIFICATE----- 78 | -------------------------------------------------------------------------------- /tests/header/mod.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine::general_purpose::STANDARD, Engine}; 2 | use jsonwebtoken::Header; 3 | use wasm_bindgen_test::wasm_bindgen_test; 4 | 5 | static CERT_CHAIN: [&str; 3] = include!("cert_chain.json"); 6 | 7 | #[test] 8 | #[wasm_bindgen_test] 9 | fn x5c_der_empty_chain() { 10 | let header = Header { x5c: None, ..Default::default() }; 11 | assert_eq!(header.x5c_der().unwrap(), None); 12 | 13 | let header = Header { x5c: Some(Vec::new()), ..Default::default() }; 14 | assert_eq!(header.x5c_der().unwrap(), Some(Vec::new())); 15 | } 16 | 17 | #[test] 18 | #[wasm_bindgen_test] 19 | fn x5c_der_valid_chain() { 20 | let der_chain: Vec> = 21 | CERT_CHAIN.iter().map(|x| STANDARD.decode(x)).collect::>().unwrap(); 22 | 23 | let x5c = Some(CERT_CHAIN.iter().map(ToString::to_string).collect()); 24 | let header = Header { x5c, ..Default::default() }; 25 | 26 | assert_eq!(header.x5c_der().unwrap(), Some(der_chain)); 27 | } 28 | 29 | #[test] 30 | #[wasm_bindgen_test] 31 | fn x5c_der_invalid_chain() { 32 | let mut x5c: Vec<_> = CERT_CHAIN.iter().map(ToString::to_string).collect(); 33 | x5c.push("invalid base64 data".to_string()); 34 | 35 | let x5c = Some(x5c); 36 | let header = Header { x5c, ..Default::default() }; 37 | 38 | assert!(header.x5c_der().is_err()); 39 | } 40 | -------------------------------------------------------------------------------- /tests/hmac.rs: -------------------------------------------------------------------------------- 1 | use jsonwebtoken::errors::ErrorKind; 2 | use jsonwebtoken::jwk::Jwk; 3 | use jsonwebtoken::{ 4 | crypto::{sign, verify}, 5 | decode, decode_header, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation, 6 | }; 7 | use serde::{Deserialize, Serialize}; 8 | use std::collections::HashMap; 9 | use time::OffsetDateTime; 10 | use wasm_bindgen_test::wasm_bindgen_test; 11 | 12 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 13 | pub struct Claims { 14 | sub: String, 15 | company: String, 16 | exp: i64, 17 | } 18 | 19 | #[test] 20 | #[wasm_bindgen_test] 21 | fn sign_hs256() { 22 | let result = 23 | sign(b"hello world", &EncodingKey::from_secret(b"secret"), Algorithm::HS256).unwrap(); 24 | let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; 25 | assert_eq!(result, expected); 26 | } 27 | 28 | #[test] 29 | #[wasm_bindgen_test] 30 | fn verify_hs256() { 31 | let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; 32 | let valid = verify(sig, b"hello world", &DecodingKey::from_secret(b"secret"), Algorithm::HS256) 33 | .unwrap(); 34 | assert!(valid); 35 | } 36 | 37 | #[test] 38 | #[wasm_bindgen_test] 39 | fn encode_with_custom_header() { 40 | let my_claims = Claims { 41 | sub: "b@b.com".to_string(), 42 | company: "ACME".to_string(), 43 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 44 | }; 45 | let header = Header { kid: Some("kid".to_string()), ..Default::default() }; 46 | let token = encode(&header, &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); 47 | let token_data = decode::( 48 | &token, 49 | &DecodingKey::from_secret(b"secret"), 50 | &Validation::new(Algorithm::HS256), 51 | ) 52 | .unwrap(); 53 | assert_eq!(my_claims, token_data.claims); 54 | assert_eq!("kid", token_data.header.kid.unwrap()); 55 | assert!(token_data.header.extras.is_empty()); 56 | } 57 | 58 | #[test] 59 | #[wasm_bindgen_test] 60 | fn encode_with_extra_custom_header() { 61 | let my_claims = Claims { 62 | sub: "b@b.com".to_string(), 63 | company: "ACME".to_string(), 64 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 65 | }; 66 | let mut extras = HashMap::with_capacity(1); 67 | extras.insert("custom".to_string(), "header".to_string()); 68 | let header = Header { kid: Some("kid".to_string()), extras, ..Default::default() }; 69 | let token = encode(&header, &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); 70 | let token_data = decode::( 71 | &token, 72 | &DecodingKey::from_secret(b"secret"), 73 | &Validation::new(Algorithm::HS256), 74 | ) 75 | .unwrap(); 76 | assert_eq!(my_claims, token_data.claims); 77 | assert_eq!("kid", token_data.header.kid.unwrap()); 78 | assert_eq!("header", token_data.header.extras.get("custom").unwrap().as_str()); 79 | } 80 | 81 | #[test] 82 | #[wasm_bindgen_test] 83 | fn encode_with_multiple_extra_custom_headers() { 84 | let my_claims = Claims { 85 | sub: "b@b.com".to_string(), 86 | company: "ACME".to_string(), 87 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 88 | }; 89 | let mut extras = HashMap::with_capacity(2); 90 | extras.insert("custom1".to_string(), "header1".to_string()); 91 | extras.insert("custom2".to_string(), "header2".to_string()); 92 | let header = Header { kid: Some("kid".to_string()), extras, ..Default::default() }; 93 | let token = encode(&header, &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); 94 | let token_data = decode::( 95 | &token, 96 | &DecodingKey::from_secret(b"secret"), 97 | &Validation::new(Algorithm::HS256), 98 | ) 99 | .unwrap(); 100 | assert_eq!(my_claims, token_data.claims); 101 | assert_eq!("kid", token_data.header.kid.unwrap()); 102 | let extras = token_data.header.extras; 103 | assert_eq!("header1", extras.get("custom1").unwrap().as_str()); 104 | assert_eq!("header2", extras.get("custom2").unwrap().as_str()); 105 | } 106 | 107 | #[test] 108 | #[wasm_bindgen_test] 109 | fn round_trip_claim() { 110 | let my_claims = Claims { 111 | sub: "b@b.com".to_string(), 112 | company: "ACME".to_string(), 113 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 114 | }; 115 | let token = 116 | encode(&Header::default(), &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); 117 | let token_data = decode::( 118 | &token, 119 | &DecodingKey::from_secret(b"secret"), 120 | &Validation::new(Algorithm::HS256), 121 | ) 122 | .unwrap(); 123 | assert_eq!(my_claims, token_data.claims); 124 | assert!(token_data.header.kid.is_none()); 125 | } 126 | 127 | #[test] 128 | #[wasm_bindgen_test] 129 | fn decode_token() { 130 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 131 | let claims = decode::( 132 | token, 133 | &DecodingKey::from_secret(b"secret"), 134 | &Validation::new(Algorithm::HS256), 135 | ); 136 | println!("{:?}", claims); 137 | claims.unwrap(); 138 | } 139 | 140 | #[test] 141 | #[wasm_bindgen_test] 142 | fn decode_token_custom_headers() { 143 | let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsImN1c3RvbTEiOiJoZWFkZXIxIiwiY3VzdG9tMiI6ImhlYWRlcjIifQ.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.FtOHsoKcNH3SriK3tnR-uWJg4UV4FkOzvq_JCfLngfU"; 144 | let claims = decode::( 145 | token, 146 | &DecodingKey::from_secret(b"secret"), 147 | &Validation::new(Algorithm::HS256), 148 | ) 149 | .unwrap(); 150 | let my_claims = 151 | Claims { sub: "b@b.com".to_string(), company: "ACME".to_string(), exp: 2532524891 }; 152 | assert_eq!(my_claims, claims.claims); 153 | assert_eq!("kid", claims.header.kid.unwrap()); 154 | let extras = claims.header.extras; 155 | assert_eq!("header1", extras.get("custom1").unwrap().as_str()); 156 | assert_eq!("header2", extras.get("custom2").unwrap().as_str()); 157 | } 158 | 159 | #[test] 160 | #[wasm_bindgen_test] 161 | #[should_panic(expected = "InvalidToken")] 162 | fn decode_token_missing_parts() { 163 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; 164 | let claims = decode::( 165 | token, 166 | &DecodingKey::from_secret(b"secret"), 167 | &Validation::new(Algorithm::HS256), 168 | ); 169 | claims.unwrap(); 170 | } 171 | 172 | #[test] 173 | #[wasm_bindgen_test] 174 | #[should_panic(expected = "InvalidSignature")] 175 | fn decode_token_invalid_signature() { 176 | let token = 177 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; 178 | let claims = decode::( 179 | token, 180 | &DecodingKey::from_secret(b"secret"), 181 | &Validation::new(Algorithm::HS256), 182 | ); 183 | claims.unwrap(); 184 | } 185 | 186 | #[test] 187 | #[wasm_bindgen_test] 188 | #[should_panic(expected = "InvalidAlgorithm")] 189 | fn decode_token_wrong_algorithm() { 190 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; 191 | let claims = decode::( 192 | token, 193 | &DecodingKey::from_secret(b"secret"), 194 | &Validation::new(Algorithm::RS512), 195 | ); 196 | claims.unwrap(); 197 | } 198 | 199 | #[test] 200 | #[wasm_bindgen_test] 201 | #[should_panic(expected = "InvalidAlgorithm")] 202 | fn encode_wrong_alg_family() { 203 | let my_claims = Claims { 204 | sub: "b@b.com".to_string(), 205 | company: "ACME".to_string(), 206 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 207 | }; 208 | let claims = encode(&Header::default(), &my_claims, &EncodingKey::from_rsa_der(b"secret")); 209 | claims.unwrap(); 210 | } 211 | 212 | #[test] 213 | #[wasm_bindgen_test] 214 | fn decode_token_with_bytes_secret() { 215 | let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; 216 | let claims = decode::( 217 | token, 218 | &DecodingKey::from_secret(b"\x01\x02\x03"), 219 | &Validation::new(Algorithm::HS256), 220 | ); 221 | assert!(claims.is_ok()); 222 | } 223 | 224 | #[test] 225 | #[wasm_bindgen_test] 226 | fn decode_header_only() { 227 | let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; 228 | let header = decode_header(token).unwrap(); 229 | assert_eq!(header.alg, Algorithm::HS256); 230 | assert_eq!(header.typ, Some("JWT".to_string())); 231 | } 232 | 233 | #[test] 234 | #[wasm_bindgen_test] 235 | fn dangerous_insecure_decode_valid_token() { 236 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; 237 | let mut validation = Validation::new(Algorithm::HS256); 238 | validation.insecure_disable_signature_validation(); 239 | let claims = decode::(token, &DecodingKey::from_secret(&[]), &validation); 240 | claims.unwrap(); 241 | } 242 | 243 | #[test] 244 | #[wasm_bindgen_test] 245 | fn dangerous_insecure_decode_token_invalid_signature() { 246 | let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.wrong"; 247 | let mut validation = Validation::new(Algorithm::HS256); 248 | validation.insecure_disable_signature_validation(); 249 | let claims = decode::(token, &DecodingKey::from_secret(&[]), &validation); 250 | claims.unwrap(); 251 | } 252 | 253 | #[test] 254 | #[wasm_bindgen_test] 255 | fn dangerous_insecure_decode_token_wrong_algorithm() { 256 | let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.fLxey-hxAKX5rNHHIx1_Ch0KmrbiuoakDVbsJjLWrx8fbjKjrPuWMYEJzTU3SBnYgnZokC-wqSdqckXUOunC-g"; 257 | let mut validation = Validation::new(Algorithm::HS256); 258 | validation.insecure_disable_signature_validation(); 259 | let claims = decode::(token, &DecodingKey::from_secret(&[]), &validation); 260 | claims.unwrap(); 261 | } 262 | 263 | #[test] 264 | #[wasm_bindgen_test] 265 | fn dangerous_insecure_decode_token_with_validation_wrong_algorithm() { 266 | let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjk1MzI1MjQ4OX0.ONtEUTtP1QmyksYH9ijtPCaXoHjZVHcHKZGX1DuJyPiSyKlT93Y-oKgrp_OSkHSu4huxCcVObLzwsdwF-xwiAQ"; 267 | let mut validation = Validation::new(Algorithm::HS256); 268 | validation.insecure_disable_signature_validation(); 269 | let claims = decode::(token, &DecodingKey::from_secret(&[]), &validation); 270 | let err = claims.unwrap_err(); 271 | assert_eq!(err.kind(), &ErrorKind::ExpiredSignature); 272 | } 273 | 274 | #[test] 275 | #[wasm_bindgen_test] 276 | fn verify_hs256_rfc7517_appendix_a1() { 277 | #[derive(Debug, PartialEq, Eq, Clone, Deserialize)] 278 | struct C { 279 | iss: String, 280 | } 281 | let token = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; 282 | let jwk = r#"{"kty":"oct", 283 | "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" 284 | }"#; 285 | let jwk: Jwk = serde_json::from_str(jwk).unwrap(); 286 | let key = DecodingKey::from_jwk(&jwk).unwrap(); 287 | let mut validation = Validation::new(Algorithm::HS256); 288 | // The RFC example signed jwt has expired 289 | validation.validate_exp = false; 290 | let c = decode::(token, &key, &validation).unwrap(); 291 | assert_eq!(c.claims.iss, "joe"); 292 | } 293 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | mod ecdsa; 2 | mod eddsa; 3 | mod header; 4 | mod rsa; 5 | -------------------------------------------------------------------------------- /tests/rsa/README.md: -------------------------------------------------------------------------------- 1 | # RSA Tests 2 | 3 | ## How to generate keys and certificates 4 | 5 | ### RSA PKCS1 6 | 7 | ``` 8 | openssl genrsa -out private_rsa_pkcs1.pem 9 | openssl rsa -in private_rsa_pkcs1.pem -RSAPublicKey_out -out public_rsa_pkcs1.pem 10 | openssl req -new -key private_rsa_pkcs1.pem -out certificate_rsa_pkcs1.csr 11 | openssl x509 -req -sha256 -days 358000 -in certificate_rsa_pkcs1.csr -signkey private_rsa_pkcs1.pem -out certificate_rsa_pkcs1.crt 12 | ``` 13 | 14 | ### RSA PKCS8 15 | 16 | ``` 17 | openssl genpkey -algorithm RSA -out private_rsa_pkcs8.pem -pkeyopt rsa_keygen_bits:2048 18 | openssl rsa -pubout -in private_rsa_pkcs8.pem -out public_rsa_pkcs.pem 19 | openssl req -new -key private_rsa_pkcs8.key -out certificate_rsa_pkcs8.csr 20 | openssl x509 -req -sha256 -days 358000 -in certificate_rsa_pkcs8.csr -signkey private_rsa_pkcs8.key -out certificate_rsa_pkcs8.crt 21 | ``` 22 | 23 | ### Convert to DER format 24 | 25 | ``` 26 | openssl rsa -inform PEM -in private.pem -outform DER -out private.der 27 | ``` 28 | -------------------------------------------------------------------------------- /tests/rsa/certificate_jwtio.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDEzCCAfsCFC36imR60P89zge2Gw14moyfJE+hMA0GCSqGSIb3DQEBCwUAMEUx 3 | CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl 4 | cm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjIxMjAxMDgzNzM2WhgPMzAwMzAyMDIw 5 | ODM3MzZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD 6 | VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA 7 | A4IBDwAwggEKAoIBAQCfPKKzVmN80HRsGAoUxK++RO3CW8GxomrtLnAD6TN5U5Wl 8 | VbCRZ1WFrizfxcz+lr/Kvjtq/v7PdVOa8NHIAdxpP3bCFEQWku/1yPmVN4lKJvKv 9 | 8yub9i2MJlVaBo5giHCtfAouo+v/XWKdawCR8jK28dZPFlgRxcuABcW5S5pLe4X2 10 | ASI1DDMZNTW/QWqSpMGvgHydbccI3jtdS7S3xjR76V/izg7FBrBYPv0n3/l3dHLS 11 | 9tXcCbUW0YmIm87BGwh9UKEOlhK1NwdMIyq29ZtXovXUFaSnMZdJbge/jepr4ZJg 12 | 4PZBTrwxvn2hKTY4H4G04ukmh+ZsYQaC+bDIIj0zAgMBAAEwDQYJKoZIhvcNAQEL 13 | BQADggEBACPyDJXCWluYWIZZGeCandW9Sp51kPVPEAcDWJjz6wlSsoR38ZyQk4so 14 | PcOrBc8tNxG7BYbzaSC31df4qvOeUM6j8A+WemtCUbB99TPBa26VyUNhkQGlqF0G 15 | 3dd6h0mCIu0aftD2aci4mDBll5D4Sl99eLq1w83yfYh2KTb8lgwsmckdU1XnWHjZ 16 | Vg/N8FaqhIN1mqFY5LozPJUyX9G2vIZFA2D8jxc9S9lfosXG7j1VRJ0UIm60A7Kb 17 | 6y25m81IF1QVhDub7AVg7svyoV6tFBefuTdZzJG/gOx75eMdkI3BEBSEaw0I87Bx 18 | Z5Qp99Qv3cs6tjUr8K0kwCbqeme5UxY= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /tests/rsa/certificate_jwtio.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx 3 | ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN 4 | AQEBBQADggEPADCCAQoCggEBAJ88orNWY3zQdGwYChTEr75E7cJbwbGiau0ucAPp 5 | M3lTlaVVsJFnVYWuLN/FzP6Wv8q+O2r+/s91U5rw0cgB3Gk/dsIURBaS7/XI+ZU3 6 | iUom8q/zK5v2LYwmVVoGjmCIcK18Ci6j6/9dYp1rAJHyMrbx1k8WWBHFy4AFxblL 7 | mkt7hfYBIjUMMxk1Nb9BapKkwa+AfJ1txwjeO11LtLfGNHvpX+LODsUGsFg+/Sff 8 | +Xd0ctL21dwJtRbRiYibzsEbCH1QoQ6WErU3B0wjKrb1m1ei9dQVpKcxl0luB7+N 9 | 6mvhkmDg9kFOvDG+faEpNjgfgbTi6SaH5mxhBoL5sMgiPTMCAwEAAaAAMA0GCSqG 10 | SIb3DQEBCwUAA4IBAQCBHiYk5HH/5G/Nw49242mb4YAS7B3gBiNvop0/Jw62u/Ys 11 | QFgaiKqPXg5un/dCvU3eoaBDKaQUhslzvya3ziKceEClJfk2w6kXSh8OU9l/39Cj 12 | NEtNcpjlj427QWI7dQA83TrTNMokk5gUkgqnY6JDDzGfctlxN9J4iQC1E5hoTpos 13 | rEjzBsVHg1h4iX7QoKO36O7S1jE/hCeFnWUPC9svqx22fyterA1Jl5W3LO7kQ8pf 14 | SYZhjZqjfM7Bm+HcVotYNFcWkoqMQ60dOCA9QB38oPXHFlweD7oWRQX1P3y8W9pP 15 | kXAuYCmWDze2K1ezWImKi9Atga+ByBf/JNBVVn/G 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /tests/rsa/certificate_rsa_key_pkcs1.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDEzCCAfsCFGRy3pRvxgdoKPiWJfvmDcJhcaYOMA0GCSqGSIb3DQEBCwUAMEUx 3 | CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl 4 | cm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjIxMjAxMDgzOTA3WhgPMzAwMzAyMDIw 5 | ODM5MDdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD 6 | VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA 7 | A4IBDwAwggEKAoIBAQDJETqse41HRBsc7cfcq3ak4oZWFCoZlcic525A3FfO4qW9 8 | BMtRO/iXiyCCHn8JhiL9y8j5JdVP2Q9ZIpfElcFd3/guS9w+5RqQGgCR+H56IVUy 9 | HZWtTJbKPcwWXQdNUX0rBFcsBzCRESJLeelOEdHIjG7LRkx5l/FUvlqsyHDVJEQs 10 | HwegZ8b8C0fz0EgT2MMEdn10t6Ur1rXzjMB/wvCg8vG8lvciXmedyo9xJ8oMOh0w 11 | UEgxziVDMMovmC+aJctcHUAYubwoGN8TyzcvnGqL7JSh36Pwy28iPzXZ2RLhAyJF 12 | U39vLaHdljwthUaupldlNyCfa6Ofy4qNctlUPlN1AgMBAAEwDQYJKoZIhvcNAQEL 13 | BQADggEBAE4/4DeDd+xNH5zdyBAve4w7x5YQ1NiSKtiV5KRUG1+mndr5MubTI1SC 14 | rezrtE9D4zEqz0u2Glg9wZK9YvbshNSo0pXUEpgnJ30wKYbB9y7JlU8/G/+17da/ 15 | KIX5pytMUw5JIcj0GGrkPoVDdyU6AYd9j+dOiMyoExcYyvOstgslEDn0MXtxi58j 16 | IykQuuwe1LDWgm58mns1yCPuVr23/r2bj1iTodS2RFeSDk7oK3Kvy+b6nsxJjdyg 17 | nQ5Jwb4Seg3+7Sa/pybG5/vNX5OYHA6laEV+dMyiEBJ+XSX55KQYc4FHu2Xd0O0A 18 | OBZ2fovx2HMaiFs8tJroFU6IAYLiQCg= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /tests/rsa/certificate_rsa_key_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/certificate_rsa_key_pkcs8.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDEzCCAfsCFFkk9C6Hjy5OekqYnYg4D54caVHwMA0GCSqGSIb3DQEBCwUAMEUx 3 | CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl 4 | cm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjIxMjAxMDgzOTAxWhgPMzAwMzAyMDIw 5 | ODM5MDFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD 6 | VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA 7 | A4IBDwAwggEKAoIBAQDJETqse41HRBsc7cfcq3ak4oZWFCoZlcic525A3FfO4qW9 8 | BMtRO/iXiyCCHn8JhiL9y8j5JdVP2Q9ZIpfElcFd3/guS9w+5RqQGgCR+H56IVUy 9 | HZWtTJbKPcwWXQdNUX0rBFcsBzCRESJLeelOEdHIjG7LRkx5l/FUvlqsyHDVJEQs 10 | HwegZ8b8C0fz0EgT2MMEdn10t6Ur1rXzjMB/wvCg8vG8lvciXmedyo9xJ8oMOh0w 11 | UEgxziVDMMovmC+aJctcHUAYubwoGN8TyzcvnGqL7JSh36Pwy28iPzXZ2RLhAyJF 12 | U39vLaHdljwthUaupldlNyCfa6Ofy4qNctlUPlN1AgMBAAEwDQYJKoZIhvcNAQEL 13 | BQADggEBAIOPwsrHPCl2F4VYXXUWjBGHlupJ9GbDk+UgjR+87eEhwuXzxiE/Yv1S 14 | 3SvWkkzUQ0zu2o5Z4B6zf5V4HnpFiy4JikWdhY5jmS30pToGQ9mhaMHa3WMqLh39 15 | vCLqvccK+PfOmS13iaMKjZQRrn6yX1fRE7zc3bIBQou1rkP0utt3kdp0lyvnK24k 16 | 6F2I7MGMLWtjdTxIHuJks7kZ2JQyrkHXBtP0PBKhINhaBY3PQovrF5RM2fCzT87x 17 | F7ZHKJu5HVRJUKcK8nRgC2J2iOHUqYSeyGid0XZZN1g8ux1ZoCaYyqZQhalwSZmD 18 | 0D2NJhnz1Eo+gAWAbjsNPBGOGsMeyHI= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /tests/rsa/certificate_rsa_key_pkcs8.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 jsonwebtoken::{ 2 | crypto::{sign, verify}, 3 | Algorithm, DecodingKey, EncodingKey, 4 | }; 5 | use serde::{Deserialize, Serialize}; 6 | use wasm_bindgen_test::wasm_bindgen_test; 7 | 8 | #[cfg(feature = "use_pem")] 9 | use jsonwebtoken::{decode, encode, Header, Validation}; 10 | #[cfg(feature = "use_pem")] 11 | use time::OffsetDateTime; 12 | 13 | const RSA_ALGORITHMS: &[Algorithm] = &[ 14 | Algorithm::RS256, 15 | Algorithm::RS384, 16 | Algorithm::RS512, 17 | Algorithm::PS256, 18 | Algorithm::PS384, 19 | Algorithm::PS512, 20 | ]; 21 | 22 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 23 | pub struct Claims { 24 | sub: String, 25 | company: String, 26 | exp: i64, 27 | } 28 | 29 | #[cfg(feature = "use_pem")] 30 | #[test] 31 | #[wasm_bindgen_test] 32 | fn round_trip_sign_verification_pem_pkcs1() { 33 | let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); 34 | let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); 35 | let certificate_pem = include_bytes!("certificate_rsa_key_pkcs1.crt"); 36 | 37 | for &alg in RSA_ALGORITHMS { 38 | let encrypted = 39 | sign(b"hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); 40 | 41 | let is_valid = verify( 42 | &encrypted, 43 | b"hello world", 44 | &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), 45 | alg, 46 | ) 47 | .unwrap(); 48 | assert!(is_valid); 49 | 50 | let cert_is_valid = verify( 51 | &encrypted, 52 | b"hello world", 53 | &DecodingKey::from_rsa_pem(certificate_pem).unwrap(), 54 | alg, 55 | ) 56 | .unwrap(); 57 | assert!(cert_is_valid); 58 | } 59 | } 60 | 61 | #[cfg(feature = "use_pem")] 62 | #[test] 63 | #[wasm_bindgen_test] 64 | fn round_trip_sign_verification_pem_pkcs8() { 65 | let privkey_pem = include_bytes!("private_rsa_key_pkcs8.pem"); 66 | let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem"); 67 | let certificate_pem = include_bytes!("certificate_rsa_key_pkcs8.crt"); 68 | 69 | for &alg in RSA_ALGORITHMS { 70 | let encrypted = 71 | sign(b"hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); 72 | 73 | let is_valid = verify( 74 | &encrypted, 75 | b"hello world", 76 | &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), 77 | alg, 78 | ) 79 | .unwrap(); 80 | assert!(is_valid); 81 | 82 | let cert_is_valid = verify( 83 | &encrypted, 84 | b"hello world", 85 | &DecodingKey::from_rsa_pem(certificate_pem).unwrap(), 86 | alg, 87 | ) 88 | .unwrap(); 89 | assert!(cert_is_valid); 90 | } 91 | } 92 | 93 | #[test] 94 | #[wasm_bindgen_test] 95 | fn round_trip_sign_verification_der() { 96 | let privkey_der = include_bytes!("private_rsa_key.der"); 97 | let pubkey_der = include_bytes!("public_rsa_key.der"); 98 | 99 | for &alg in RSA_ALGORITHMS { 100 | let encrypted = sign(b"hello world", &EncodingKey::from_rsa_der(privkey_der), alg).unwrap(); 101 | let is_valid = 102 | verify(&encrypted, b"hello world", &DecodingKey::from_rsa_der(pubkey_der), alg) 103 | .unwrap(); 104 | assert!(is_valid); 105 | } 106 | } 107 | 108 | #[cfg(feature = "use_pem")] 109 | #[test] 110 | #[wasm_bindgen_test] 111 | fn round_trip_claim() { 112 | let my_claims = Claims { 113 | sub: "b@b.com".to_string(), 114 | company: "ACME".to_string(), 115 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 116 | }; 117 | let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); 118 | let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); 119 | let certificate_pem = include_bytes!("certificate_rsa_key_pkcs1.crt"); 120 | 121 | for &alg in RSA_ALGORITHMS { 122 | let token = 123 | encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey_pem).unwrap()) 124 | .unwrap(); 125 | let token_data = decode::( 126 | &token, 127 | &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), 128 | &Validation::new(alg), 129 | ) 130 | .unwrap(); 131 | assert_eq!(my_claims, token_data.claims); 132 | assert!(token_data.header.kid.is_none()); 133 | 134 | let cert_token_data = decode::( 135 | &token, 136 | &DecodingKey::from_rsa_pem(certificate_pem).unwrap(), 137 | &Validation::new(alg), 138 | ) 139 | .unwrap(); 140 | assert_eq!(my_claims, cert_token_data.claims); 141 | assert!(cert_token_data.header.kid.is_none()); 142 | } 143 | } 144 | 145 | #[cfg(feature = "use_pem")] 146 | #[test] 147 | #[wasm_bindgen_test] 148 | fn rsa_modulus_exponent() { 149 | let privkey = include_str!("private_rsa_key_pkcs1.pem"); 150 | let my_claims = Claims { 151 | sub: "b@b.com".to_string(), 152 | company: "ACME".to_string(), 153 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 154 | }; 155 | let n = "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ"; 156 | let e = "AQAB"; 157 | 158 | let encrypted = encode( 159 | &Header::new(Algorithm::RS256), 160 | &my_claims, 161 | &EncodingKey::from_rsa_pem(privkey.as_ref()).unwrap(), 162 | ) 163 | .unwrap(); 164 | let res = decode::( 165 | &encrypted, 166 | &DecodingKey::from_rsa_components(n, e).unwrap(), 167 | &Validation::new(Algorithm::RS256), 168 | ); 169 | assert!(res.is_ok()); 170 | } 171 | 172 | #[cfg(feature = "use_pem")] 173 | #[test] 174 | #[wasm_bindgen_test] 175 | fn rsa_jwk() { 176 | use jsonwebtoken::jwk::Jwk; 177 | use serde_json::json; 178 | 179 | let privkey = include_str!("private_rsa_key_pkcs8.pem"); 180 | let my_claims = Claims { 181 | sub: "b@b.com".to_string(), 182 | company: "ACME".to_string(), 183 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 184 | }; 185 | let jwk:Jwk = serde_json::from_value(json!({ 186 | "kty": "RSA", 187 | "n": "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ", 188 | "e": "AQAB", 189 | "kid": "rsa01", 190 | "alg": "RS256", 191 | "use": "sig" 192 | })).unwrap(); 193 | 194 | let encrypted = encode( 195 | &Header::new(Algorithm::RS256), 196 | &my_claims, 197 | &EncodingKey::from_rsa_pem(privkey.as_ref()).unwrap(), 198 | ) 199 | .unwrap(); 200 | let res = decode::( 201 | &encrypted, 202 | &DecodingKey::from_jwk(&jwk).unwrap(), 203 | &Validation::new(Algorithm::RS256), 204 | ); 205 | assert!(res.is_ok()); 206 | } 207 | 208 | // https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken 209 | #[cfg(feature = "use_pem")] 210 | #[test] 211 | #[wasm_bindgen_test] 212 | fn roundtrip_with_jwtio_example_jey() { 213 | let privkey_pem = include_bytes!("private_jwtio.pem"); 214 | let pubkey_pem = include_bytes!("public_jwtio.pem"); 215 | let certificate_pem = include_bytes!("certificate_jwtio.crt"); 216 | 217 | let my_claims = Claims { 218 | sub: "b@b.com".to_string(), 219 | company: "ACME".to_string(), 220 | exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, 221 | }; 222 | 223 | for &alg in RSA_ALGORITHMS { 224 | let token = 225 | encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey_pem).unwrap()) 226 | .unwrap(); 227 | 228 | let token_data = decode::( 229 | &token, 230 | &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), 231 | &Validation::new(alg), 232 | ) 233 | .unwrap(); 234 | assert_eq!(my_claims, token_data.claims); 235 | 236 | let cert_token_data = decode::( 237 | &token, 238 | &DecodingKey::from_rsa_pem(certificate_pem).unwrap(), 239 | &Validation::new(alg), 240 | ) 241 | .unwrap(); 242 | assert_eq!(my_claims, cert_token_data.claims); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /tests/rsa/private_jwtio.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.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keats/jsonwebtoken/5cd1887848f7d30f11adb4ebcec6a01baffd5252/tests/rsa/private_rsa_key.der -------------------------------------------------------------------------------- /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_jwtio.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.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keats/jsonwebtoken/5cd1887848f7d30f11adb4ebcec6a01baffd5252/tests/rsa/public_rsa_key.der -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------