ezidam: added jwks route in well-known
This commit is contained in:
parent
8c37fc1181
commit
d62cfcd1d9
6 changed files with 94 additions and 7 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -771,6 +771,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"database_pool",
|
"database_pool",
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
|
"futures",
|
||||||
"hash",
|
"hash",
|
||||||
"id",
|
"id",
|
||||||
"identicon-rs",
|
"identicon-rs",
|
||||||
|
|
@ -911,6 +912,7 @@ checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
|
@ -961,6 +963,17 @@ version = "0.3.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
|
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.26"
|
version = "0.3.26"
|
||||||
|
|
@ -982,6 +995,7 @@ dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -1483,6 +1497,8 @@ dependencies = [
|
||||||
name = "jwt"
|
name = "jwt"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"database",
|
||||||
"id",
|
"id",
|
||||||
"jwt-compact",
|
"jwt-compact",
|
||||||
"rand",
|
"rand",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ infer = { version = "0.12.0", default-features = false }
|
||||||
erased-serde = "0.3"
|
erased-serde = "0.3"
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
identicon-rs = "4.0.1"
|
identicon-rs = "4.0.1"
|
||||||
|
futures = "0.3.26"
|
||||||
|
|
||||||
# local crates
|
# local crates
|
||||||
database_pool = { path = "../database_pool" }
|
database_pool = { path = "../database_pool" }
|
||||||
|
|
|
||||||
|
|
@ -35,3 +35,9 @@ impl From<openid::Error> for Error {
|
||||||
Error::internal_server_error(e)
|
Error::internal_server_error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<jwt::Error> for Error {
|
||||||
|
fn from(e: jwt::Error) -> Self {
|
||||||
|
Error::internal_server_error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
use futures::future::join_all;
|
||||||
|
use jwt::database::Key;
|
||||||
|
use jwt::PublicKey;
|
||||||
use rocket::get;
|
use rocket::get;
|
||||||
use rocket::serde::json::{Json, Value};
|
use rocket::serde::json::{Json, Value};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![openid_configuration]
|
routes![openid_configuration, json_web_keys]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/openid-configuration")]
|
#[get("/openid-configuration")]
|
||||||
|
|
@ -24,6 +27,35 @@ async fn openid_configuration(mut db: Connection<Database>) -> Result<Json<Value
|
||||||
Ok(Json(openid_configuration))
|
Ok(Json(openid_configuration))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/jwks.json")]
|
||||||
|
async fn json_web_keys(mut db: Connection<Database>) -> Result<Json<Vec<Value>>> {
|
||||||
|
// Get keys
|
||||||
|
let keys = Key::get_all(&mut *db, Some(false)).await?;
|
||||||
|
|
||||||
|
// For each key, import as public key and extract the jwk
|
||||||
|
let json_web_keys = join_all(keys.into_iter().map(|key| {
|
||||||
|
task::spawn_blocking(move || {
|
||||||
|
// Import public key
|
||||||
|
PublicKey::try_from(key)
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
// Handle JoinError if one is found
|
||||||
|
.into_iter()
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()?
|
||||||
|
// Collect all values, return error immediately if one is found
|
||||||
|
.into_iter()
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()?
|
||||||
|
// Extract jwk
|
||||||
|
.into_iter()
|
||||||
|
.map(|key| key.jwk())
|
||||||
|
// Collect all values, return error immediately if one is found
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
// HTTP response
|
||||||
|
Ok(Json(json_web_keys))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::tests::*;
|
use crate::tests::*;
|
||||||
|
|
@ -58,4 +90,17 @@ mod test {
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_ne!(response.status(), Status::Ok);
|
assert_ne!(response.status(), Status::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn jwks() {
|
||||||
|
// Setup http server
|
||||||
|
let client = setup_rocket_testing();
|
||||||
|
|
||||||
|
// Make request
|
||||||
|
let response = client.get(uri!("/.well-known/jwks.json")).dispatch();
|
||||||
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
|
||||||
|
// Make sure it is valid json
|
||||||
|
assert!(response.into_json::<Value>().is_some());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,7 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("Failed to parse JWT: `{0}`")]
|
#[error("Failed to parse JWT: `{0}`")]
|
||||||
JwtParsing(#[from] jwt_compact::ParseError),
|
JwtParsing(#[from] jwt_compact::ParseError),
|
||||||
|
|
||||||
|
#[error("Failed to serialize JWK: `{0}`")]
|
||||||
|
JwkSerialization(#[from] serde_json::Error),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::database::Key;
|
||||||
use crate::jwk::JsonWebKey;
|
use crate::jwk::JsonWebKey;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use id::KeyID;
|
use id::KeyID;
|
||||||
|
|
@ -5,12 +6,21 @@ use jwt_compact::alg::{Rsa, RsaPublicKey, StrongKey};
|
||||||
use jwt_compact::jwk::JsonWebKey as JsonWebKeyBase;
|
use jwt_compact::jwk::JsonWebKey as JsonWebKeyBase;
|
||||||
use jwt_compact::Algorithm;
|
use jwt_compact::Algorithm;
|
||||||
use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey};
|
use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
pub struct PublicKey {
|
pub struct PublicKey {
|
||||||
id: KeyID,
|
id: KeyID,
|
||||||
key: RsaPublicKey,
|
key: RsaPublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Key> for PublicKey {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(key: Key) -> Result<Self, Self::Error> {
|
||||||
|
PublicKey::from_der(key.public_der(), key.key_id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PublicKey {
|
impl PublicKey {
|
||||||
pub fn new(id: &KeyID, key: StrongKey<RsaPublicKey>) -> Self {
|
pub fn new(id: &KeyID, key: StrongKey<RsaPublicKey>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -30,12 +40,16 @@ impl PublicKey {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jwk(&self) -> JsonWebKey {
|
pub fn jwk(&self) -> Result<Value, Error> {
|
||||||
JsonWebKey {
|
Ok(serde_json::to_value(JsonWebKey {
|
||||||
base: JsonWebKeyBase::from(&self.key),
|
base: JsonWebKeyBase::from(&self.key),
|
||||||
key_id: self.id.as_ref(),
|
key_id: self.id.as_ref(),
|
||||||
algorithm: Rsa::ps256().name(),
|
algorithm: Rsa::ps256().name(),
|
||||||
}
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_id(&self) -> &KeyID {
|
||||||
|
&self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,11 +96,13 @@ mod tests {
|
||||||
let premade_der = include_bytes!("../../tests/public_key.der");
|
let premade_der = include_bytes!("../../tests/public_key.der");
|
||||||
let public_key = PublicKey::from_der(premade_der, &key_id).unwrap();
|
let public_key = PublicKey::from_der(premade_der, &key_id).unwrap();
|
||||||
|
|
||||||
let generated_jwk = serde_json::to_string(&public_key.jwk());
|
// Generate jwk
|
||||||
|
let generated_jwk = public_key.jwk();
|
||||||
assert!(generated_jwk.is_ok());
|
assert!(generated_jwk.is_ok());
|
||||||
|
|
||||||
let jwk = r#"{"kty":"RSA","n":"3os0j_kNfdTHJVQ-eMYXyRBWIqsrJDdELxLAh3_WlOZtsBwiGVNnpHQm9cRB63Un9UJpYGbWz38emglXc8bHPrUArDl-K_5ioDlbh7hAaz3rZ6b8LDIPUO-jYICdxBdv1THXSWbTEistZF1TYsXg7G4xrxiFKnZLBNaSgJrKOAY8AUNWuby-vZKr5X9e3SG7kvPsUITyqSmDz4ZTCj4QScx4O9gyqz1_UEBxTRSKcpS82YzAo2Byo5avRWesiGoaxs8lNv0QJ22IY1KVoROv3hHFeFEcg3D4NTfFG2Cd8d1OMXfILhtFnQZbt5ZxIG9SCOfirn32-9OtoLemKlgSq0gbLf6t1OK12LK6mIJ78pphlnhHdvHeJ75PV6c2lS2Wwd75NYBJzhIojG4U4Lbpe7T_NDFaxExry_7V5oxX8tbb-OzJnuPOQRR0H5uOBjdVo7i5vjnDKOTDpro3XPQjBbIBkABhDdU2FcXkEbl8_ByyYZZni7ekzGrVSJB_vxvv","e":"AQAB","kid":"SgTG8ulMHAp5UsGWuCclw36zWsdEo5","alg":"PS256"}"#;
|
// Convert to string to verify easily
|
||||||
let generated_jwk = generated_jwk.unwrap();
|
let generated_jwk = serde_json::to_string(&generated_jwk.unwrap()).unwrap();
|
||||||
|
let jwk = r#"{"alg":"PS256","e":"AQAB","kid":"SgTG8ulMHAp5UsGWuCclw36zWsdEo5","kty":"RSA","n":"3os0j_kNfdTHJVQ-eMYXyRBWIqsrJDdELxLAh3_WlOZtsBwiGVNnpHQm9cRB63Un9UJpYGbWz38emglXc8bHPrUArDl-K_5ioDlbh7hAaz3rZ6b8LDIPUO-jYICdxBdv1THXSWbTEistZF1TYsXg7G4xrxiFKnZLBNaSgJrKOAY8AUNWuby-vZKr5X9e3SG7kvPsUITyqSmDz4ZTCj4QScx4O9gyqz1_UEBxTRSKcpS82YzAo2Byo5avRWesiGoaxs8lNv0QJ22IY1KVoROv3hHFeFEcg3D4NTfFG2Cd8d1OMXfILhtFnQZbt5ZxIG9SCOfirn32-9OtoLemKlgSq0gbLf6t1OK12LK6mIJ78pphlnhHdvHeJ75PV6c2lS2Wwd75NYBJzhIojG4U4Lbpe7T_NDFaxExry_7V5oxX8tbb-OzJnuPOQRR0H5uOBjdVo7i5vjnDKOTDpro3XPQjBbIBkABhDdU2FcXkEbl8_ByyYZZni7ekzGrVSJB_vxvv"}"#;
|
||||||
assert_eq!(jwk, generated_jwk);
|
assert_eq!(jwk, generated_jwk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue