use security_core::severity::SecuritySeverity;
use security_events::event::{EventOutcome, SecurityEvent};
use security_events::kind::EventKind;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BiometricClass {
Class1 = 1,
Class2 = 2,
Class3 = 3,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CryptoBinding {
pub key_id: String,
pub enrollment_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BiometricAuthResult {
pub biometric_class: BiometricClass,
pub crypto_binding: Option<CryptoBinding>,
pub device_credential_fallback: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BiometricRejection {
WeakBiometric,
NoCryptoBinding,
EnrollmentChanged,
DeviceCredentialNotAllowed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BiometricValidation {
Accepted,
Rejected(BiometricRejection),
}
#[derive(Clone, Debug)]
pub struct BiometricPolicy {
pub minimum_class: BiometricClass,
pub require_crypto_binding: bool,
pub allow_device_credential: bool,
}
impl Default for BiometricPolicy {
fn default() -> Self {
Self {
minimum_class: BiometricClass::Class3,
require_crypto_binding: true,
allow_device_credential: false,
}
}
}
impl BiometricPolicy {
#[must_use]
pub fn validate(
&self,
result: &BiometricAuthResult,
current_enrollment_id: Option<&str>,
) -> BiometricValidation {
if result.device_credential_fallback && !self.allow_device_credential {
return BiometricValidation::Rejected(BiometricRejection::DeviceCredentialNotAllowed);
}
if result.biometric_class < self.minimum_class {
return BiometricValidation::Rejected(BiometricRejection::WeakBiometric);
}
if self.require_crypto_binding {
match &result.crypto_binding {
None => {
return BiometricValidation::Rejected(BiometricRejection::NoCryptoBinding);
}
Some(binding) => {
if let Some(current) = current_enrollment_id {
if binding.enrollment_id != current {
return BiometricValidation::Rejected(
BiometricRejection::EnrollmentChanged,
);
}
}
}
}
}
BiometricValidation::Accepted
}
#[must_use]
pub fn validate_with_events(
&self,
result: &BiometricAuthResult,
current_enrollment_id: Option<&str>,
) -> Vec<SecurityEvent> {
let validation = self.validate(result, current_enrollment_id);
match validation {
BiometricValidation::Accepted => vec![],
BiometricValidation::Rejected(reason) => {
let severity = match reason {
BiometricRejection::WeakBiometric => SecuritySeverity::High,
BiometricRejection::NoCryptoBinding => SecuritySeverity::High,
BiometricRejection::EnrollmentChanged => SecuritySeverity::Critical,
BiometricRejection::DeviceCredentialNotAllowed => SecuritySeverity::Medium,
};
let mut event = SecurityEvent::new(
EventKind::BiometricAuthFailure,
severity,
EventOutcome::Blocked,
);
event.reason_code = Some(match reason {
BiometricRejection::WeakBiometric => "weak_biometric",
BiometricRejection::NoCryptoBinding => "no_crypto_binding",
BiometricRejection::EnrollmentChanged => "enrollment_changed",
BiometricRejection::DeviceCredentialNotAllowed => {
"device_credential_not_allowed"
}
});
vec![event]
}
}
}
}