261 lines
8.6 KiB
Rust
261 lines
8.6 KiB
Rust
//! Authentication simulation example
|
|
//!
|
|
//! This example simulates the authentication flow without requiring a running server.
|
|
//! It demonstrates:
|
|
//! 1. Key generation and management
|
|
//! 2. Nonce request simulation
|
|
//! 3. Message signing and verification
|
|
//! 4. Credential management
|
|
//! 5. Authentication state checking
|
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
use log::info;
|
|
|
|
// Import authentication modules
|
|
use circle_client_ws::CircleWsClientBuilder;
|
|
|
|
#[cfg(feature = "crypto")]
|
|
use circle_client_ws::auth::{
|
|
generate_private_key,
|
|
derive_public_key,
|
|
sign_message,
|
|
verify_signature,
|
|
AuthCredentials,
|
|
NonceResponse
|
|
};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Initialize logging
|
|
env_logger::init();
|
|
|
|
info!("🔐 Starting authentication simulation example");
|
|
|
|
// Step 1: Generate cryptographic keys
|
|
info!("🔑 Generating cryptographic keys...");
|
|
|
|
#[cfg(feature = "crypto")]
|
|
let (private_key, public_key) = {
|
|
let private_key = generate_private_key()?;
|
|
let public_key = derive_public_key(&private_key)?;
|
|
info!("✅ Generated private key: {}...", &private_key[..10]);
|
|
info!("✅ Derived public key: {}...", &public_key[..20]);
|
|
(private_key, public_key)
|
|
};
|
|
|
|
#[cfg(not(feature = "crypto"))]
|
|
let (private_key, _public_key) = {
|
|
let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
|
|
let public_key = "04abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string();
|
|
info!("📝 Using fallback keys (crypto feature disabled)");
|
|
(private_key, public_key)
|
|
};
|
|
|
|
// Step 2: Simulate nonce request and response
|
|
info!("📡 Simulating nonce request...");
|
|
let current_time = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs();
|
|
|
|
let simulated_nonce = format!("nonce_{}_{}", current_time, "abcdef123456");
|
|
let expires_at = current_time + 300; // 5 minutes from now
|
|
|
|
#[cfg(feature = "crypto")]
|
|
let nonce_response = NonceResponse {
|
|
nonce: simulated_nonce.clone(),
|
|
expires_at,
|
|
};
|
|
|
|
info!("✅ Simulated nonce response:");
|
|
info!(" Nonce: {}", simulated_nonce);
|
|
info!(" Expires at: {}", expires_at);
|
|
|
|
// Step 3: Sign the nonce
|
|
info!("✍️ Signing nonce with private key...");
|
|
|
|
#[cfg(feature = "crypto")]
|
|
let signature = {
|
|
match sign_message(&private_key, &simulated_nonce) {
|
|
Ok(sig) => {
|
|
info!("✅ Signature created: {}...", &sig[..20]);
|
|
sig
|
|
}
|
|
Err(e) => {
|
|
error!("❌ Failed to sign message: {}", e);
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
};
|
|
|
|
#[cfg(not(feature = "crypto"))]
|
|
let _signature = {
|
|
info!("📝 Using fallback signature (crypto feature disabled)");
|
|
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string()
|
|
};
|
|
|
|
// Step 4: Verify the signature
|
|
info!("🔍 Verifying signature...");
|
|
|
|
#[cfg(feature = "crypto")]
|
|
{
|
|
match verify_signature(&public_key, &simulated_nonce, &signature) {
|
|
Ok(true) => info!("✅ Signature verification successful!"),
|
|
Ok(false) => {
|
|
error!("❌ Signature verification failed!");
|
|
return Err("Signature verification failed".into());
|
|
}
|
|
Err(e) => {
|
|
error!("❌ Signature verification error: {}", e);
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "crypto"))]
|
|
{
|
|
info!("📝 Skipping signature verification (crypto feature disabled)");
|
|
}
|
|
|
|
// Step 5: Create authentication credentials
|
|
info!("📋 Creating authentication credentials...");
|
|
#[cfg(feature = "crypto")]
|
|
let credentials = AuthCredentials::new(
|
|
public_key.clone(),
|
|
signature.clone(),
|
|
nonce_response.nonce.clone(),
|
|
expires_at
|
|
);
|
|
|
|
#[cfg(feature = "crypto")]
|
|
{
|
|
info!("✅ Credentials created:");
|
|
info!(" Public key: {}...", &credentials.public_key()[..20]);
|
|
info!(" Signature: {}...", &credentials.signature()[..20]);
|
|
info!(" Nonce: {}", credentials.nonce());
|
|
info!(" Expires at: {}", credentials.expires_at);
|
|
info!(" Is expired: {}", credentials.is_expired());
|
|
info!(" Expires within 60s: {}", credentials.expires_within(60));
|
|
info!(" Expires within 400s: {}", credentials.expires_within(400));
|
|
}
|
|
|
|
// Step 6: Create client with authentication
|
|
info!("🔌 Creating WebSocket client with authentication...");
|
|
let _client = CircleWsClientBuilder::new("ws://localhost:8080/ws".to_string())
|
|
.with_keypair(private_key.clone())
|
|
.build();
|
|
|
|
info!("✅ Client created");
|
|
|
|
// Step 7: Demonstrate key rotation
|
|
info!("🔄 Demonstrating key rotation...");
|
|
|
|
#[cfg(feature = "crypto")]
|
|
{
|
|
let new_private_key = generate_private_key()?;
|
|
let new_public_key = derive_public_key(&new_private_key)?;
|
|
|
|
info!("✅ Generated new keys:");
|
|
info!(" New private key: {}...", &new_private_key[..10]);
|
|
info!(" New public key: {}...", &new_public_key[..20]);
|
|
|
|
// Create new client with rotated keys
|
|
let _new_client = CircleWsClientBuilder::new("ws://localhost:8080/ws".to_string())
|
|
.with_keypair(new_private_key)
|
|
.build();
|
|
|
|
info!("✅ Created client with rotated keys");
|
|
}
|
|
|
|
#[cfg(not(feature = "crypto"))]
|
|
{
|
|
info!("📝 Skipping key rotation (crypto feature disabled)");
|
|
}
|
|
|
|
// Step 8: Demonstrate credential expiration
|
|
info!("⏰ Demonstrating credential expiration...");
|
|
|
|
// Create credentials that expire soon
|
|
#[cfg(feature = "crypto")]
|
|
let short_lived_credentials = AuthCredentials::new(
|
|
public_key,
|
|
signature,
|
|
nonce_response.nonce,
|
|
current_time + 5 // Expires in 5 seconds
|
|
);
|
|
|
|
#[cfg(feature = "crypto")]
|
|
{
|
|
info!("✅ Created short-lived credentials:");
|
|
info!(" Expires at: {}", short_lived_credentials.expires_at);
|
|
info!(" Is expired: {}", short_lived_credentials.is_expired());
|
|
info!(" Expires within 10s: {}", short_lived_credentials.expires_within(10));
|
|
|
|
// Wait a moment and check again
|
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
|
info!("⏳ After 1 second:");
|
|
info!(" Is expired: {}", short_lived_credentials.is_expired());
|
|
info!(" Expires within 5s: {}", short_lived_credentials.expires_within(5));
|
|
}
|
|
|
|
info!("🎉 Authentication simulation completed successfully!");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_key_generation() {
|
|
#[cfg(feature = "crypto")]
|
|
{
|
|
let private_key = generate_private_key().unwrap();
|
|
assert!(private_key.starts_with("0x"));
|
|
assert_eq!(private_key.len(), 66); // 0x + 64 hex chars
|
|
|
|
let public_key = derive_public_key(&private_key).unwrap();
|
|
assert!(public_key.starts_with("04"));
|
|
assert_eq!(public_key.len(), 130); // 04 + 128 hex chars (uncompressed)
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_signature_flow() {
|
|
#[cfg(feature = "crypto")]
|
|
{
|
|
let private_key = generate_private_key().unwrap();
|
|
let public_key = derive_public_key(&private_key).unwrap();
|
|
let message = "test_nonce_12345";
|
|
|
|
let signature = sign_message(&private_key, message).unwrap();
|
|
let is_valid = verify_signature(&public_key, message, &signature).unwrap();
|
|
|
|
assert!(is_valid);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_credentials() {
|
|
let current_time = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs();
|
|
|
|
#[cfg(feature = "crypto")]
|
|
let credentials = AuthCredentials::new(
|
|
"04abcdef...".to_string(),
|
|
"0x123456...".to_string(),
|
|
"nonce_123".to_string(),
|
|
current_time + 300
|
|
);
|
|
|
|
#[cfg(feature = "crypto")]
|
|
{
|
|
assert!(!credentials.is_expired());
|
|
assert!(credentials.expires_within(400));
|
|
assert!(!credentials.expires_within(100));
|
|
}
|
|
}
|
|
} |