mirror of
https://github.com/neondatabase/neon.git
synced 2026-10-16 16:53:02 +00:00
flesh out jwt code
This commit is contained in:
Generated
+147
-3
@@ -1548,6 +1548,33 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.1"
|
||||
@@ -1629,6 +1656,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -1781,10 +1809,48 @@ checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
|
||||
dependencies = [
|
||||
"der 0.6.1",
|
||||
"elliptic-curve 0.12.3",
|
||||
"rfc6979",
|
||||
"rfc6979 0.3.1",
|
||||
"signature 1.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.16.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||
dependencies = [
|
||||
"der 0.7.8",
|
||||
"digest",
|
||||
"elliptic-curve 0.13.8",
|
||||
"rfc6979 0.4.0",
|
||||
"signature 2.2.0",
|
||||
"spki 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8 0.10.2",
|
||||
"signature 2.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"serde",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
@@ -1823,6 +1889,9 @@ dependencies = [
|
||||
"ff 0.13.0",
|
||||
"generic-array",
|
||||
"group 0.13.0",
|
||||
"hkdf",
|
||||
"pem-rfc7468",
|
||||
"pkcs8 0.10.2",
|
||||
"rand_core 0.6.4",
|
||||
"sec1 0.7.3",
|
||||
"subtle",
|
||||
@@ -1988,6 +2057,12 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.22"
|
||||
@@ -2404,6 +2479,15 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
@@ -2885,6 +2969,20 @@ dependencies = [
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa 0.16.9",
|
||||
"elliptic-curve 0.13.8",
|
||||
"once_cell",
|
||||
"sha2",
|
||||
"signature 2.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.7"
|
||||
@@ -3586,7 +3684,7 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"ecdsa 0.14.8",
|
||||
"elliptic-curve 0.12.3",
|
||||
"sha2",
|
||||
]
|
||||
@@ -3597,8 +3695,10 @@ version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
|
||||
dependencies = [
|
||||
"ecdsa 0.16.9",
|
||||
"elliptic-curve 0.13.8",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3607,8 +3707,24 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
|
||||
dependencies = [
|
||||
"ecdsa 0.16.9",
|
||||
"elliptic-curve 0.13.8",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p521"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
|
||||
dependencies = [
|
||||
"base16ct 0.2.0",
|
||||
"ecdsa 0.16.9",
|
||||
"elliptic-curve 0.13.8",
|
||||
"primeorder",
|
||||
"rand_core 0.6.4",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3972,6 +4088,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
@@ -4388,6 +4513,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"async-compression",
|
||||
"async-trait",
|
||||
"atomic-take",
|
||||
@@ -4405,6 +4531,8 @@ dependencies = [
|
||||
"consumption_metrics",
|
||||
"crossbeam-deque",
|
||||
"dashmap",
|
||||
"ecdsa 0.16.9",
|
||||
"ed25519-dalek",
|
||||
"env_logger",
|
||||
"fallible-iterator",
|
||||
"framed-websockets",
|
||||
@@ -4427,13 +4555,16 @@ dependencies = [
|
||||
"itertools 0.10.5",
|
||||
"jose-jwa",
|
||||
"jose-jwk",
|
||||
"jsonwebtoken",
|
||||
"k256",
|
||||
"lasso",
|
||||
"md5",
|
||||
"measured",
|
||||
"metrics",
|
||||
"once_cell",
|
||||
"opentelemetry",
|
||||
"p256 0.13.2",
|
||||
"p384",
|
||||
"p521",
|
||||
"parking_lot 0.12.1",
|
||||
"parquet",
|
||||
"parquet_derive",
|
||||
@@ -4454,6 +4585,7 @@ dependencies = [
|
||||
"reqwest-retry",
|
||||
"reqwest-tracing",
|
||||
"routerify",
|
||||
"rsa",
|
||||
"rstest",
|
||||
"rustc-hash",
|
||||
"rustls 0.22.4",
|
||||
@@ -4463,6 +4595,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"signature 2.2.0",
|
||||
"smallvec",
|
||||
"smol_str",
|
||||
"socket2 0.5.5",
|
||||
@@ -4965,6 +5098,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
@@ -5390,6 +5533,7 @@ dependencies = [
|
||||
"base16ct 0.2.0",
|
||||
"der 0.7.8",
|
||||
"generic-array",
|
||||
"pkcs8 0.10.2",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
+14
-4
@@ -11,6 +11,7 @@ testing = []
|
||||
[dependencies]
|
||||
ahash.workspace = true
|
||||
anyhow.workspace = true
|
||||
arc-swap.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-trait.workspace = true
|
||||
atomic-take.workspace = true
|
||||
@@ -46,9 +47,6 @@ http-body-util = { version = "0.1" }
|
||||
indexmap.workspace = true
|
||||
ipnet.workspace = true
|
||||
itertools.workspace = true
|
||||
jose-jwa = "0.1.2"
|
||||
jose-jwk = "0.1.2"
|
||||
jsonwebtoken.workspace = true
|
||||
lasso = { workspace = true, features = ["multi-threaded"] }
|
||||
md5.workspace = true
|
||||
measured = { workspace = true, features = ["lasso"] }
|
||||
@@ -76,7 +74,7 @@ rustls.workspace = true
|
||||
scopeguard.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2 = { workspace = true, features = ["asm"] }
|
||||
sha2 = { workspace = true, features = ["asm", "oid"] }
|
||||
smol_str.workspace = true
|
||||
smallvec.workspace = true
|
||||
socket2.workspace = true
|
||||
@@ -106,6 +104,18 @@ x509-parser.workspace = true
|
||||
postgres-protocol.workspace = true
|
||||
redis.workspace = true
|
||||
|
||||
# jwt stuff
|
||||
jose-jwa = "0.1.2"
|
||||
jose-jwk = { version = "0.1.2", features = ["p256", "p384", "rsa"] }
|
||||
signature = "2"
|
||||
ecdsa = "0.16"
|
||||
p256 = "0.13"
|
||||
p384 = "0.13"
|
||||
p521 = "0.13"
|
||||
k256 = "0.13"
|
||||
ed25519-dalek = "2"
|
||||
rsa = "0.9"
|
||||
|
||||
workspace_hack.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod classic;
|
||||
mod hacks;
|
||||
pub mod jwt;
|
||||
mod link;
|
||||
mod jwt;
|
||||
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
+218
-94
@@ -1,17 +1,25 @@
|
||||
use std::time::Duration;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use arc_swap::{access::Access, ArcSwap, Guard};
|
||||
use dashmap::{mapref::entry::Entry, DashMap};
|
||||
use jsonwebtoken::{jwk::JwkSet, Algorithm, DecodingKey, TokenData, Validation};
|
||||
use hmac::digest::generic_array::GenericArray;
|
||||
// use jose_jwa::S;
|
||||
use jose_jwk::crypto::KeyInfo;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use crate::intern::EndpointIdInt;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JWKCache {
|
||||
client: reqwest::Client,
|
||||
|
||||
// I don't like this. jsonwebtoken crate is horrendous with the amount of string allocations.. oh well.
|
||||
map: DashMap<EndpointIdInt, JWKCacheEntry>,
|
||||
map: DashMap<EndpointIdInt, Arc<JWKCacheEntryLock>>,
|
||||
}
|
||||
|
||||
pub struct JWKCacheEntryLock {
|
||||
cached: ArcSwap<JWKCacheEntry>,
|
||||
lookup: tokio::sync::Mutex<()>,
|
||||
}
|
||||
|
||||
pub struct JWKCacheEntry {
|
||||
@@ -19,13 +27,46 @@ pub struct JWKCacheEntry {
|
||||
/// Should refetch when new key IDs are seen only every 5 minutes or so
|
||||
last_retrieved: Instant,
|
||||
|
||||
/// jwks urls
|
||||
urls: Vec<url::Url>,
|
||||
|
||||
// /// jwks urls
|
||||
// urls: Vec<url::Url>,
|
||||
/// cplane will return multiple JWKs urls that we need to scrape.
|
||||
key_sets: Vec<jose_jwk::JwkSet>,
|
||||
}
|
||||
|
||||
impl JWKCacheEntryLock {
|
||||
async fn renew_jwks(
|
||||
&self,
|
||||
client: &reqwest::Client,
|
||||
endpoint: EndpointIdInt,
|
||||
) -> Arc<JWKCacheEntry> {
|
||||
let _guard = self.lookup.lock().await;
|
||||
|
||||
// double check that no one beat us to updating the cache.
|
||||
let now = Instant::now();
|
||||
let guard = self.cached.load_full();
|
||||
let last_update = now.duration_since(guard.last_retrieved);
|
||||
if last_update < Duration::from_secs(300) {
|
||||
return guard;
|
||||
}
|
||||
|
||||
todo!("refetch jwks from cplane");
|
||||
|
||||
// fetch from cplane
|
||||
// fetch jwks
|
||||
// entry.last_retrieved = now;
|
||||
|
||||
let x = Arc::new(JWKCacheEntry {
|
||||
last_retrieved: now,
|
||||
key_sets: vec![],
|
||||
});
|
||||
self.cached.swap(x.clone());
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_RENEW: Duration = Duration::from_secs(300);
|
||||
const MAX_RENEW: Duration = Duration::from_secs(3600);
|
||||
|
||||
impl JWKCache {
|
||||
pub async fn check_jwt(
|
||||
&self,
|
||||
@@ -48,82 +89,197 @@ impl JWKCache {
|
||||
let header = serde_json::from_slice::<JWTHeader>(&header)
|
||||
.context("not a valid compact JWT encoding")?;
|
||||
|
||||
// let header = jsonwebtoken::decode_header(&jwt).context("could not parse JWT")?;
|
||||
ensure!(header.typ == "JWT");
|
||||
let kid = header.kid.context("missing key id")?;
|
||||
|
||||
let entry = match self.map.get(&endpoint) {
|
||||
Some(entry) => entry,
|
||||
Some(entry) => Arc::clone(&*entry),
|
||||
None => match self.map.entry(endpoint) {
|
||||
Entry::Occupied(entry) => entry.into_ref().downgrade(),
|
||||
Entry::Occupied(entry) => Arc::clone(&*entry.into_ref()),
|
||||
Entry::Vacant(_) => todo!("fetch jwks from cplane"),
|
||||
},
|
||||
};
|
||||
|
||||
let now = Instant::now();
|
||||
if now.duration_since(entry.last_retrieved) > Duration::from_secs(3600) {
|
||||
todo!("refetch jwks from cplane")
|
||||
// drop(entry) - don't want to hold the lock while running long network operations
|
||||
// fetch from cplane
|
||||
// fetch jwks
|
||||
// entry.last_retrieved = now;
|
||||
// check if the cached JWKs need updating.
|
||||
let mut guard = {
|
||||
let now = Instant::now();
|
||||
let guard = entry.cached.load();
|
||||
let last_update = now.duration_since(guard.last_retrieved);
|
||||
|
||||
// can we use arcswap compare_and_swap here?
|
||||
}
|
||||
if last_update > MAX_RENEW {
|
||||
// it's been too long since we checked the keys. wait for them to update.
|
||||
entry.renew_jwks(&self.client, endpoint).await
|
||||
} else if last_update > MIN_RENEW {
|
||||
// every 5 minutes we should spawn a job to eagerly update the token.
|
||||
|
||||
let jwk = entry.key_sets.iter().flat_map(|jwks| &jwks.keys).find(|jwk| {
|
||||
jwk.prm.kid.as_deref() == Some(kid) && jwk.prm.alg == Some(header.alg)
|
||||
});
|
||||
let entry = entry.clone();
|
||||
let client = self.client.clone();
|
||||
tokio::spawn(async move {
|
||||
entry.renew_jwks(&client, endpoint).await;
|
||||
});
|
||||
|
||||
let key = match jwk {
|
||||
Some(jwk) if jwk.is_supported() => DecodingKey::from_jwk(jwk).context("invalid key")?,
|
||||
None if now.duration_since(entry.last_retrieved) > Duration::from_secs(300) => {
|
||||
// refetch jwks
|
||||
todo!("fetch jwks again");
|
||||
}
|
||||
_ => {
|
||||
bail!("jwk not found");
|
||||
Guard::into_inner(guard)
|
||||
} else {
|
||||
Guard::into_inner(guard)
|
||||
}
|
||||
};
|
||||
|
||||
let mut validation = Validation::new(Algorithm::ES256);
|
||||
// support all public key algorithms
|
||||
validation.algorithms = vec![
|
||||
Algorithm::ES256,
|
||||
Algorithm::ES384,
|
||||
Algorithm::RS256,
|
||||
Algorithm::RS384,
|
||||
Algorithm::RS512,
|
||||
Algorithm::PS256,
|
||||
Algorithm::PS384,
|
||||
Algorithm::PS512,
|
||||
Algorithm::EdDSA,
|
||||
];
|
||||
// get the key from the JWKs if possible. If not, wait for the keys to update.
|
||||
let jwk = loop {
|
||||
let jwk = guard
|
||||
.key_sets
|
||||
.iter()
|
||||
.flat_map(|jwks| &jwks.keys)
|
||||
.find(|jwk| {
|
||||
jwk.prm.kid.as_deref() == Some(kid) && jwk.key.is_supported(&header.alg)
|
||||
});
|
||||
|
||||
// should we require some set of common claims?
|
||||
// eg `exp`?
|
||||
validation.required_spec_claims.clear();
|
||||
validation.leeway = 60;
|
||||
match jwk {
|
||||
Some(jwk) => break jwk,
|
||||
None if guard.last_retrieved.elapsed() > MIN_RENEW => {
|
||||
guard = entry.renew_jwks(&self.client, endpoint).await
|
||||
}
|
||||
_ => {
|
||||
bail!("jwk not found");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
validation.validate_exp = true;
|
||||
validation.validate_nbf = true;
|
||||
// we don't have an audience - I want cplane to add this field
|
||||
validation.validate_aud = false;
|
||||
let sig = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)
|
||||
.context("not a valid compact JWT encoding")?;
|
||||
match &jwk.key {
|
||||
jose_jwk::Key::Ec(key) => {
|
||||
verify_ec_signature(header_payload.as_bytes(), &sig, key)?;
|
||||
}
|
||||
jose_jwk::Key::Rsa(key) => {
|
||||
verify_rsa_signature(header_payload.as_bytes(), &sig, key, &jwk.prm.alg)?;
|
||||
}
|
||||
jose_jwk::Key::Okp(key) => {
|
||||
verify_okp_signature(header_payload.as_bytes(), &sig, key)?;
|
||||
}
|
||||
key => bail!("unsupported key type {key:?}"),
|
||||
};
|
||||
|
||||
// subject can be anything - we support all users
|
||||
validation.sub = None;
|
||||
// don't know the audience - I want cplane to add this field
|
||||
validation.aud = None;
|
||||
// don't know the issuer - I want cplane to add this field
|
||||
validation.iss = None;
|
||||
|
||||
// ew...
|
||||
let token: TokenData<serde_json::Value> =
|
||||
jsonwebtoken::decode(&jwt, &key, &validation).context("invalid token")?;
|
||||
// TODO: verify iss, exp, nbf, etc...
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_ec_signature(data: &[u8], sig: &[u8], key: &jose_jwk::Ec) -> anyhow::Result<()> {
|
||||
use ecdsa::Signature;
|
||||
use signature::Verifier;
|
||||
|
||||
match key.crv {
|
||||
jose_jwk::EcCurves::P256 => {
|
||||
let pk =
|
||||
p256::PublicKey::try_from(key).map_err(|_| anyhow::anyhow!("invalid P256 key"))?;
|
||||
let key = p256::ecdsa::VerifyingKey::from(&pk);
|
||||
let sig = Signature::from_slice(sig)?;
|
||||
key.verify(data, &sig)?;
|
||||
}
|
||||
jose_jwk::EcCurves::P384 => {
|
||||
let pk =
|
||||
p384::PublicKey::try_from(key).map_err(|_| anyhow::anyhow!("invalid P384 key"))?;
|
||||
let key = p384::ecdsa::VerifyingKey::from(&pk);
|
||||
let sig = Signature::from_slice(sig)?;
|
||||
key.verify(data, &sig)?;
|
||||
}
|
||||
jose_jwk::EcCurves::P521 => {
|
||||
ensure!(key.x.len() == 66 && key.y.len() == 66);
|
||||
let x = GenericArray::from_slice(&key.x);
|
||||
let y = GenericArray::from_slice(&key.y);
|
||||
|
||||
let encoded = p521::EncodedPoint::from_affine_coordinates(x, y, false);
|
||||
let key = p521::ecdsa::VerifyingKey::from_encoded_point(&encoded)?;
|
||||
let sig = Signature::from_slice(sig)?;
|
||||
key.verify(data, &sig)?;
|
||||
}
|
||||
jose_jwk::EcCurves::P256K => {
|
||||
ensure!(key.x.len() == 32 && key.y.len() == 32);
|
||||
let x = GenericArray::from_slice(&key.x);
|
||||
let y = GenericArray::from_slice(&key.y);
|
||||
|
||||
let encoded = k256::EncodedPoint::from_affine_coordinates(x, y, false);
|
||||
let key = k256::ecdsa::VerifyingKey::from_encoded_point(&encoded)?;
|
||||
let sig = Signature::from_slice(sig)?;
|
||||
key.verify(data, &sig)?;
|
||||
}
|
||||
key => bail!("unsupported ec key type {key:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_okp_signature(data: &[u8], sig: &[u8], key: &jose_jwk::Okp) -> anyhow::Result<()> {
|
||||
use ed25519_dalek::Signature;
|
||||
use signature::Verifier;
|
||||
|
||||
match key.crv {
|
||||
jose_jwk::OkpCurves::Ed25519 => {
|
||||
let x = <&[u8; 32]>::try_from(&**key.x)?;
|
||||
let key = ed25519_dalek::VerifyingKey::from_bytes(x)?;
|
||||
let sig = Signature::from_slice(sig)?;
|
||||
key.verify(data, &sig)?;
|
||||
}
|
||||
// jose_jwk::OkpCurves::Ed448 => todo!(),
|
||||
// jose_jwk::OkpCurves::X25519 => todo!(),
|
||||
// jose_jwk::OkpCurves::X448 => todo!(),
|
||||
key => bail!("unsupported octet key pair curve type {key:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_rsa_signature(
|
||||
data: &[u8],
|
||||
sig: &[u8],
|
||||
key: &jose_jwk::Rsa,
|
||||
alg: &Option<jose_jwa::Algorithm>,
|
||||
) -> anyhow::Result<()> {
|
||||
use jose_jwa::{Algorithm, Signing};
|
||||
use rsa::{Pkcs1v15Sign, Pss, RsaPublicKey};
|
||||
use sha2::Digest;
|
||||
|
||||
let key = RsaPublicKey::try_from(key).map_err(|_| anyhow::anyhow!("invalid RSA key"))?;
|
||||
|
||||
match alg {
|
||||
Some(Algorithm::Signing(Signing::Ps256)) => {
|
||||
let hashed = sha2::Sha256::digest(data);
|
||||
let scheme = Pss::new::<sha2::Sha256>();
|
||||
key.verify(scheme, &hashed, sig)?;
|
||||
}
|
||||
Some(Algorithm::Signing(Signing::Ps384)) => {
|
||||
let hashed = sha2::Sha384::digest(data);
|
||||
let scheme = Pss::new::<sha2::Sha384>();
|
||||
key.verify(scheme, &hashed, sig)?;
|
||||
}
|
||||
Some(Algorithm::Signing(Signing::Ps512)) => {
|
||||
let hashed = sha2::Sha512::digest(data);
|
||||
let scheme = Pss::new::<sha2::Sha512>();
|
||||
key.verify(scheme, &hashed, sig)?;
|
||||
}
|
||||
Some(Algorithm::Signing(Signing::Rs256)) => {
|
||||
let hashed = sha2::Sha256::digest(data);
|
||||
let scheme = Pkcs1v15Sign::new::<sha2::Sha256>();
|
||||
key.verify(scheme, &hashed, sig)?;
|
||||
}
|
||||
Some(Algorithm::Signing(Signing::Rs384)) => {
|
||||
let hashed = sha2::Sha384::digest(data);
|
||||
let scheme = Pkcs1v15Sign::new::<sha2::Sha384>();
|
||||
key.verify(scheme, &hashed, sig)?;
|
||||
}
|
||||
Some(Algorithm::Signing(Signing::Rs512)) => {
|
||||
let hashed = sha2::Sha512::digest(data);
|
||||
let scheme = Pkcs1v15Sign::new::<sha2::Sha512>();
|
||||
key.verify(scheme, &hashed, sig)?;
|
||||
}
|
||||
_ => bail!("invalid RSA signing algorithm"),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://datatracker.ietf.org/doc/html/rfc7515#section-4.1>
|
||||
#[derive(serde::Deserialize)]
|
||||
struct JWTHeader<'a> {
|
||||
@@ -134,35 +290,3 @@ struct JWTHeader<'a> {
|
||||
/// key id, must be provided for our usecase
|
||||
kid: Option<&'a str>,
|
||||
}
|
||||
|
||||
enum JwsAlg {
|
||||
// we do not support symmetric key algorithms such as HMAC.
|
||||
|
||||
// RSASSA PKCS1-v1_5: <https://www.rfc-editor.org/rfc/rfc7518#section-3.3>
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-256
|
||||
RS256,
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-384
|
||||
RS384,
|
||||
/// RSASSA-PKCS1-v1_5 using SHA-512
|
||||
RS512,
|
||||
|
||||
// ECDSA: <https://www.rfc-editor.org/rfc/rfc7518#section-3.4>
|
||||
/// ECDSA using P-256 and SHA-256
|
||||
ES256,
|
||||
/// ECDSA using P-384 and SHA-384
|
||||
ES384,
|
||||
/// ECDSA using P-521 and SHA-512
|
||||
ES512,
|
||||
|
||||
// RSASSA-PSS: <https://www.rfc-editor.org/rfc/rfc7518#section-3.5>
|
||||
/// RSASSA-PSS using SHA-256 and MGF1 with SHA-256
|
||||
PS256,
|
||||
/// RSASSA-PSS using SHA-384 and MGF1 with SHA-384
|
||||
PS384,
|
||||
/// RSASSA-PSS using SHA-512 and MGF1 with SHA-512
|
||||
PS512,
|
||||
|
||||
// EdDSA: <https://datatracker.ietf.org/doc/html/rfc8037#section-3.1>
|
||||
/// Edwards-curve Digital Signature Algorithm (EdDSA)
|
||||
EdDSA,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user