use async_trait::async_trait;
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
use tracing::info;
use uuid::Uuid;
use uvb_core::{TenantId, UserId};
#[derive(Debug, Error)]
pub enum BiometricError {
#[error("User verification not performed")]
UserVerificationNotPerformed,
#[error("Liveness detection not supported")]
LivenessNotSupported,
#[error("Step-up authentication required")]
StepUpRequired,
#[error("Biometric re-verification required")]
ReverificationRequired,
#[error("Biometric quality too low: {quality} (minimum: {minimum})")]
QualityTooLow { quality: u8, minimum: u8 },
#[error("Authenticator not verified")]
AuthenticatorNotVerified,
#[error("Biometric verification expired at {0}")]
VerificationExpired(DateTime<Utc>),
#[error("Risk score too high: {0}")]
RiskScoreTooHigh(u8),
#[error("Storage error: {0}")]
Storage(String),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum BiometricModality {
Fingerprint,
FaceRecognition,
IrisScanning,
VoiceRecognition,
BehavioralBiometrics,
}
impl BiometricModality {
pub fn name(&self) -> &'static str {
match self {
Self::Fingerprint => "Fingerprint",
Self::FaceRecognition => "Face Recognition",
Self::IrisScanning => "Iris Scanning",
Self::VoiceRecognition => "Voice Recognition",
Self::BehavioralBiometrics => "Behavioral Biometrics",
}
}
pub fn spoofing_risk(&self) -> RiskLevel {
match self {
Self::IrisScanning => RiskLevel::Low,
Self::FaceRecognition => RiskLevel::Medium, Self::Fingerprint => RiskLevel::Medium, Self::VoiceRecognition => RiskLevel::High, Self::BehavioralBiometrics => RiskLevel::Low,
}
}
pub fn requires_liveness(&self) -> bool {
matches!(
self,
Self::FaceRecognition | Self::Fingerprint | Self::VoiceRecognition
)
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum RiskLevel {
Low,
Medium,
High,
Critical,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthenticatorCapabilities {
pub aaguid: String,
pub manufacturer: Option<String>,
pub model: Option<String>,
pub supports_uv: bool,
pub supports_liveness: bool,
pub biometric_modality: Option<BiometricModality>,
pub certification_level: CertificationLevel,
pub firmware_version: Option<String>,
}
impl AuthenticatorCapabilities {
pub fn new(aaguid: String) -> Self {
Self {
aaguid,
manufacturer: None,
model: None,
supports_uv: false,
supports_liveness: false,
biometric_modality: None,
certification_level: CertificationLevel::None,
firmware_version: None,
}
}
pub fn is_secure(&self) -> bool {
self.supports_uv
&& self.supports_liveness
&& self.certification_level >= CertificationLevel::Level1
}
pub fn security_score(&self) -> u8 {
let mut score = 0u8;
if self.supports_uv {
score += 30;
}
if self.supports_liveness {
score += 30;
}
score += match self.certification_level {
CertificationLevel::Level3Plus => 40,
CertificationLevel::Level3 => 30,
CertificationLevel::Level2 => 20,
CertificationLevel::Level1 => 10,
CertificationLevel::None => 0,
};
score
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum CertificationLevel {
None,
Level1,
Level2,
Level3,
Level3Plus,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiometricVerification {
pub verification_id: String,
pub user_id: UserId,
pub tenant_id: TenantId,
pub aaguid: String,
pub modality: Option<BiometricModality>,
pub user_verification: bool,
pub liveness_detection: bool,
pub quality_score: Option<u8>,
pub verified_at: DateTime<Utc>,
pub expires_at: DateTime<Utc>,
pub ip_address: Option<String>,
pub risk_score: u8,
pub step_up_completed: bool,
}
impl BiometricVerification {
pub fn new(
user_id: UserId,
tenant_id: TenantId,
aaguid: String,
modality: Option<BiometricModality>,
user_verification: bool,
liveness_detection: bool,
validity_seconds: i64,
) -> Self {
let now = Utc::now();
Self {
verification_id: Uuid::new_v4().to_string(),
user_id,
tenant_id,
aaguid,
modality,
user_verification,
liveness_detection,
quality_score: None,
verified_at: now,
expires_at: now + Duration::seconds(validity_seconds),
ip_address: None,
risk_score: 0,
step_up_completed: false,
}
}
pub fn is_expired(&self) -> bool {
Utc::now() > self.expires_at
}
pub fn with_quality(mut self, score: u8) -> Self {
self.quality_score = Some(score);
self
}
pub fn with_risk_score(mut self, score: u8) -> Self {
self.risk_score = score;
self
}
pub fn with_ip(mut self, ip: String) -> Self {
self.ip_address = Some(ip);
self
}
pub fn mark_step_up_completed(mut self) -> Self {
self.step_up_completed = true;
self
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiometricPolicy {
pub tenant_id: TenantId,
pub require_uv: bool,
pub require_liveness: bool,
pub min_quality_score: u8,
pub min_certification_level: CertificationLevel,
pub verification_validity_seconds: i64,
pub require_step_up_sensitive: bool,
pub risk_threshold_reverify: u8,
pub allowed_modalities: Option<Vec<BiometricModality>>,
}
impl Default for BiometricPolicy {
fn default() -> Self {
Self {
tenant_id: TenantId::new("default"),
require_uv: true, require_liveness: false, min_quality_score: 50,
min_certification_level: CertificationLevel::Level1,
verification_validity_seconds: 300, require_step_up_sensitive: true,
risk_threshold_reverify: 70,
allowed_modalities: None, }
}
}
impl BiometricPolicy {
pub fn strict() -> Self {
Self {
tenant_id: TenantId::new("strict"),
require_uv: true,
require_liveness: true, min_quality_score: 70,
min_certification_level: CertificationLevel::Level2,
verification_validity_seconds: 60, require_step_up_sensitive: true,
risk_threshold_reverify: 50, allowed_modalities: Some(vec![
BiometricModality::Fingerprint,
BiometricModality::FaceRecognition,
BiometricModality::IrisScanning,
]),
}
}
pub fn lenient() -> Self {
Self {
tenant_id: TenantId::new("lenient"),
require_uv: true, require_liveness: false,
min_quality_score: 30,
min_certification_level: CertificationLevel::None,
verification_validity_seconds: 900, require_step_up_sensitive: false,
risk_threshold_reverify: 90, allowed_modalities: None,
}
}
pub fn check_compliance(
&self,
verification: &BiometricVerification,
capabilities: &AuthenticatorCapabilities,
) -> Result<(), BiometricError> {
if self.require_uv && !verification.user_verification {
return Err(BiometricError::UserVerificationNotPerformed);
}
if self.require_liveness && !verification.liveness_detection {
return Err(BiometricError::LivenessNotSupported);
}
if let Some(quality) = verification.quality_score {
if quality < self.min_quality_score {
return Err(BiometricError::QualityTooLow {
quality,
minimum: self.min_quality_score,
});
}
}
if capabilities.certification_level < self.min_certification_level {
return Err(BiometricError::AuthenticatorNotVerified);
}
if verification.is_expired() {
return Err(BiometricError::VerificationExpired(verification.expires_at));
}
if verification.risk_score > self.risk_threshold_reverify {
return Err(BiometricError::ReverificationRequired);
}
if let Some(ref allowed) = self.allowed_modalities {
if let Some(modality) = verification.modality {
if !allowed.contains(&modality) {
return Err(BiometricError::AuthenticatorNotVerified);
}
}
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum OperationSensitivity {
Low,
Medium,
High,
Critical,
}
impl OperationSensitivity {
pub fn requires_step_up(&self) -> bool {
matches!(self, Self::High | Self::Critical)
}
pub fn required_freshness(&self) -> i64 {
match self {
Self::Low => 900, Self::Medium => 300, Self::High => 60, Self::Critical => 30, }
}
}
#[async_trait]
pub trait BiometricStorage: Send + Sync {
async fn store_capabilities(
&self,
capabilities: &AuthenticatorCapabilities,
) -> Result<(), BiometricError>;
async fn get_capabilities(
&self,
aaguid: &str,
) -> Result<Option<AuthenticatorCapabilities>, BiometricError>;
async fn store_verification(
&self,
verification: &BiometricVerification,
) -> Result<(), BiometricError>;
async fn get_latest_verification(
&self,
user_id: &UserId,
) -> Result<Option<BiometricVerification>, BiometricError>;
async fn store_policy(&self, policy: &BiometricPolicy) -> Result<(), BiometricError>;
async fn get_policy(&self, tenant_id: &TenantId) -> Result<BiometricPolicy, BiometricError>;
}
pub struct InMemoryBiometricStorage {
capabilities: tokio::sync::RwLock<HashMap<String, AuthenticatorCapabilities>>,
verifications: tokio::sync::RwLock<HashMap<UserId, BiometricVerification>>,
policies: tokio::sync::RwLock<HashMap<TenantId, BiometricPolicy>>,
}
impl InMemoryBiometricStorage {
pub fn new() -> Self {
Self {
capabilities: tokio::sync::RwLock::new(HashMap::new()),
verifications: tokio::sync::RwLock::new(HashMap::new()),
policies: tokio::sync::RwLock::new(HashMap::new()),
}
}
}
impl Default for InMemoryBiometricStorage {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl BiometricStorage for InMemoryBiometricStorage {
async fn store_capabilities(
&self,
capabilities: &AuthenticatorCapabilities,
) -> Result<(), BiometricError> {
let mut caps = self.capabilities.write().await;
caps.insert(capabilities.aaguid.clone(), capabilities.clone());
Ok(())
}
async fn get_capabilities(
&self,
aaguid: &str,
) -> Result<Option<AuthenticatorCapabilities>, BiometricError> {
let caps = self.capabilities.read().await;
Ok(caps.get(aaguid).cloned())
}
async fn store_verification(
&self,
verification: &BiometricVerification,
) -> Result<(), BiometricError> {
let mut verifications = self.verifications.write().await;
verifications.insert(verification.user_id.clone(), verification.clone());
Ok(())
}
async fn get_latest_verification(
&self,
user_id: &UserId,
) -> Result<Option<BiometricVerification>, BiometricError> {
let verifications = self.verifications.read().await;
Ok(verifications.get(user_id).cloned())
}
async fn store_policy(&self, policy: &BiometricPolicy) -> Result<(), BiometricError> {
let mut policies = self.policies.write().await;
policies.insert(policy.tenant_id.clone(), policy.clone());
Ok(())
}
async fn get_policy(&self, tenant_id: &TenantId) -> Result<BiometricPolicy, BiometricError> {
let policies = self.policies.read().await;
Ok(policies
.get(tenant_id)
.cloned()
.unwrap_or_else(BiometricPolicy::default))
}
}
pub struct BiometricSecurityManager<S: BiometricStorage> {
storage: S,
}
impl<S: BiometricStorage> BiometricSecurityManager<S> {
pub fn new(storage: S) -> Self {
Self { storage }
}
pub async fn register_authenticator(
&self,
capabilities: AuthenticatorCapabilities,
) -> Result<AuthenticatorCapabilities, BiometricError> {
self.storage.store_capabilities(&capabilities).await?;
info!(
"Authenticator registered: {} (secure: {})",
capabilities.aaguid,
capabilities.is_secure()
);
Ok(capabilities)
}
pub async fn record_verification(
&self,
verification: BiometricVerification,
) -> Result<BiometricVerification, BiometricError> {
let policy = self.storage.get_policy(&verification.tenant_id).await?;
let capabilities = self
.storage
.get_capabilities(&verification.aaguid)
.await?
.unwrap_or_else(|| AuthenticatorCapabilities::new(verification.aaguid.clone()));
policy.check_compliance(&verification, &capabilities)?;
self.storage.store_verification(&verification).await?;
info!(
"Biometric verification recorded for user {} (UV: {}, liveness: {})",
verification.user_id, verification.user_verification, verification.liveness_detection
);
Ok(verification)
}
pub async fn verify_for_operation(
&self,
user_id: &UserId,
tenant_id: &TenantId,
sensitivity: OperationSensitivity,
) -> Result<(), BiometricError> {
let policy = self.storage.get_policy(tenant_id).await?;
let verification = self
.storage
.get_latest_verification(user_id)
.await?
.ok_or(BiometricError::UserVerificationNotPerformed)?;
let capabilities = self
.storage
.get_capabilities(&verification.aaguid)
.await?
.unwrap_or_else(|| AuthenticatorCapabilities::new(verification.aaguid.clone()));
policy.check_compliance(&verification, &capabilities)?;
let age_seconds = (Utc::now() - verification.verified_at).num_seconds();
if age_seconds > sensitivity.required_freshness() {
return Err(BiometricError::ReverificationRequired);
}
if sensitivity.requires_step_up()
&& policy.require_step_up_sensitive
&& !verification.step_up_completed
{
return Err(BiometricError::StepUpRequired);
}
info!(
"Biometric verification passed for user {} (sensitivity: {:?})",
user_id, sensitivity
);
Ok(())
}
pub fn calculate_risk_score(
&self,
verification: &BiometricVerification,
capabilities: &AuthenticatorCapabilities,
) -> u8 {
let mut risk = 0u8;
if !verification.user_verification {
risk = risk.saturating_add(40);
}
if !verification.liveness_detection {
if let Some(modality) = verification.modality {
if modality.requires_liveness() {
risk = risk.saturating_add(30);
}
}
}
if let Some(quality) = verification.quality_score {
if quality < 50 {
risk = risk.saturating_add(20);
}
}
if capabilities.certification_level < CertificationLevel::Level1 {
risk = risk.saturating_add(10);
}
risk
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_authenticator_security_score() {
let mut caps = AuthenticatorCapabilities::new("test-aaguid".to_string());
caps.supports_uv = true;
caps.supports_liveness = true;
caps.certification_level = CertificationLevel::Level3;
assert_eq!(caps.security_score(), 90); assert!(caps.is_secure());
}
#[tokio::test]
async fn test_verification_recording() {
let storage = InMemoryBiometricStorage::new();
let manager = BiometricSecurityManager::new(storage);
let mut caps = AuthenticatorCapabilities::new("secure-auth".to_string());
caps.supports_uv = true;
caps.supports_liveness = true;
caps.certification_level = CertificationLevel::Level2;
manager.register_authenticator(caps).await.unwrap();
let verification = BiometricVerification::new(
UserId::new("test_user"),
TenantId::new("test_tenant"),
"secure-auth".to_string(),
Some(BiometricModality::Fingerprint),
true,
true,
300,
);
let recorded = manager.record_verification(verification).await.unwrap();
assert!(recorded.user_verification);
assert!(recorded.liveness_detection);
}
#[tokio::test]
async fn test_policy_compliance() {
let policy = BiometricPolicy::strict();
let mut caps = AuthenticatorCapabilities::new("test".to_string());
caps.supports_uv = true;
caps.supports_liveness = true;
caps.certification_level = CertificationLevel::Level2;
let verification = BiometricVerification::new(
UserId::new("test_user"),
TenantId::new("test_tenant"),
"test".to_string(),
Some(BiometricModality::Fingerprint),
true,
true,
300,
);
assert!(policy.check_compliance(&verification, &caps).is_ok());
let mut bad_verification = verification.clone();
bad_verification.user_verification = false;
assert!(policy.check_compliance(&bad_verification, &caps).is_err());
}
#[test]
fn test_risk_scoring() {
let storage = InMemoryBiometricStorage::new();
let manager = BiometricSecurityManager::new(storage);
let verification = BiometricVerification::new(
UserId::new("test_user"),
TenantId::new("test_tenant"),
"test".to_string(),
Some(BiometricModality::FaceRecognition),
false, false, 300,
)
.with_quality(30);
let caps = AuthenticatorCapabilities::new("test".to_string());
let risk = manager.calculate_risk_score(&verification, &caps);
assert_eq!(risk, 100); }
}