commit 93f90f6d4994f4a976b170ecda88d511c3276724
parent eb9f58ce4af8cb2ba25265d698cb68ecd8eaff8e
Author: Henrique Chan Carvalho Machado <henriqueccmachado@tecnico.ulisboa.pt>
Date: Sun, 19 Oct 2025 22:33:13 +0200
oauth2_gateway: implemented cryptographically secure nonce generation
Diffstat:
4 files changed, 68 insertions(+), 12 deletions(-)
diff --git a/oauth2_gateway/Cargo.toml b/oauth2_gateway/Cargo.toml
@@ -23,7 +23,7 @@ tower-http = { version = "0.6.6", features = ["trace"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
-# HTTP client needed for performing requests
+# HTTP client
reqwest = { version = "0.12", features = ["json"] }
# Configuration
@@ -41,3 +41,6 @@ tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
# Error handling
anyhow = "1.0.100"
+# Cryptography
+rand = "0.8.5"
+base64 = "0.22.1"
diff --git a/oauth2_gateway/src/crypto.rs b/oauth2_gateway/src/crypto.rs
@@ -0,0 +1,53 @@
+use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
+use rand::Rng;
+
+/// Generate a cryptographically secure nonce
+///
+/// Format: base64 encoded random bytes
+/// Length: 256 bits (43 base64 characters)
+///
+/// Example: "k7E9mZqYvXwPxR2nT8uL5sA6fH3jC1dG4bN0iM9oU2p"
+pub fn generate_nonce() -> String {
+ let mut rng = rand::thread_rng();
+ let mut bytes = [0u8; 32]; // 32 bytes
+ rng.fill(&mut bytes);
+
+ // base64 encoding (no padding)
+ URL_SAFE_NO_PAD.encode(bytes)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::collections::HashSet;
+
+ #[test]
+ fn test_nonce_generation() {
+ let nonce = generate_nonce();
+
+ // Check length (32 bytes base64 = 43 chars without padding)
+ assert_eq!(nonce.len(), 43);
+
+ // Check it's URL-safe (only contains valid characters)
+ assert!(nonce.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_'));
+ }
+
+ #[test]
+ fn test_nonces_are_unique() {
+ let mut nonces = HashSet::new();
+
+ // Generate 1000 nonces, all should be unique
+ for _ in 0..1000 {
+ let nonce = generate_nonce();
+ assert!(nonces.insert(nonce), "Duplicate nonce generated!");
+ }
+ }
+
+ #[test]
+ fn test_no_padding_in_tokens() {
+ // Ensure no '=' padding characters
+ let nonce = generate_nonce();
+
+ assert!(!nonce.contains('='));
+ }
+}
+\ No newline at end of file
diff --git a/oauth2_gateway/src/handlers.rs b/oauth2_gateway/src/handlers.rs
@@ -9,6 +9,7 @@ use serde_json::json;
use crate::{
models::*,
state::AppState,
+ crypto,
};
// Health check endpoint
@@ -27,10 +28,8 @@ pub async fn setup(
Json(request): Json<SetupRequest>,
) -> impl IntoResponse {
tracing::info!("Setup request for client: {}, scope: {}", client_id, request.scope);
-
- // Generate a simple nonce for now
- // TODO: Change to cyptographic nonce
- let nonce = uuid::Uuid::new_v4().to_string();
+
+ let nonce = crypto::generate_nonce();
tracing::info!("Generated nonce: {}", nonce);
@@ -47,6 +46,7 @@ pub async fn authorize(
) -> impl IntoResponse {
tracing::info!("Authorize request for nonce: {}", nonce);
+ // TODO: Validate nonce
// TODO: Call the SwiyuVerifier to generate the QR code/verification URL
// For now, return a mock response
@@ -73,10 +73,9 @@ pub async fn token(
}
// TODO: Validate nonce/code
- // TODO: Change to cyptographic token
+ // TODO: Change to cryptographically secure token
- // Generate a simple access token
- let access_token = uuid::Uuid::new_v4().to_string();
+ let access_token = crypto::generate_nonce();
let response = TokenResponse {
access_token,
@@ -125,8 +124,7 @@ pub async fn notification_webhook(
request.verification_complete
);
- // TODO: When verification is complete, post the Exchange at
- // TODO: {exchange_base_url}/oauth2gw/kyc/notify/{clientId}
+ // TODO: POST the Exchange at {exchange_base_url}/oauth2gw/kyc/notify/{clientId}
StatusCode::OK
}
diff --git a/oauth2_gateway/src/lib.rs b/oauth2_gateway/src/lib.rs
@@ -1,4 +1,5 @@
pub mod config;
pub mod handlers;
pub mod models;
-pub mod state;
-\ No newline at end of file
+pub mod state;
+pub mod crypto;
+\ No newline at end of file