Skip to content

Commit 84f7f96

Browse files
committed
trustpub: Implement UnverifiedClaims::decode() fn
This fn can be used to decode a JSON web token without verifying it's signature or claims. Only the `iss` claim will actually be decoded, since we use that to find the correct decoding key for the JWT issuer.
1 parent e9c0a3e commit 84f7f96

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

crates/crates_io_trustpub/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pub mod github;
44
pub mod keystore;
55
#[cfg(any(test, feature = "test-helpers"))]
66
pub mod test_keys;
7+
pub mod unverified;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use jsonwebtoken::errors::Error;
2+
use jsonwebtoken::{DecodingKey, TokenData, Validation};
3+
use serde::Deserialize;
4+
use std::collections::HashSet;
5+
use std::sync::LazyLock;
6+
7+
/// [`Validation`] configuration for decoding JWTs without any
8+
/// signature validation.
9+
///
10+
/// **This must only be used to extract the `iss` claim from the JWT, which
11+
/// is then used to look up the corresponding OIDC key set.**
12+
static NO_VALIDATION: LazyLock<Validation> = LazyLock::new(|| {
13+
let mut no_validation = Validation::default();
14+
no_validation.validate_aud = false;
15+
no_validation.validate_exp = false;
16+
no_validation.required_spec_claims = HashSet::new();
17+
no_validation.insecure_disable_signature_validation();
18+
no_validation
19+
});
20+
21+
/// Empty [`DecodingKey`] used for decoding JWTs without any signature
22+
/// validation.
23+
///
24+
/// **This must only be used to extract the `iss` claim from the JWT, which
25+
/// is then used to look up the corresponding OIDC key set.**
26+
static EMPTY_KEY: LazyLock<DecodingKey> = LazyLock::new(|| DecodingKey::from_secret(b""));
27+
28+
/// Claims that are extracted from the JWT without any signature
29+
/// validation. Specifically, this only extracts the `iss` claim, which is
30+
/// used to look up the corresponding OIDC key set to then verify the
31+
/// JWT signature.
32+
#[derive(Debug, Deserialize)]
33+
pub struct UnverifiedClaims {
34+
pub iss: String,
35+
}
36+
37+
impl UnverifiedClaims {
38+
/// Decode the JWT and extract the `iss` claim without any
39+
/// signature validation.
40+
///
41+
/// **This must only be used to extract the `iss` claim from the JWT, which
42+
/// is then used to look up the corresponding OIDC key set.**
43+
pub fn decode(token: &str) -> Result<TokenData<Self>, Error> {
44+
jsonwebtoken::decode(token, &EMPTY_KEY, &NO_VALIDATION)
45+
}
46+
}
47+
48+
#[cfg(test)]
49+
mod tests {
50+
use super::*;
51+
use claims::{assert_err, assert_ok, assert_some_eq};
52+
use insta::assert_compact_debug_snapshot;
53+
use jsonwebtoken::{EncodingKey, Header, encode};
54+
use serde::Serialize;
55+
56+
#[derive(Debug, Serialize)]
57+
struct TestClaims {
58+
iss: String,
59+
}
60+
61+
#[test]
62+
fn test_decode_valid_token() {
63+
const KEY_ID: &str = "test-key-id";
64+
const ISSUER: &str = "https://example.com";
65+
66+
let header = Header {
67+
kid: Some(KEY_ID.to_string()),
68+
..Default::default()
69+
};
70+
71+
let iss = ISSUER.to_string();
72+
let claims = TestClaims { iss };
73+
74+
let key = EncodingKey::from_secret(b"test-secret");
75+
let token = assert_ok!(encode(&header, &claims, &key));
76+
77+
let decoded = assert_ok!(UnverifiedClaims::decode(&token));
78+
assert_some_eq!(decoded.header.kid, KEY_ID);
79+
assert_eq!(decoded.claims.iss, ISSUER);
80+
}
81+
82+
#[test]
83+
fn test_decode_invalid_token() {
84+
let error = assert_err!(UnverifiedClaims::decode(""));
85+
assert_compact_debug_snapshot!(error, @"Error(InvalidToken)");
86+
87+
let error = assert_err!(UnverifiedClaims::decode("invalid.token"));
88+
assert_compact_debug_snapshot!(error, @"Error(InvalidToken)");
89+
90+
let error = assert_err!(UnverifiedClaims::decode("invalid.token.format"));
91+
assert_compact_debug_snapshot!(error, @"Error(Base64(InvalidLastSymbol(6, 100)))");
92+
}
93+
}

0 commit comments

Comments
 (0)