server-mark2-dev/src/websocket/session.rs
Pakin 3821214de8 add jwt debug
Signed-off-by: Pakin <pakin.t@forth.co.th>
2026-06-17 16:53:08 +07:00

177 lines
5.6 KiB
Rust

use std::sync::Arc;
use aes_gcm::{Aes256Gcm, aead::AeadMut};
use log::{error, info, warn};
use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
use rand::Rng;
use tokio::time::Instant;
use crate::websocket::core::GOOGLE_PUBLIC_ENDPOINT;
pub(crate) struct SecureSession {
pub uid: String,
pub cipher: Aes256Gcm,
pub key_established_at: Instant,
}
#[derive(serde::Deserialize)]
pub(crate) struct HandshakePayload {
pub token: String,
pub client_public_key: String, // BASE 64
}
#[derive(serde::Serialize)]
pub(crate) struct HandshakeAck {
pub status: String,
pub server_public_key: String,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub(crate) struct EncryptedFrame {
pub iv: String, // Initialized vector per message
pub ciphertext: String, // Encrypted application message
}
#[derive(serde::Deserialize)]
pub(crate) struct FirebaseJwtClaims {
aud: String, // Audience (Expect Firebase project id),
sub: String, // Subject (Firebase user uid),
exp: u64, // Expiration timestamp
}
pub(crate) async fn refresh_jwk_cache(
state: Arc<crate::app::AppState>,
) -> Result<(), Box<dyn std::error::Error>> {
let response: serde_json::Value = reqwest::get(GOOGLE_PUBLIC_ENDPOINT).await?.json().await?;
let mut new_keys = Vec::new();
if let Some(obj) = response.as_object() {
for (_, cert_pem) in obj {
if let Some(pem_str) = cert_pem.as_str() {
if let Ok(key) = jsonwebtoken::DecodingKey::from_rsa_pem(pem_str.as_bytes()) {
new_keys.push(key);
}
}
}
}
{
let mut cache = state.jwk_encoding_keys.write().unwrap();
*cache = new_keys;
info!("Google Jwk Identity cache updated!");
}
Ok(())
}
pub(crate) async fn verify_token(
token: &str,
state: Arc<crate::app::AppState>,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::RS256);
validation.set_audience(&[&state.firebase_project_id]);
validation.validate_exp = true;
let keys = { state.jwk_encoding_keys.read().unwrap() };
for key in keys.iter() {
match jsonwebtoken::decode::<FirebaseJwtClaims>(token, key, &validation) {
Ok(token_data) => {
return Ok(token_data.claims.sub);
}
Err(e) => {
error!("failed to decode jwt: {e:?}");
}
}
}
if let Ok(td) = jsonwebtoken::dangerous::insecure_decode::<serde_json::Value>(token) {
let kid = td.header.kid;
error!(
"Invalid Firebase Token signature or metadata mismatch, kid: {:?}, from {:?} ({:?})",
kid.clone(),
td.claims.get("name"),
td.claims.get("email")
);
warn!("current token: {token}");
}
Err(Box::from(
"Invalid Firebase Token signature or metadata mismatch",
))
}
pub(crate) fn execute_dh_handshake(
client_pub_b64: &str,
) -> Result<(String, aes_gcm::Aes256Gcm), Box<dyn std::error::Error + Send + Sync>> {
use aes_gcm::KeyInit;
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use p256::{EncodedPoint, PublicKey, ecdh::EphemeralSecret};
use rand_core::OsRng;
// Step: decode client public key
// info!("client_pub_b64: {client_pub_b64}");
let client_bytes = BASE64.decode(client_pub_b64)?;
let encoded_point = EncodedPoint::from_bytes(&client_bytes)?;
let client_public = PublicKey::from_encoded_point(&encoded_point).unwrap();
// Generate server ephemeral keypair
let server_secret = EphemeralSecret::random(&mut OsRng);
let server_public = PublicKey::from(&server_secret);
// Compute symmetric shared secret
let shared_secret = server_secret.diffie_hellman(&client_public);
let secret_bytes = shared_secret.raw_secret_bytes();
// Instantiate AES-256 GCM Core Cipher block natively using derived 32-byte hash block
let cipher = aes_gcm::Aes256Gcm::new_from_slice(&secret_bytes)
.map_err(|_| "failed allocating cipher payload context init")?;
let server_pub_bytes = server_public.to_encoded_point(false);
let server_public_b64 = BASE64.encode(server_pub_bytes.as_bytes());
Ok((server_public_b64, cipher))
}
pub(crate) fn decrypt_message(
cipher: &aes_gcm::Aes256Gcm,
frame: &EncryptedFrame,
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
use aes_gcm::aead::Aead;
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
let iv_bytes = BASE64.decode(&frame.iv)?;
let ciphertext_bytes = BASE64.decode(&frame.ciphertext)?;
let nonce = aes_gcm::Nonce::from_slice(&iv_bytes);
let decrypted = cipher
.decrypt(nonce, ciphertext_bytes.as_slice())
.map_err(|_| "Decryption routine validation assertion failed")?;
Ok(decrypted)
}
pub(crate) fn encrypt_server_message(
mut cipher: aes_gcm::Aes256Gcm,
plain_text: &str,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
let mut iv_bytes = [0u8; 12];
rand::rng().fill_bytes(&mut iv_bytes);
let nonce = aes_gcm::Nonce::from_slice(&iv_bytes);
let ciphertext_bytes = cipher
.encrypt(nonce, plain_text.as_bytes())
.map_err(|_| "Encryption execution routine failed")?;
let frame = EncryptedFrame {
iv: BASE64.encode(iv_bytes),
ciphertext: BASE64.encode(ciphertext_bytes),
};
let json_output = serde_json::to_string(&frame)?;
Ok(json_output)
}