fix: add missing DCR fields, PKCE verifier validation, and Cargo.lock sync
- Add policy_uri, tos_uri, software_id, software_version to DCR per RFC 7591 - Add code_verifier length (43-128) and charset validation per RFC 7636 §4.1 - Warn at startup if OIDC server enabled without identity providers - Include Cargo.lock update for ring dependency
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -5519,6 +5519,7 @@ dependencies = [
|
|||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest 0.13.1",
|
"reqwest 0.13.1",
|
||||||
|
"ring",
|
||||||
"ruma",
|
"ruma",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustyline-async",
|
"rustyline-async",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ pub(crate) async fn registration_route(State(services): State<crate::State>, Jso
|
|||||||
let reg = oidc.register_client(body)?;
|
let reg = oidc.register_client(body)?;
|
||||||
info!("OIDC client registered: {} ({})", reg.client_id, reg.client_name.as_deref().unwrap_or("unnamed"));
|
info!("OIDC client registered: {} ({})", reg.client_id, reg.client_name.as_deref().unwrap_or("unnamed"));
|
||||||
|
|
||||||
Ok((StatusCode::CREATED, Json(serde_json::json!({"client_id": reg.client_id, "client_id_issued_at": reg.registered_at, "redirect_uris": reg.redirect_uris, "client_name": reg.client_name, "client_uri": reg.client_uri, "logo_uri": reg.logo_uri, "contacts": reg.contacts, "token_endpoint_auth_method": reg.token_endpoint_auth_method, "grant_types": reg.grant_types, "response_types": reg.response_types, "application_type": reg.application_type}))))
|
Ok((StatusCode::CREATED, Json(serde_json::json!({"client_id": reg.client_id, "client_id_issued_at": reg.registered_at, "redirect_uris": reg.redirect_uris, "client_name": reg.client_name, "client_uri": reg.client_uri, "logo_uri": reg.logo_uri, "contacts": reg.contacts, "token_endpoint_auth_method": reg.token_endpoint_auth_method, "grant_types": reg.grant_types, "response_types": reg.response_types, "application_type": reg.application_type, "policy_uri": reg.policy_uri, "tos_uri": reg.tos_uri, "software_id": reg.software_id, "software_version": reg.software_version}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use ruma::UserId;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use tuwunel_core::{
|
use tuwunel_core::{
|
||||||
Err, Result, err, implement, info,
|
Err, Result, err, implement, info, warn,
|
||||||
utils::{hash::sha256, result::LogErr, stream::ReadyExt},
|
utils::{hash::sha256, result::LogErr, stream::ReadyExt},
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -46,6 +46,9 @@ impl crate::Service for Service {
|
|||||||
let oidc_server = if !args.server.config.identity_provider.is_empty()
|
let oidc_server = if !args.server.config.identity_provider.is_empty()
|
||||||
|| args.server.config.well_known.client.is_some()
|
|| args.server.config.well_known.client.is_some()
|
||||||
{
|
{
|
||||||
|
if args.server.config.identity_provider.is_empty() {
|
||||||
|
warn!("OIDC server enabled (well_known.client is set) but no identity_provider configured; authorization flow will not work");
|
||||||
|
}
|
||||||
info!("Initializing OIDC server for next-gen auth (MSC2965)");
|
info!("Initializing OIDC server for next-gen auth (MSC2965)");
|
||||||
Some(Arc::new(OidcServer::build(args)?))
|
Some(Arc::new(OidcServer::build(args)?))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ pub struct DcrRequest {
|
|||||||
pub logo_uri: Option<String>, #[serde(default)] pub contacts: Vec<String>,
|
pub logo_uri: Option<String>, #[serde(default)] pub contacts: Vec<String>,
|
||||||
pub token_endpoint_auth_method: Option<String>, pub grant_types: Option<Vec<String>>,
|
pub token_endpoint_auth_method: Option<String>, pub grant_types: Option<Vec<String>>,
|
||||||
pub response_types: Option<Vec<String>>, pub application_type: Option<String>,
|
pub response_types: Option<Vec<String>>, pub application_type: Option<String>,
|
||||||
|
pub policy_uri: Option<String>, pub tos_uri: Option<String>,
|
||||||
|
pub software_id: Option<String>, pub software_version: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@@ -30,7 +32,8 @@ pub struct OidcClientRegistration {
|
|||||||
pub client_id: String, pub redirect_uris: Vec<String>, pub client_name: Option<String>,
|
pub client_id: String, pub redirect_uris: Vec<String>, pub client_name: Option<String>,
|
||||||
pub client_uri: Option<String>, pub logo_uri: Option<String>, pub contacts: Vec<String>,
|
pub client_uri: Option<String>, pub logo_uri: Option<String>, pub contacts: Vec<String>,
|
||||||
pub token_endpoint_auth_method: String, pub grant_types: Vec<String>, pub response_types: Vec<String>,
|
pub token_endpoint_auth_method: String, pub grant_types: Vec<String>, pub response_types: Vec<String>,
|
||||||
pub application_type: Option<String>, pub registered_at: u64,
|
pub application_type: Option<String>, pub policy_uri: Option<String>, pub tos_uri: Option<String>,
|
||||||
|
pub software_id: Option<String>, pub software_version: Option<String>, pub registered_at: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@@ -124,7 +127,8 @@ impl OidcServer {
|
|||||||
token_endpoint_auth_method: auth_method,
|
token_endpoint_auth_method: auth_method,
|
||||||
grant_types: request.grant_types.unwrap_or_else(|| vec!["authorization_code".to_owned(), "refresh_token".to_owned()]),
|
grant_types: request.grant_types.unwrap_or_else(|| vec!["authorization_code".to_owned(), "refresh_token".to_owned()]),
|
||||||
response_types: request.response_types.unwrap_or_else(|| vec!["code".to_owned()]),
|
response_types: request.response_types.unwrap_or_else(|| vec!["code".to_owned()]),
|
||||||
application_type: request.application_type,
|
application_type: request.application_type, policy_uri: request.policy_uri, tos_uri: request.tos_uri,
|
||||||
|
software_id: request.software_id, software_version: request.software_version,
|
||||||
registered_at: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs(),
|
registered_at: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs(),
|
||||||
};
|
};
|
||||||
self.db.oidcclientid_registration.raw_put(&*client_id, Cbor(®istration));
|
self.db.oidcclientid_registration.raw_put(&*client_id, Cbor(®istration));
|
||||||
@@ -172,6 +176,7 @@ impl OidcServer {
|
|||||||
|
|
||||||
if let Some(challenge) = &session.code_challenge {
|
if let Some(challenge) = &session.code_challenge {
|
||||||
let Some(verifier) = code_verifier else { return Err!(Request(Forbidden("code_verifier required for PKCE"))); };
|
let Some(verifier) = code_verifier else { return Err!(Request(Forbidden("code_verifier required for PKCE"))); };
|
||||||
|
Self::validate_code_verifier(verifier)?;
|
||||||
let method = session.code_challenge_method.as_deref().unwrap_or("S256");
|
let method = session.code_challenge_method.as_deref().unwrap_or("S256");
|
||||||
let computed = match method {
|
let computed = match method {
|
||||||
| "S256" => { let hash = utils::hash::sha256::hash(verifier.as_bytes()); b64.encode(hash) },
|
| "S256" => { let hash = utils::hash::sha256::hash(verifier.as_bytes()); b64.encode(hash) },
|
||||||
@@ -184,6 +189,19 @@ impl OidcServer {
|
|||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate code_verifier per RFC 7636 Section 4.1: must be 43-128
|
||||||
|
/// characters using only unreserved characters [A-Z] / [a-z] / [0-9] /
|
||||||
|
/// "-" / "." / "_" / "~".
|
||||||
|
fn validate_code_verifier(verifier: &str) -> Result {
|
||||||
|
if !(43..=128).contains(&verifier.len()) {
|
||||||
|
return Err!(Request(InvalidParam("code_verifier must be 43-128 characters")));
|
||||||
|
}
|
||||||
|
if !verifier.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'.' || b == b'_' || b == b'~') {
|
||||||
|
return Err!(Request(InvalidParam("code_verifier contains invalid characters")));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sign_id_token(&self, claims: &IdTokenClaims) -> Result<String> {
|
pub fn sign_id_token(&self, claims: &IdTokenClaims) -> Result<String> {
|
||||||
let mut header = jwt::Header::new(jwt::Algorithm::ES256);
|
let mut header = jwt::Header::new(jwt::Algorithm::ES256);
|
||||||
header.kid = Some(self.key_id.clone());
|
header.kid = Some(self.key_id.clone());
|
||||||
|
|||||||
Reference in New Issue
Block a user