Skip to content

Commit bdd21e1

Browse files
committed
Drop jsonwebtoken dependency
1 parent 674acca commit bdd21e1

4 files changed

Lines changed: 200 additions & 15 deletions

File tree

rust/Cargo.lock

Lines changed: 137 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/auth-impls/Cargo.toml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ edition = "2021"
55
rust-version.workspace = true
66

77
[features]
8-
jwt = [ "jsonwebtoken", "serde" ]
8+
jwt = [ "base64", "serde", "serde_json", "rsa" ]
99
sigs = [ "bitcoin_hashes", "hex-conservative", "secp256k1" ]
1010

1111
[dependencies]
12-
async-trait = "0.1.77"
1312
api = { path = "../api" }
14-
jsonwebtoken = { version = "9.3.0", optional = true, default-features = false, features = ["use_pem"] }
15-
serde = { version = "1.0.210", optional = true, default-features = false, features = ["derive"] }
16-
13+
async-trait = "0.1.77"
14+
base64 = { version = "0.22.1", optional = true, default-features = false, features = ["std"] }
1715
bitcoin_hashes = { version = "0.19", optional = true, default-features = false }
1816
hex-conservative = { version = "1.0", optional = true, default-features = false }
17+
rsa = { version = "0.9.10", optional = true, default-features = false, features = ["sha2"] }
1918
secp256k1 = { version = "0.31", optional = true, default-features = false, features = [ "global-context" ] }
19+
serde = { version = "1.0.210", optional = true, default-features = false, features = ["derive"] }
20+
serde_json = { version = "1.0.149", optional = true, default-features = false, features = ["std"] }
2021

2122
[dev-dependencies]
23+
jsonwebtoken = { version = "9.3.0", default-features = false, features = ["use_pem"] }
2224
tokio = { version = "1.38.0", default-features = false, features = ["rt-multi-thread", "macros"] }

rust/auth-impls/src/jwt.rs

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,25 @@
55
use api::auth::{AuthResponse, Authorizer};
66
use api::error::VssError;
77
use async_trait::async_trait;
8-
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
9-
use serde::{Deserialize, Serialize};
8+
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
9+
use base64::Engine;
10+
use rsa::sha2::{Digest, Sha256};
11+
use rsa::{pkcs8::DecodePublicKey, RsaPublicKey};
12+
use serde::Deserialize;
1013
use std::collections::HashMap;
1114

1215
/// A JWT based authorizer, only allows requests with verified 'JsonWebToken' signed by the given
1316
/// issuer key.
1417
///
1518
/// Refer: https://datatracker.ietf.org/doc/html/rfc7519
1619
pub struct JWTAuthorizer {
17-
jwt_issuer_key: DecodingKey,
20+
jwt_issuer_key: RsaPublicKey,
1821
}
1922

2023
/// A set of Claims claimed by 'JsonWebToken'
2124
///
2225
/// Refer: https://datatracker.ietf.org/doc/html/rfc7519#section-4
23-
#[derive(Serialize, Deserialize, Debug)]
26+
#[derive(Deserialize, Debug)]
2427
pub(crate) struct Claims {
2528
/// The "sub" (subject) claim identifies the principal that is the subject of the JWT.
2629
/// The claims in a JWT are statements about the subject. This can be used as user identifier.
@@ -31,10 +34,22 @@ pub(crate) struct Claims {
3134

3235
const BEARER_PREFIX: &str = "Bearer ";
3336

37+
fn parse_public_key_pem(pem: &str) -> Result<RsaPublicKey, String> {
38+
let body = pem
39+
.trim()
40+
.strip_prefix("-----BEGIN PUBLIC KEY-----")
41+
.ok_or(String::from("Prefix not found"))?
42+
.strip_suffix("-----END PUBLIC KEY-----")
43+
.ok_or(String::from("Suffix not found"))?;
44+
let body: String = body.lines().map(|line| line.trim()).collect();
45+
let body = STANDARD.decode(body).map_err(|_| String::from("Base64 decode failed"))?;
46+
RsaPublicKey::from_public_key_der(&body).map_err(|_| String::from("DER decode failed"))
47+
}
48+
3449
impl JWTAuthorizer {
3550
/// Creates a new instance of [`JWTAuthorizer`], fails on failure to parse the PEM formatted RSA public key
3651
pub async fn new(rsa_pem: &str) -> Result<Self, String> {
37-
let jwt_issuer_key = DecodingKey::from_rsa_pem(rsa_pem.as_bytes())
52+
let jwt_issuer_key = parse_public_key_pem(rsa_pem)
3853
.map_err(|e| format!("Failed to parse the PEM formatted RSA public key: {}", e))?;
3954
Ok(Self { jwt_issuer_key })
4055
}
@@ -53,10 +68,41 @@ impl Authorizer for JWTAuthorizer {
5368
.strip_prefix(BEARER_PREFIX)
5469
.ok_or(VssError::AuthError("Invalid token format.".to_string()))?;
5570

56-
let claims =
57-
decode::<Claims>(token, &self.jwt_issuer_key, &Validation::new(Algorithm::RS256))
58-
.map_err(|e| VssError::AuthError(format!("Authentication failure. {}", e)))?
59-
.claims;
71+
let mut iter = token.split('.');
72+
let [header_base64, claims_base64, signature_base64] =
73+
match [iter.next(), iter.next(), iter.next(), iter.next()] {
74+
[Some(h), Some(c), Some(s), None] => [h, c, s],
75+
_ => {
76+
return Err(VssError::AuthError(String::from(
77+
"Token does not have three parts",
78+
)))
79+
},
80+
};
81+
82+
let header_bytes = URL_SAFE_NO_PAD
83+
.decode(header_base64)
84+
.map_err(|_| VssError::AuthError(String::from("Header base64 decode failed")))?;
85+
let header: serde_json::Value = serde_json::from_slice(&header_bytes)
86+
.map_err(|_| VssError::AuthError(String::from("Header json decode failed")))?;
87+
match header["alg"] {
88+
serde_json::Value::String(ref alg) if alg == "RS256" => (),
89+
_ => return Err(VssError::AuthError(String::from("alg: RS256 not found in header"))),
90+
}
91+
92+
let (message, _) = token.rsplit_once('.').expect("There are two periods in the token");
93+
let signature = URL_SAFE_NO_PAD
94+
.decode(signature_base64)
95+
.map_err(|_| VssError::AuthError(String::from("Signature base64 decode failed")))?;
96+
let digest = Sha256::digest(message.as_bytes());
97+
self.jwt_issuer_key
98+
.verify(rsa::pkcs1v15::Pkcs1v15Sign::new::<Sha256>(), &digest, &signature)
99+
.map_err(|_| VssError::AuthError(String::from("RSA verification failed")))?;
100+
101+
let claims_json = URL_SAFE_NO_PAD
102+
.decode(claims_base64)
103+
.map_err(|_| VssError::AuthError(String::from("Claims base64 decode failed")))?;
104+
let claims: Claims = serde_json::from_slice(&claims_json)
105+
.map_err(|_| VssError::AuthError(String::from("Claims json decode failed")))?;
60106

61107
Ok(AuthResponse { user_token: claims.sub })
62108
}

rust/auth-impls/src/signature.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ mod tests {
9292
use crate::signature::{SignatureValidatingAuthorizer, SIGNING_CONSTANT};
9393
use api::auth::Authorizer;
9494
use api::error::VssError;
95-
use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
95+
use secp256k1::{Message, PublicKey, SecretKey};
9696
use std::collections::HashMap;
9797
use std::fmt::Write;
9898
use std::time::SystemTime;

0 commit comments

Comments
 (0)