Skip to content
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

fix: better handle unknown key types in a JWKS #13

Merged
merged 2 commits into from
Jun 15, 2022
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
3 changes: 0 additions & 3 deletions .rusty-hook.toml

This file was deleted.

8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ This changelog is based on the format from [Keep a Changelog](https://keepachang

- New `aliri_axum` crate introduced
- Introduce new `scope_guards!` macro to make it easy to define scope guards
- Introduced `scope!` and `policy!` macros to make those easier to define as well.
- Introduced `scope!` and `policy!` macros to make those easier to define as well
- Several examples added for `aliri_tower` and `aliri_axum`
- `aliri` crate now has an optional `tracing` feature (disabled by default)

### Changed

- Updated to `aliri_braid` v0.2
- This has several minor breaking changes on braid types, but should improve ergonomics overall
- `ScopeToken` now uses small-string optimizations for tokens up to 23 characters (on 64-bit architectures)
- `ScopePolicy` and `Scope` have been optimized for single entry cases.

### Fixes

- `Jwks` is now able to deserialize and ignore JWKs with unrecognized algorithms and uses.
2 changes: 2 additions & 0 deletions aliri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ regex = "1"
ring = "0.16"
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
tracing = { version = "0.1", optional = true }
thiserror = "1"

# EC and Private Key support
openssl = { version = "0.10", optional = true }

[dev-dependencies]
color-eyre = "0.6"
tracing-test = "0.2.2"

[package.metadata.workspaces]
independent = true
7 changes: 5 additions & 2 deletions aliri/data/jwks.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@
"k": "1nk4304g9iJ904hpKLBYo4vL10HIx8QC0scYYpY7vSg"
},
{
"kid": "EkKhyPqtd",
"use": "sig",
"alg": "RS256",
"kty": "RSA",
"n": "3_fVZa_dRW6ubEbH2WFwCfY1Oyiwc3t-rwXa9jvzRKr0bswblwCh-txyEvWOr1eYTSRPgqGgau_0tN2V6vAWvxMCAjuWzddA_JIAYr-EnVcqXh3XxCY0RPZjRtHUenQ5xfemsL0tnhPYAqrLIRnMvs0g12EtO5jVMM-I0JgmUEYRESU1ucgvrSED8_5HqZg-eePc7qoc90vlkm84LgzVvWhswgZ0CpoSX9fWfmFb2Q3kkdGZ_a0d2jQqF2vUMBV0srGJgfm5CyvH9DxDS7HxudQja7UVa4h8QfAzBPWcmdFYGw7ixRHRwL1dLJlxo3Kse7dQXlGHYtg10FFeVHtsCQ",
"e": "AQAB"
},
{
"use": "enc",
"alg": "RSA-OAEP"
}
]
}
}
6 changes: 3 additions & 3 deletions aliri/src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,13 @@ impl Signer for Jwk {

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct JwkDto {
#[serde(rename = "kid")]
#[serde(rename = "kid", default, skip_serializing_if = "Option::is_none")]
key_id: Option<KeyId>,

#[serde(rename = "use")]
#[serde(rename = "use", default, skip_serializing_if = "Option::is_none")]
usage: Option<jwa::Usage>,

#[serde(rename = "alg")]
#[serde(rename = "alg", default, skip_serializing_if = "Option::is_none")]
algorithm: Option<jwa::Algorithm>,

#[serde(flatten)]
Expand Down
146 changes: 139 additions & 7 deletions aliri/src/jwks.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{jwa, jwk, Jwk};

use serde::{Deserialize, Serialize};
use serde::Serialize;

/// A JSON Web Key Set (JWKS)
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
pub struct Jwks {
keys: Vec<Jwk>,
}
Expand Down Expand Up @@ -131,20 +131,152 @@ fn get_key_by_id_impl<'a>(
}

#[cfg(test)]
#[cfg(feature = "rsa")]
mod tests {
use color_eyre::Result;

use crate::test::rsa::*;
#[cfg(feature = "tracing")]
use tracing_test::traced_test;

use super::*;

const JWKS_WITH_UNKNOWN_ALG: &str = r#"
{
"keys": [
{
"kid": "1",
"use": "enc",
"alg": "RSA-OAEP"
}
]
}
"#;

const JWKS_WITH_NO_ALG: &str = r#"
{
"keys": [
{
"kid": "1",
"use": "enc"
}
]
}
"#;

const JWKS_WITH_NOTHING: &str = r#"
{
"keys": [
{}
]
}
"#;

#[test]
#[cfg_attr(feature = "tracing", traced_test)]
fn deserializes_jwks_with_unknown_alg() -> Result<()> {
let jwks: Jwks = serde_json::from_str(JWKS_WITH_UNKNOWN_ALG)?;
dbg!(&jwks);
assert!(jwks.keys.is_empty());
Ok(())
}

#[test]
#[cfg_attr(feature = "tracing", traced_test)]
fn deserialize_jwks_with_no_alg() -> Result<()> {
let jwks: Jwks = serde_json::from_str(JWKS_WITH_NO_ALG)?;
dbg!(&jwks);
assert!(jwks.keys.is_empty());
Ok(())
}

#[test]
fn decodes_jwks() -> Result<()> {
let jwks: Jwks = serde_json::from_str(JWKS)?;
#[cfg_attr(feature = "tracing", traced_test)]
fn deserialize_jwks_with_nothing() -> Result<()> {
let jwks: Jwks = serde_json::from_str(JWKS_WITH_NOTHING)?;
dbg!(&jwks);
assert!(jwks.keys.is_empty());
Ok(())
}

#[cfg(feature = "rsa")]
mod rsa {
use super::*;
use crate::test::rsa::*;

#[test]
#[cfg_attr(feature = "tracing", traced_test)]
fn decodes_jwks() -> Result<()> {
let jwks: Jwks = serde_json::from_str(JWKS)?;
dbg!(&jwks);
assert_eq!(jwks.keys.len(), 1);
Ok(())
}
}

#[cfg(all(feature = "rsa", feature = "hmac", feature = "ec"))]
mod mixed {
use super::*;
use crate::test::mixed::*;

#[test]
#[cfg_attr(feature = "tracing", traced_test)]
fn decodes_jwks() -> Result<()> {
let jwks: Jwks = serde_json::from_str(JWKS)?;
dbg!(&jwks);
assert_eq!(jwks.keys.len(), 3);
Ok(())
}
}
}

impl<'de> serde::Deserialize<'de> for Jwks {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct MaybeJwks {
#[serde(rename = "keys")]
maybe_keys: Vec<MaybeJwk>,
}

#[derive(serde::Deserialize)]
#[serde(untagged)]
enum MaybeJwk {
Jwk(Jwk),
Unknown(JwkLike),
}

#[allow(dead_code)]
#[derive(serde::Deserialize)]
struct JwkLike {
#[serde(default)]
kid: Option<jwk::KeyId>,
#[serde(rename = "use", default)]
r#use: Option<String>,
#[serde(default)]
alg: Option<String>,
}

let maybe_jwks: MaybeJwks = serde::Deserialize::deserialize(deserializer)?;
let keys = maybe_jwks
.maybe_keys
.into_iter()
.enumerate()
.filter_map(|(idx, k)| match k {
MaybeJwk::Jwk(key) => Some(key),
MaybeJwk::Unknown(key) => {
#[cfg(feature = "tracing")]
tracing::warn!(
jwk.kid = ?key.kid, "jwk.use" = ?key.r#use, jwk.alg = ?key.alg, jwks.idx = idx,
"ignoring unknown JWK"
);
let _ = (idx, key);
None
}
})
.collect();

Ok(Jwks { keys })
}
}

// #[cfg(test)]
Expand Down
1 change: 0 additions & 1 deletion aliri_oauth2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ tracing = "0.1.15"
[dev-dependencies]
aliri = { version = "0.5.0", path = "../aliri", features = [ "private-keys" ] }
openssl = "0.10"
rusty-hook = "0.11"
serde_json = "1"
tokio = { version = "1", features = [ "rt-multi-thread", "macros" ] }

Expand Down
2 changes: 0 additions & 2 deletions aliri_tokens/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@ serde_json = { version = "1", optional = true }
thiserror = "1"
tokio = { version = "1", features = [ "time" ] }
tracing = "0.1.15"
tracing-futures = "0.2"

[dev-dependencies]
color-eyre = "0.6"
dotenv = "0.15.0"
structopt = "0.3.21"
tracing-subscriber = { version = "0.3", features = [ "fmt", "env-filter" ] }
test-env-log = { version = "0.2", default-features = false, features = [ "trace" ] }
tokio = { version = "1", features = [ "rt-multi-thread", "macros" ] }