summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-08-22 02:56:42 +0200
committermetamuffin <metamuffin@disroot.org>2024-08-22 02:56:42 +0200
commit79f59dc9f2f82e2d21dd4738375c53a9f43a4d26 (patch)
tree49b7d880af49b9328786cdd606a8855b72015af7
parent4555531d2cb4856d6216907a22aac6797d097ad2 (diff)
downloadgnix-79f59dc9f2f82e2d21dd4738375c53a9f43a4d26.tar
gnix-79f59dc9f2f82e2d21dd4738375c53a9f43a4d26.tar.bz2
gnix-79f59dc9f2f82e2d21dd4738375c53a9f43a4d26.tar.zst
oidc verify works
-rw-r--r--src/modules/auth/openid.rs116
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();