Skip to content

Commit b96842f

Browse files
committed
Switch to JWK Thumbprints
1 parent 3d911a8 commit b96842f

File tree

4 files changed

+79
-17
lines changed

4 files changed

+79
-17
lines changed

crates/config/src/sections/secrets.rs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,9 @@ use std::borrow::Cow;
99
use anyhow::{Context, bail};
1010
use camino::Utf8PathBuf;
1111
use futures_util::future::{try_join, try_join_all};
12-
use mas_jose::jwk::{JsonWebKey, JsonWebKeySet};
12+
use mas_jose::jwk::{JsonWebKey, JsonWebKeySet, Thumbprint};
1313
use mas_keystore::{Encrypter, Keystore, PrivateKey};
14-
use rand::{
15-
Rng, SeedableRng,
16-
distributions::{Alphanumeric, DistString, Standard},
17-
prelude::Distribution as _,
18-
};
14+
use rand::{Rng, SeedableRng, distributions::Standard, prelude::Distribution as _};
1915
use schemars::JsonSchema;
2016
use serde::{Deserialize, Serialize};
2117
use serde_with::serde_as;
@@ -185,7 +181,7 @@ impl KeyConfig {
185181

186182
let kid = match self.kid.clone() {
187183
Some(kid) => kid,
188-
None => kid_from_key(&private_key)?,
184+
None => private_key.thumbprint_sha256_base64(),
189185
};
190186

191187
Ok(JsonWebKey::new(private_key)
@@ -194,13 +190,6 @@ impl KeyConfig {
194190
}
195191
}
196192

197-
/// Returns a kid derived from the given key.
198-
fn kid_from_key(private_key: &PrivateKey) -> anyhow::Result<String> {
199-
let fingerprint = private_key.fingerprint()?;
200-
let head = fingerprint.first_chunk::<4>().unwrap();
201-
Ok(hex::encode(head))
202-
}
203-
204193
/// Encryption config option.
205194
#[derive(Debug, Clone)]
206195
pub enum Encryption {
@@ -494,7 +483,7 @@ mod tests {
494483

495484
let key_store = config.key_store().await.unwrap();
496485
assert!(key_store.iter().any(|k| k.kid() == Some("lekid0")));
497-
assert!(key_store.iter().any(|k| k.kid() == Some("040b0ab8")));
486+
assert!(key_store.iter().any(|k| k.kid() == Some("ONUCn80fsiISFWKrVMEiirNVr-QEvi7uQI0QH9q9q4o")));
498487
});
499488

500489
Ok(())

crates/jose/src/jwk/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use mas_iana::jose::{
1313
use schemars::JsonSchema;
1414
use serde::{Deserialize, Serialize};
1515
use serde_with::skip_serializing_none;
16+
use sha2::{Digest, Sha256};
1617
use url::Url;
1718

1819
use crate::{
@@ -239,6 +240,28 @@ impl<P> JsonWebKey<P> {
239240
}
240241
}
241242

243+
/// Methods to calculate RFC 7638 JWK Thumbprints.
244+
pub trait Thumbprint {
245+
/// Returns the RFC 7638 JWK Thumbprint JSON string.
246+
fn thumbprint_prehashed(&self) -> String;
247+
248+
/// Returns the RFC 7638 SHA256-hashed JWK Thumbprint.
249+
fn thumbprint_sha256(&self) -> [u8; 32] {
250+
Sha256::digest(self.thumbprint_prehashed()).into()
251+
}
252+
253+
/// Returns the RFC 7638 SHA256-hashed JWK Thumbprint as base64url string.
254+
fn thumbprint_sha256_base64(&self) -> String {
255+
Base64UrlNoPad::new(self.thumbprint_sha256().into()).encode()
256+
}
257+
}
258+
259+
impl<P: Thumbprint> Thumbprint for JsonWebKey<P> {
260+
fn thumbprint_prehashed(&self) -> String {
261+
self.parameters.thumbprint_prehashed()
262+
}
263+
}
264+
242265
impl<P> Constrainable for JsonWebKey<P>
243266
where
244267
P: ParametersInfo,

crates/jose/src/jwk/public_parameters.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use schemars::JsonSchema;
1111
use serde::{Deserialize, Serialize};
1212

1313
use super::ParametersInfo;
14-
use crate::base64::Base64UrlNoPad;
14+
use crate::{base64::Base64UrlNoPad, jwk::Thumbprint};
1515

1616
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1717
#[serde(tag = "kty")]
@@ -52,6 +52,22 @@ impl JsonWebKeyPublicParameters {
5252
}
5353
}
5454

55+
impl Thumbprint for JsonWebKeyPublicParameters {
56+
fn thumbprint_prehashed(&self) -> String {
57+
match self {
58+
JsonWebKeyPublicParameters::Rsa(RsaPublicParameters { n, e }) => {
59+
format!("{{\"e\":\"{e}\",\"kty\":\"RSA\",\"n\":\"{n}\"}}")
60+
}
61+
JsonWebKeyPublicParameters::Ec(EcPublicParameters { crv, x, y }) => {
62+
format!("{{\"crv\":\"{crv}\",\"kty\":\"EC\",\"x\":\"{x}\",\"y\":\"{y}\"}}")
63+
}
64+
JsonWebKeyPublicParameters::Okp(OkpPublicParameters { crv, x }) => {
65+
format!("{{\"crv\":\"{crv}\",\"kty\":\"OKP\",\"x\":\"{x}\"}}")
66+
}
67+
}
68+
}
69+
}
70+
5571
impl ParametersInfo for JsonWebKeyPublicParameters {
5672
fn kty(&self) -> JsonWebKeyType {
5773
match self {
@@ -300,3 +316,31 @@ mod ec_impls {
300316
}
301317
}
302318
}
319+
320+
#[cfg(test)]
321+
mod tests {
322+
use super::*;
323+
324+
#[test]
325+
fn test_thumbprint_rfc_example() {
326+
// From https://www.rfc-editor.org/rfc/rfc7638.html#section-3.1
327+
let n = Base64UrlNoPad::parse(
328+
"\
329+
0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt\
330+
VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6\
331+
4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD\
332+
W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9\
333+
1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH\
334+
aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
335+
)
336+
.unwrap();
337+
let e = Base64UrlNoPad::parse("AQAB").unwrap();
338+
339+
let jwkpps = JsonWebKeyPublicParameters::Rsa(RsaPublicParameters { n, e });
340+
341+
assert_eq!(
342+
jwkpps.thumbprint_sha256_base64(),
343+
"NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
344+
)
345+
}
346+
}

crates/keystore/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use mas_iana::jose::{JsonWebKeyType, JsonWebSignatureAlg};
1818
pub use mas_jose::jwk::{JsonWebKey, JsonWebKeySet};
1919
use mas_jose::{
2020
jwa::{AsymmetricSigningKey, AsymmetricVerifyingKey},
21-
jwk::{JsonWebKeyPublicParameters, ParametersInfo, PublicJsonWebKeySet},
21+
jwk::{JsonWebKeyPublicParameters, ParametersInfo, PublicJsonWebKeySet, Thumbprint},
2222
};
2323
use pem_rfc7468::PemLabel;
2424
use pkcs1::EncodeRsaPrivateKey;
@@ -621,6 +621,12 @@ impl ParametersInfo for PrivateKey {
621621
}
622622
}
623623

624+
impl Thumbprint for PrivateKey {
625+
fn thumbprint_prehashed(&self) -> String {
626+
JsonWebKeyPublicParameters::from(self).thumbprint_prehashed()
627+
}
628+
}
629+
624630
/// A structure to store a list of [`PrivateKey`]. The keys are held in an
625631
/// [`Arc`] to ensure they are only loaded once in memory and allow cheap
626632
/// cloning

0 commit comments

Comments
 (0)