Skip to content

JWK thumbprint #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ my_claims = decode_jws(&encoded, &DecodingKey::from_secret("secret".as_ref()), &
The generic parameter in `Jws<C>` indicates the claims type and prevents accidentally encoding or decoding the wrong claims type
when the Jws is nested in another struct.

### JWK Thumbprints

If you have a JWK object, you can generate a thumbprint like

```
let tp = my_jwk.thumbprint(&jsonwebtoken::DIGEST_SHA256);
```

### Convert SEC1 private key to PKCS8
`jsonwebtoken` currently only supports PKCS8 format for private EC keys. If your key has `BEGIN EC PRIVATE KEY` at the top,
this is a SEC1 type and can be converted to PKCS8 like so:
Expand Down
81 changes: 80 additions & 1 deletion src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use crate::{
errors::{self, Error, ErrorKind},
serialization::b64_encode,
Algorithm,
};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
Expand Down Expand Up @@ -402,6 +403,13 @@ pub enum AlgorithmParameters {
OctetKeyPair(OctetKeyPairParameters),
}

/// The function to use to hash the intermediate thumbprint data.
pub enum ThumbprintHash {
SHA256,
SHA384,
SHA512,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub struct Jwk {
#[serde(flatten)]
Expand All @@ -416,6 +424,60 @@ impl Jwk {
pub fn is_supported(&self) -> bool {
self.common.key_algorithm.unwrap().to_algorithm().is_ok()
}

/// Compute the thumbprint of the JWK.
///
/// Per (RFC-7638)[https://datatracker.ietf.org/doc/html/rfc7638]
pub fn thumbprint(&self, hash_function: ThumbprintHash) -> String {
let hash_function = match hash_function {
ThumbprintHash::SHA256 => &ring::digest::SHA256,
ThumbprintHash::SHA384 => &ring::digest::SHA384,
ThumbprintHash::SHA512 => &ring::digest::SHA512,
};
let pre = match &self.algorithm {
AlgorithmParameters::EllipticCurve(a) => match a.curve {
EllipticCurve::P256 | EllipticCurve::P384 | EllipticCurve::P521 => {
format!(
r#"{{"crv":{},"kty":{},"x":"{}","y":"{}"}}"#,
serde_json::to_string(&a.curve).unwrap(),
serde_json::to_string(&a.key_type).unwrap(),
a.x,
a.y,
)
}
EllipticCurve::Ed25519 => panic!("EllipticCurve can't contain this curve type"),
},
AlgorithmParameters::RSA(a) => {
format!(
r#"{{"e":"{}","kty":{},"n":"{}"}}"#,
a.e,
serde_json::to_string(&a.key_type).unwrap(),
a.n,
)
}
AlgorithmParameters::OctetKey(a) => {
format!(
r#"{{"k":"{}","kty":{}}}"#,
a.value,
serde_json::to_string(&a.key_type).unwrap()
)
}
AlgorithmParameters::OctetKeyPair(a) => match a.curve {
EllipticCurve::P256 | EllipticCurve::P384 | EllipticCurve::P521 => {
panic!("OctetKeyPair can't contain this curve type")
}
EllipticCurve::Ed25519 => {
format!(
r#"{{crv:{},"kty":{},"x":"{}"}}"#,
serde_json::to_string(&a.curve).unwrap(),
serde_json::to_string(&a.key_type).unwrap(),
a.x,
)
}
},
};
return b64_encode(ring::digest::digest(hash_function, pre.as_bytes()));
}
}

/// A JWK set
Expand All @@ -435,7 +497,9 @@ impl JwkSet {

#[cfg(test)]
mod tests {
use crate::jwk::{AlgorithmParameters, JwkSet, OctetKeyType};
use crate::jwk::{
AlgorithmParameters, Jwk, JwkSet, OctetKeyType, RSAKeyParameters, ThumbprintHash,
};
use crate::serialization::b64_encode;
use crate::Algorithm;
use serde_json::json;
Expand Down Expand Up @@ -471,4 +535,19 @@ mod tests {
_ => panic!("Unexpected key algorithm"),
}
}

#[test]
#[wasm_bindgen_test]
fn check_thumbprint() {
let tp = Jwk {
common: crate::jwk::CommonParameters { key_id: Some("2011-04-29".to_string()), ..Default::default() },
algorithm: AlgorithmParameters::RSA(RSAKeyParameters {
key_type: crate::jwk::RSAKeyType::RSA,
n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw".to_string(),
e: "AQAB".to_string(),
}),
}
.thumbprint(ThumbprintHash::SHA256);
assert_eq!(tp.as_str(), "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs");
}
}