diff options
author | metamuffin <metamuffin@disroot.org> | 2024-08-22 02:56:42 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-08-22 02:56:42 +0200 |
commit | 79f59dc9f2f82e2d21dd4738375c53a9f43a4d26 (patch) | |
tree | 49b7d880af49b9328786cdd606a8855b72015af7 | |
parent | 4555531d2cb4856d6216907a22aac6797d097ad2 (diff) | |
download | gnix-79f59dc9f2f82e2d21dd4738375c53a9f43a4d26.tar gnix-79f59dc9f2f82e2d21dd4738375c53a9f43a4d26.tar.bz2 gnix-79f59dc9f2f82e2d21dd4738375c53a9f43a4d26.tar.zst |
oidc verify works
-rw-r--r-- | src/modules/auth/openid.rs | 116 |
1 files changed, 79 insertions, 37 deletions
diff --git a/src/modules/auth/openid.rs b/src/modules/auth/openid.rs index 979649e..fb1fbcb 100644 --- a/src/modules/auth/openid.rs +++ b/src/modules/auth/openid.rs @@ -3,6 +3,10 @@ use crate::{ error::ServiceError, modules::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, }; +use aes_gcm_siv::{ + aead::{Aead, Payload}, + Nonce, +}; use base64::Engine; use bytes::Buf; use futures::Future; @@ -16,6 +20,7 @@ use hyper::{Response, StatusCode}; use hyper_util::rt::TokioIo; use log::info; use percent_encoding::{percent_decode, utf8_percent_encode, NON_ALPHANUMERIC}; +use rand::random; use serde::Deserialize; use serde_yaml::Value; use sha2::{Digest, Sha256}; @@ -66,59 +71,79 @@ impl Node for OpenIDAuth { let state = state.ok_or(ServiceError::CustomStatic("state parameter missing"))?; let code = code.ok_or(ServiceError::CustomStatic("code parameter missing"))?; - let mut redirect_uri = Parts::default(); - redirect_uri.scheme = Some(Scheme::HTTP); - redirect_uri.path_and_query = Some(PathAndQuery::from_str(&state).unwrap()); - redirect_uri.authority = Authority::from_str( - request - .headers() - .get(HOST) - .ok_or(ServiceError::InvalidHeader)? - .to_str()?, - ) - .ok(); - let redirect_uri = Uri::from_parts(redirect_uri) - .map_err(|_| ServiceError::InvalidUri)? - .to_string(); + let (verif_cipher, return_path) = state + .split_once("_") + .ok_or(ServiceError::CustomStatic("state malformed"))?; + + let verif_cipher = hex::decode(verif_cipher) + .map_err(|_| ServiceError::CustomStatic("invalid hex in code verifier"))?; + if verif_cipher.len() < 12 { + return Err(ServiceError::BadAuth); + } - token_request(&self.provider, &self.client_id, &redirect_uri, &code).await?; + let verif_plain = { + let (msg, nonce) = verif_cipher.split_at(verif_cipher.len() - 12); + let verif_plain = context + .state + .crypto_key + .decrypt(Nonce::from_slice(nonce), Payload { msg, aad: &[] }) + .map_err(|_| ServiceError::CustomStatic("authentication invalid"))?; + String::from_utf8(verif_plain)? + }; + + let redirect_uri = redirect_uri(&request)?.to_string(); + token_request( + &self.provider, + &self.client_id, + &redirect_uri, + &code, + &verif_plain, + ) + .await?; let mut r = Response::new(BoxBody::<_, ServiceError>::new( - format!("state={state:?}\ncode={code:?}").map_err(|_| unreachable!()), + format!("state={state:?}\ncode={code:?}\nreturn_path={return_path:?}") + .map_err(|_| unreachable!()), )); r.headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static("text/plain")); return Ok(r); + } + if request.method() == Method::GET && request.uri().path() == "/favicon.ico" { + let mut resp = + Response::new("".to_string()).map(|b| b.map_err(|e| match e {}).boxed()); + *resp.status_mut() = StatusCode::NO_CONTENT; + Ok(resp) } else { - let mut redirect_uri = Parts::default(); - redirect_uri.scheme = Some(Scheme::HTTP); - redirect_uri.path_and_query = - Some(PathAndQuery::from_static("/_gnix_auth_callback")); - redirect_uri.authority = Authority::from_str( - request - .headers() - .get(HOST) - .ok_or(ServiceError::InvalidHeader)? - .to_str()?, - ) - .ok(); - let redirect_uri = Uri::from_parts(redirect_uri) - .map_err(|_| ServiceError::InvalidUri)? - .to_string(); + let (chal, verif_cipher): (Vec<u8>, Vec<u8>) = { + let r = [(); 16].map(|()| random::<u8>()); + let r = base64::engine::general_purpose::URL_SAFE.encode(r); + let r = r.as_bytes(); + + let nonce = [(); 12].map(|_| random::<u8>()); + // gcm and siv are overkill but its fine + let mut v = context + .state + .crypto_key + .encrypt(Nonce::from_slice(&nonce), Payload { msg: &r, aad: &[] }) + .unwrap(); + v.extend(nonce); - let chal: Vec<u8> = { let mut hasher = Sha256::new(); - hasher.update(r"testvalue"); - hasher.finalize().to_vec() + hasher.update(r); + (hasher.finalize().to_vec(), v) }; + let redirect_uri = redirect_uri(&request)?.to_string(); + let uri = format!( - "{}/authorize?client_id={}&redirect_uri={}&state={}&code_challenge={}&code_challenge_method=S256&response_type=code&scope=openid profile email", + "{}/authorize?client_id={}&redirect_uri={}&state={}_{}&code_challenge={}&code_challenge_method=S256&response_type=code&scope=openid profile email", self.provider, utf8_percent_encode(&self.client_id, NON_ALPHANUMERIC), utf8_percent_encode(&redirect_uri, NON_ALPHANUMERIC), + hex::encode(verif_cipher), utf8_percent_encode(&request.uri().to_string(), NON_ALPHANUMERIC), - base64::engine::general_purpose::URL_SAFE.encode(chal), + base64::engine::general_purpose::URL_SAFE.encode(chal).trim_end_matches("="), ); info!("redirect {uri:?}"); let mut resp = @@ -134,11 +159,27 @@ impl Node for OpenIDAuth { } } +fn redirect_uri(request: &NodeRequest) -> Result<Uri, ServiceError> { + let mut redirect_uri = Parts::default(); + redirect_uri.scheme = Some(Scheme::HTTP); + redirect_uri.path_and_query = Some(PathAndQuery::from_static("/_gnix_auth_callback")); + redirect_uri.authority = Authority::from_str( + request + .headers() + .get(HOST) + .ok_or(ServiceError::InvalidHeader)? + .to_str()?, + ) + .ok(); + Ok(Uri::from_parts(redirect_uri).map_err(|_| ServiceError::InvalidUri)?) +} + async fn token_request( provider: &str, client_id: &str, redirect_uri: &str, code: &str, + verifier: &str, ) -> Result<(), ServiceError> { let url = Uri::from_str(&format!("{provider}/token")).unwrap(); let body = format!( @@ -146,8 +187,9 @@ async fn token_request( utf8_percent_encode(client_id, NON_ALPHANUMERIC), utf8_percent_encode(redirect_uri, NON_ALPHANUMERIC), utf8_percent_encode(code, NON_ALPHANUMERIC), - "testvalue" + utf8_percent_encode(verifier, NON_ALPHANUMERIC), ); + info!("validate {url} {body:?}"); let authority = url.authority().unwrap().clone(); let stream = TcpStream::connect(authority.as_str()).await.unwrap(); |