[dependencies]jsonwebtoken = { version = "=11.1.0", features = ["rust_crypto"] }serde_json = "=1.0.154"use jsonwebtoken::jwk::{Jwk, ThumbprintHash};use jsonwebtoken::DecodingKey; fn main() -> Result<(), Box<dyn std::error::Error>> { // From a JWK: Jwk::thumbprint implements RFC 7638 (kid, use and alg are ignored) for file in ["ec.jwk.json", "rsa.jwk.json", "okp.jwk.json"] { let jwk: Jwk = serde_json::from_str(&std::fs::read_to_string(file)?)?; println!("{file}: {}", jwk.thumbprint(ThumbprintHash::SHA256)?); } // From a PEM public key: DecodingKey -> Jwk -> thumbprint let pems: [(&str, fn(&[u8]) -> jsonwebtoken::errors::Result<DecodingKey>); 3] = [ ("ec.pub.pem", DecodingKey::from_ec_pem), ("rsa.pub.pem", DecodingKey::from_rsa_pem), ("okp.pub.pem", DecodingKey::from_ed_pem), ]; for (file, parse) in pems { let jwk = Jwk::from_decoding_key(&parse(&std::fs::read(file)?)?, None)?; println!("{file}: {}", jwk.thumbprint(ThumbprintHash::SHA256)?); } Ok(())}How it works
serde_json::from_str::<Jwk>parses a JWK;kid,useandalgland incommonand are ignored by the thumbprint.jwk.thumbprint(ThumbprintHash::SHA256)returns the base64url thumbprint as aString.DecodingKey::from_ec_pem,from_rsa_pemandfrom_ed_pemread PEM public keys;Jwk::from_decoding_key(&key, None)turns them into JWKs.ThumbprintHash::SHA384andSHA512give the other hash variants.
Gotchas
from_decoding_keyneeds a crypto backend feature (rust_cryptooraws_lc_rs) to extract the key components from DER; without one, the program compiles and then panics inside jsonwebtoken'scryptomodule.- Each PEM constructor accepts only its key type:
from_ec_pemon an RSA key is an error, so the code needs to know (or try) the type. - For keys outside jsonwebtoken's supported set — X25519, Ed448, secp256k1 —
thumbprintreturns an error; compute those over the RFC 7638 members withsha2andbase64directly. - Thumbprints are of public keys: build the JWK from the
DecodingKey, not from theEncodingKey, so the private part can never end up in it.