#![deny(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]
use crate::types::traits::{
ContinuousVerifiable, ProofOfPossession, VerificationStatus, ZeroTrustAuthenticable,
};
use crate::unified_api::{
ProofComplexity, ZeroTrustConfig,
error::{CoreError, Result},
logging::session_id_to_hex,
};
use crate::{
log_zero_trust_auth_failure, log_zero_trust_auth_success, log_zero_trust_challenge_generated,
log_zero_trust_proof_verified, log_zero_trust_session_created, log_zero_trust_session_expired,
log_zero_trust_session_verification_failed, log_zero_trust_session_verified,
log_zero_trust_unverified_mode,
types::{PrivateKey, PublicKey},
};
pub use crate::types::zero_trust::TrustLevel;
use chrono::{DateTime, Duration, Utc};
use std::cell::RefCell;
use subtle::ConstantTimeEq;
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub enum SecurityMode<'a> {
Verified(&'a VerifiedSession),
Unverified,
}
impl<'a> SecurityMode<'a> {
#[must_use]
pub fn is_verified(&self) -> bool {
matches!(self, Self::Verified(_))
}
#[must_use]
pub fn is_unverified(&self) -> bool {
matches!(self, Self::Unverified)
}
#[must_use]
pub fn session(&self) -> Option<&'a VerifiedSession> {
match self {
Self::Verified(session) => Some(session),
Self::Unverified => None,
}
}
pub fn validate(&self) -> Result<()> {
match self {
Self::Verified(session) => {
let result = session.verify_valid();
if result.is_ok() {
log_zero_trust_session_verified!(session_id_to_hex(session.session_id()));
} else {
log_zero_trust_session_expired!(session_id_to_hex(session.session_id()));
}
result
}
Self::Unverified => {
log_zero_trust_unverified_mode!("validate");
Ok(())
}
}
}
}
impl<'a> From<&'a VerifiedSession> for SecurityMode<'a> {
fn from(session: &'a VerifiedSession) -> Self {
Self::Verified(session)
}
}
impl Default for SecurityMode<'_> {
fn default() -> Self {
Self::Unverified
}
}
pub struct VerifiedSession {
session_id: [u8; 32],
authenticated_at: DateTime<Utc>,
trust_level: TrustLevel,
public_key: PublicKey,
expires_at: DateTime<Utc>,
}
impl std::fmt::Debug for VerifiedSession {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VerifiedSession")
.field("session_id", &"[REDACTED]")
.field("authenticated_at", &self.authenticated_at)
.field("trust_level", &self.trust_level)
.field("public_key", &"[REDACTED]")
.field("expires_at", &self.expires_at)
.finish()
}
}
const DEFAULT_SESSION_LIFETIME_SECS: i64 = 30 * 60;
impl VerifiedSession {
pub fn establish(public_key: &[u8], private_key: &[u8]) -> Result<Self> {
let pk: PublicKey = PublicKey::new(public_key.to_vec());
let sk: PrivateKey = PrivateKey::new(private_key.to_vec());
let auth = ZeroTrustAuth::new(pk, sk)?;
let mut session = ZeroTrustSession::new(auth);
let challenge = session.initiate_authentication()?;
let proof = session.auth.generate_proof(challenge.data())?;
session.verify_response(&proof)?;
session.into_verified()
}
pub(crate) fn from_authenticated(session: &ZeroTrustSession) -> Result<Self> {
if !session.is_authenticated() {
log_zero_trust_auth_failure!(
"pending",
"Session must be authenticated before creating VerifiedSession"
);
return Err(CoreError::AuthenticationRequired(
"Session must be authenticated before creating VerifiedSession".to_string(),
));
}
let session_id_vec = crate::primitives::security::generate_secure_random_bytes(32)
.map_err(|_e| CoreError::EntropyDepleted {
message: "Failed to generate session ID".to_string(),
action: "Check system entropy source".to_string(),
})?;
let mut session_id = [0u8; 32];
session_id.copy_from_slice(&session_id_vec);
let now = Utc::now();
let expires_at = now
.checked_add_signed(Duration::seconds(DEFAULT_SESSION_LIFETIME_SECS))
.ok_or_else(|| {
CoreError::ConfigurationError(
"Cannot compute session expiry: timestamp overflow".to_string(),
)
})?;
let trust_level = TrustLevel::Trusted;
let session_id_hex = session_id_to_hex(&session_id);
log_zero_trust_session_created!(session_id_hex, trust_level, expires_at);
log_zero_trust_auth_success!(session_id_hex, trust_level);
Ok(Self {
session_id,
authenticated_at: now,
trust_level,
public_key: session.auth.public_key.clone(),
expires_at,
})
}
#[must_use]
pub fn is_valid(&self) -> bool {
Utc::now() < self.expires_at
}
#[must_use]
pub fn trust_level(&self) -> TrustLevel {
self.trust_level
}
#[must_use]
pub fn session_id(&self) -> &[u8; 32] {
&self.session_id
}
#[must_use]
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
#[must_use]
pub fn authenticated_at(&self) -> DateTime<Utc> {
self.authenticated_at
}
#[must_use]
pub fn expires_at(&self) -> DateTime<Utc> {
self.expires_at
}
pub fn verify_valid(&self) -> Result<()> {
let session_id_hex = session_id_to_hex(&self.session_id);
if self.is_valid() {
log_zero_trust_session_verified!(session_id_hex);
Ok(())
} else {
log_zero_trust_session_expired!(session_id_hex);
Err(CoreError::SessionExpired)
}
}
#[cfg(test)]
#[must_use]
pub(crate) fn expired_clone(&self) -> Self {
Self {
session_id: self.session_id,
authenticated_at: self.authenticated_at,
trust_level: self.trust_level,
public_key: self.public_key.clone(),
expires_at: DateTime::<Utc>::from_timestamp(0, 0).unwrap_or_else(Utc::now),
}
}
}
pub struct ZeroTrustAuth {
pub(crate) public_key: PublicKey,
private_key: PrivateKey,
config: ZeroTrustConfig,
session_start: DateTime<Utc>,
last_verification: RefCell<DateTime<Utc>>,
}
impl ZeroTrustAuth {
pub fn new(public_key: PublicKey, private_key: PrivateKey) -> Result<Self> {
let config = ZeroTrustConfig::default();
config.validate()?;
let now = Utc::now();
Ok(Self {
public_key,
private_key,
config,
session_start: now,
last_verification: RefCell::new(now),
})
}
pub fn with_config(
public_key: PublicKey,
private_key: PrivateKey,
config: ZeroTrustConfig,
) -> Result<Self> {
config.validate()?;
let now = Utc::now();
Ok(Self {
public_key,
private_key,
config,
session_start: now,
last_verification: RefCell::new(now),
})
}
pub fn generate_challenge(&self) -> Result<Challenge> {
let challenge_data = generate_challenge_data(&self.config.proof_complexity)?;
log_zero_trust_challenge_generated!(self.config.proof_complexity);
Ok(Challenge {
data: challenge_data,
timestamp: Utc::now(),
complexity: self.config.proof_complexity.clone(),
timeout_ms: self.config.challenge_timeout_ms,
})
}
pub fn verify_challenge_age(&self, challenge: &Challenge) -> Result<bool> {
let elapsed = Utc::now().signed_duration_since(challenge.timestamp());
let elapsed_ms = elapsed.num_milliseconds();
let elapsed_u64 = u64::try_from(elapsed_ms).unwrap_or(u64::MAX);
Ok(elapsed_u64 <= challenge.timeout_ms())
}
#[must_use]
pub fn start_continuous_verification(&self) -> ContinuousSession {
ContinuousSession {
auth_public_key: self.public_key.clone(),
start_time: Utc::now(),
verification_interval_ms: self.config.verification_interval_ms,
last_verification: Utc::now(),
}
}
}
impl ZeroTrustAuthenticable for ZeroTrustAuth {
type Proof = ZeroKnowledgeProof;
type Error = CoreError;
fn generate_proof(&self, challenge: &[u8]) -> Result<Self::Proof> {
if challenge.is_empty() {
return Err(CoreError::AuthenticationFailed("Empty challenge".to_string()));
}
let proof_data = self.compute_proof_data(challenge)?;
let timestamp = Utc::now();
Ok(ZeroKnowledgeProof {
challenge: challenge.to_vec(),
proof: proof_data,
timestamp,
complexity: self.config.proof_complexity.clone(),
})
}
fn verify_proof(
&self,
proof: &Self::Proof,
challenge: &[u8],
) -> std::result::Result<bool, Self::Error> {
let len_eq = proof.challenge().len().ct_eq(&challenge.len());
let content_eq = proof.challenge().ct_eq(challenge);
let challenge_matches: bool = (len_eq & content_eq).into();
if !challenge_matches {
log_zero_trust_proof_verified!(false);
return Ok(false);
}
let result = self.verify_proof_data(proof.proof_data(), challenge)?;
log_zero_trust_proof_verified!(result);
Ok(result)
}
}
impl ProofOfPossession for ZeroTrustAuth {
type Pop = ProofOfPossessionData;
type Error = CoreError;
fn generate_pop(&self) -> Result<Self::Pop> {
let timestamp = Utc::now();
let ts_secs = u64::try_from(timestamp.timestamp()).unwrap_or(0);
let message = format!("proof-of-possession-{}", ts_secs);
let signature = crate::unified_api::convenience::ed25519::sign_ed25519_internal(
message.as_bytes(),
self.private_key.as_slice(),
)?;
Ok(ProofOfPossessionData { public_key: self.public_key.clone(), signature, timestamp })
}
fn verify_pop(&self, pop: &Self::Pop) -> std::result::Result<bool, Self::Error> {
const PROOF_OF_POSSESSION_MAX_AGE_SECS: i64 = 5 * 60; let elapsed = Utc::now().signed_duration_since(pop.timestamp());
if elapsed.num_seconds() > PROOF_OF_POSSESSION_MAX_AGE_SECS {
return Err(CoreError::InvalidInput(format!(
"Proof-of-possession is stale: {}s > {}s",
elapsed.num_seconds(),
PROOF_OF_POSSESSION_MAX_AGE_SECS
)));
}
if elapsed.num_seconds() < -30 {
return Err(CoreError::InvalidInput(
"Proof-of-possession timestamp is in the future".to_string(),
));
}
let ts_secs = u64::try_from(pop.timestamp().timestamp()).unwrap_or(0);
let message = format!("proof-of-possession-{}", ts_secs);
crate::unified_api::convenience::ed25519::verify_ed25519_internal(
message.as_bytes(),
pop.signature(),
pop.public_key().as_slice(),
)
}
}
impl ContinuousVerifiable for ZeroTrustAuth {
type Error = CoreError;
fn verify_continuously(&self) -> Result<VerificationStatus> {
let session_elapsed = Utc::now().signed_duration_since(self.session_start);
let max_session_time: u64 = 30 * 60 * 1000;
let session_elapsed_u64 = u64::try_from(session_elapsed.num_milliseconds()).unwrap_or(0);
if session_elapsed_u64 > max_session_time {
return Ok(VerificationStatus::Expired);
}
if !self.config.continuous_verification {
return Ok(VerificationStatus::Verified);
}
let verification_elapsed =
Utc::now().signed_duration_since(*self.last_verification.borrow());
let verification_elapsed_u64 =
u64::try_from(verification_elapsed.num_milliseconds()).unwrap_or(0);
if verification_elapsed_u64 > self.config.verification_interval_ms {
return Ok(VerificationStatus::Pending);
}
Ok(VerificationStatus::Verified)
}
fn reauthenticate(&self) -> Result<()> {
let challenge = self.generate_challenge()?;
let _proof = self.generate_proof(challenge.data())?;
*self.last_verification.borrow_mut() = Utc::now();
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Challenge {
data: Vec<u8>,
timestamp: DateTime<Utc>,
complexity: ProofComplexity,
timeout_ms: u64,
}
impl Challenge {
#[must_use]
pub fn data(&self) -> &[u8] {
&self.data
}
#[must_use]
pub fn timestamp(&self) -> DateTime<Utc> {
self.timestamp
}
#[must_use]
pub fn complexity(&self) -> &ProofComplexity {
&self.complexity
}
#[must_use]
pub fn timeout_ms(&self) -> u64 {
self.timeout_ms
}
#[must_use]
pub fn is_expired(&self) -> bool {
let elapsed = Utc::now().signed_duration_since(self.timestamp);
let elapsed_u64 = u64::try_from(elapsed.num_milliseconds()).unwrap_or(0);
elapsed_u64 > self.timeout_ms
}
}
#[derive(Debug, Clone)]
pub struct ZeroKnowledgeProof {
challenge: Vec<u8>,
proof: Vec<u8>,
timestamp: DateTime<Utc>,
complexity: ProofComplexity,
}
impl ZeroKnowledgeProof {
#[must_use]
pub fn new(
challenge: Vec<u8>,
proof: Vec<u8>,
timestamp: DateTime<Utc>,
complexity: ProofComplexity,
) -> Self {
Self { challenge, proof, timestamp, complexity }
}
#[must_use]
pub fn challenge(&self) -> &[u8] {
&self.challenge
}
#[must_use]
pub fn proof_data(&self) -> &[u8] {
&self.proof
}
#[must_use]
pub fn proof_data_mut(&mut self) -> &mut Vec<u8> {
&mut self.proof
}
#[must_use]
pub fn timestamp(&self) -> DateTime<Utc> {
self.timestamp
}
#[must_use]
pub fn complexity(&self) -> &ProofComplexity {
&self.complexity
}
#[must_use]
pub fn is_valid_format(&self) -> bool {
!self.challenge.is_empty() && !self.proof.is_empty() && self.timestamp <= Utc::now()
}
}
#[derive(Debug, Clone)]
pub struct ProofOfPossessionData {
public_key: PublicKey,
signature: Vec<u8>,
timestamp: DateTime<Utc>,
}
impl ProofOfPossessionData {
#[must_use]
pub fn new(public_key: PublicKey, signature: Vec<u8>, timestamp: DateTime<Utc>) -> Self {
Self { public_key, signature, timestamp }
}
#[must_use]
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
#[must_use]
pub fn signature_mut(&mut self) -> &mut Vec<u8> {
&mut self.signature
}
#[must_use]
pub fn signature(&self) -> &[u8] {
&self.signature
}
#[must_use]
pub fn timestamp(&self) -> DateTime<Utc> {
self.timestamp
}
}
#[derive(Debug)]
pub struct ContinuousSession {
auth_public_key: PublicKey,
start_time: DateTime<Utc>,
verification_interval_ms: u64,
last_verification: DateTime<Utc>,
}
impl ContinuousSession {
#[must_use]
pub fn auth_public_key(&self) -> &PublicKey {
&self.auth_public_key
}
pub fn is_valid(&self) -> Result<bool> {
let elapsed = Utc::now().signed_duration_since(self.start_time);
let max_duration: u64 = 60 * 60 * 1000;
let elapsed_u64 = u64::try_from(elapsed.num_milliseconds()).unwrap_or(0);
if elapsed_u64 > max_duration {
return Ok(false);
}
let verification_elapsed = Utc::now().signed_duration_since(self.last_verification);
let verification_elapsed_u64 =
u64::try_from(verification_elapsed.num_milliseconds()).unwrap_or(0);
Ok(verification_elapsed_u64 <= self.verification_interval_ms)
}
pub fn update_verification(&mut self) -> Result<()> {
self.last_verification = Utc::now();
Ok(())
}
}
fn generate_challenge_data(complexity: &ProofComplexity) -> Result<Vec<u8>> {
let size = match complexity {
ProofComplexity::Low => 32,
ProofComplexity::Medium => 64,
ProofComplexity::High => 128,
};
let data = crate::primitives::security::generate_secure_random_bytes(size).map_err(|e| {
CoreError::EntropyDepleted {
message: format!("Failed to generate challenge: {e}"),
action: "Check system entropy source".to_string(),
}
})?;
Ok(data)
}
impl ZeroTrustAuth {
fn compute_proof_data(&self, challenge: &[u8]) -> Result<Vec<u8>> {
let timestamp = Utc::now().timestamp_millis().to_le_bytes();
let message_to_sign = match self.config.proof_complexity {
ProofComplexity::Low => {
challenge.to_vec()
}
ProofComplexity::Medium => {
let mut msg = challenge.to_vec();
msg.extend_from_slice(×tamp);
msg
}
ProofComplexity::High => {
let mut msg = challenge.to_vec();
msg.extend_from_slice(×tamp);
msg.extend_from_slice(self.public_key.as_slice());
msg
}
};
let signature = crate::unified_api::convenience::ed25519::sign_ed25519_internal(
&message_to_sign,
self.private_key.as_slice(),
)?;
match self.config.proof_complexity {
ProofComplexity::Low => Ok(signature),
ProofComplexity::Medium | ProofComplexity::High => {
let mut proof = signature;
proof.extend_from_slice(×tamp);
Ok(proof)
}
}
}
fn verify_proof_data(&self, proof: &[u8], challenge: &[u8]) -> Result<bool> {
if proof.len() < 64 {
return Ok(false);
}
match self.config.proof_complexity {
ProofComplexity::Low => {
crate::unified_api::convenience::ed25519::verify_ed25519_internal(
challenge,
proof,
self.public_key.as_slice(),
)
}
ProofComplexity::Medium => {
if proof.len() < 72 {
return Ok(false);
}
let signature = proof.get(..64).ok_or_else(|| {
CoreError::AuthenticationFailed("Invalid proof format".to_string())
})?;
let timestamp_slice = proof.get(64..72).ok_or_else(|| {
CoreError::AuthenticationFailed("Invalid proof format".to_string())
})?;
let timestamp_bytes: [u8; 8] = timestamp_slice.try_into().map_err(|_e| {
CoreError::AuthenticationFailed("Invalid proof format".to_string())
})?;
let proof_ts_ms = i64::from_le_bytes(timestamp_bytes);
let now_ms = Utc::now().timestamp_millis();
let drift_ms = now_ms.abs_diff(proof_ts_ms);
if drift_ms > 300_000 {
tracing::warn!(drift_ms, "proof timestamp outside 5-min freshness window");
return Ok(false);
}
let mut message = challenge.to_vec();
message.extend_from_slice(×tamp_bytes);
crate::unified_api::convenience::ed25519::verify_ed25519_internal(
&message,
signature,
self.public_key.as_slice(),
)
}
ProofComplexity::High => {
if proof.len() < 72 {
return Ok(false);
}
let signature = proof.get(..64).ok_or_else(|| {
CoreError::AuthenticationFailed("Invalid proof format".to_string())
})?;
let timestamp_slice = proof.get(64..72).ok_or_else(|| {
CoreError::AuthenticationFailed("Invalid proof format".to_string())
})?;
let timestamp_bytes: [u8; 8] = timestamp_slice.try_into().map_err(|_e| {
CoreError::AuthenticationFailed("Invalid proof format".to_string())
})?;
let proof_ts_ms = i64::from_le_bytes(timestamp_bytes);
let now_ms = Utc::now().timestamp_millis();
let drift_ms = now_ms.abs_diff(proof_ts_ms);
if drift_ms > 300_000 {
tracing::warn!(drift_ms, "proof timestamp outside 5-min freshness window");
return Ok(false);
}
let mut message = challenge.to_vec();
message.extend_from_slice(×tamp_bytes);
message.extend_from_slice(self.public_key.as_slice());
crate::unified_api::convenience::ed25519::verify_ed25519_internal(
&message,
signature,
self.public_key.as_slice(),
)
}
}
}
}
pub struct ZeroTrustSession {
pub(crate) auth: ZeroTrustAuth,
challenge: Option<Challenge>,
verified: bool,
session_start: DateTime<Utc>,
}
impl ZeroTrustSession {
#[must_use]
pub fn new(auth: ZeroTrustAuth) -> Self {
Self { auth, challenge: None, verified: false, session_start: Utc::now() }
}
pub fn initiate_authentication(&mut self) -> Result<Challenge> {
let challenge = self.auth.generate_challenge()?;
self.challenge = Some(challenge.clone());
Ok(challenge)
}
pub fn verify_response(&mut self, proof: &ZeroKnowledgeProof) -> Result<bool> {
let challenge = self.challenge.as_ref().ok_or_else(|| {
log_zero_trust_session_verification_failed!("pending", "No active challenge");
CoreError::AuthenticationFailed("No active challenge".to_string())
})?;
if challenge.is_expired() {
log_zero_trust_session_verification_failed!("pending", "Challenge expired");
return Err(CoreError::AuthenticationFailed("Challenge expired".to_string()));
}
let verified = self.auth.verify_proof(proof, challenge.data())?;
self.verified = verified;
if !verified {
log_zero_trust_session_verification_failed!("pending", "Proof verification failed");
}
Ok(self.verified)
}
pub fn generate_proof(&self, challenge: &Challenge) -> Result<ZeroKnowledgeProof> {
self.auth.generate_proof(challenge.data())
}
#[must_use]
pub fn is_authenticated(&self) -> bool {
self.verified
}
pub fn session_age_ms(&self) -> Result<u64> {
let elapsed = Utc::now().signed_duration_since(self.session_start);
let elapsed_u64 = u64::try_from(elapsed.num_milliseconds()).unwrap_or(0);
Ok(elapsed_u64)
}
pub fn into_verified(self) -> Result<VerifiedSession> {
VerifiedSession::from_authenticated(&self)
}
}
#[cfg(test)]
#[allow(
clippy::panic,
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::panic_in_result_fn,
clippy::unnecessary_wraps,
clippy::redundant_clone,
clippy::useless_vec,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::clone_on_copy,
clippy::len_zero,
clippy::single_match,
clippy::unnested_or_patterns,
clippy::default_constructed_unit_structs,
clippy::redundant_closure_for_method_calls,
clippy::semicolon_if_nothing_returned,
clippy::unnecessary_unwrap,
clippy::redundant_pattern_matching,
clippy::missing_const_for_thread_local,
clippy::get_first,
clippy::float_cmp,
clippy::needless_borrows_for_generic_args,
unused_qualifications
)]
mod tests {
use super::*;
use crate::unified_api::generate_keypair;
#[test]
fn test_trust_level_default_has_correct_value_succeeds() {
let level = TrustLevel::default();
assert_eq!(level, TrustLevel::Untrusted);
}
#[test]
fn test_trust_level_variants_are_correct() {
assert_eq!(TrustLevel::Untrusted as i32, 0);
assert_eq!(TrustLevel::Partial as i32, 1);
assert_eq!(TrustLevel::Trusted as i32, 2);
assert_eq!(TrustLevel::FullyTrusted as i32, 3);
}
#[test]
fn test_trust_level_is_trusted_succeeds() {
assert!(!TrustLevel::Untrusted.is_trusted());
assert!(TrustLevel::Partial.is_trusted());
assert!(TrustLevel::Trusted.is_trusted());
assert!(TrustLevel::FullyTrusted.is_trusted());
}
#[test]
fn test_trust_level_is_fully_trusted_succeeds() {
assert!(!TrustLevel::Untrusted.is_fully_trusted());
assert!(!TrustLevel::Partial.is_fully_trusted());
assert!(!TrustLevel::Trusted.is_fully_trusted());
assert!(TrustLevel::FullyTrusted.is_fully_trusted());
}
#[test]
fn test_trust_level_ordering_is_correct() {
assert!(TrustLevel::Untrusted < TrustLevel::Partial);
assert!(TrustLevel::Partial < TrustLevel::Trusted);
assert!(TrustLevel::Trusted < TrustLevel::FullyTrusted);
}
#[test]
fn test_security_mode_unverified_is_unverified_succeeds() {
let mode = SecurityMode::Unverified;
assert!(mode.is_unverified());
assert!(!mode.is_verified());
}
#[test]
fn test_security_mode_validate_unverified_succeeds() -> Result<()> {
let mode = SecurityMode::Unverified;
mode.validate()?;
Ok(())
}
#[test]
fn test_verified_session_establish_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
assert!(session.is_valid());
assert!(session.trust_level() >= TrustLevel::Partial);
Ok(())
}
#[test]
fn test_verified_session_session_id_is_accessible() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let session_id = session.session_id();
assert_eq!(session_id.len(), 32);
Ok(())
}
#[test]
fn test_verified_session_public_key_is_accessible() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let pk = session.public_key();
assert!(!pk.is_empty());
Ok(())
}
#[test]
fn test_verified_session_timestamps_are_accessible() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let authenticated_at = session.authenticated_at();
let expires_at = session.expires_at();
assert!(expires_at > authenticated_at, "Session should expire after authentication");
Ok(())
}
#[test]
fn test_verified_session_verify_valid_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
session.verify_valid()?;
Ok(())
}
#[test]
fn test_security_mode_verified_with_session_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let mode = SecurityMode::Verified(&session);
assert!(mode.is_verified());
assert!(!mode.is_unverified());
Ok(())
}
#[test]
fn test_security_mode_verified_validate_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let mode = SecurityMode::Verified(&session);
mode.validate()?;
Ok(())
}
#[test]
fn test_security_mode_verified_session_is_accessible() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let mode = SecurityMode::Verified(&session);
assert!(mode.session().is_some());
Ok(())
}
#[test]
fn test_security_mode_unverified_session_returns_none_succeeds() {
let mode = SecurityMode::Unverified;
assert!(mode.session().is_none());
}
#[test]
fn test_zero_trust_auth_new_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
assert!(std::mem::size_of_val(&auth) > 0);
Ok(())
}
#[test]
fn test_zero_trust_auth_with_config_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config =
ZeroTrustConfig::new().with_timeout(10000).with_complexity(ProofComplexity::High);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
assert!(std::mem::size_of_val(&auth) > 0);
Ok(())
}
#[test]
fn test_zero_trust_auth_generate_challenge_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let challenge = auth.generate_challenge()?;
assert!(!challenge.is_expired());
Ok(())
}
#[test]
fn test_zero_trust_auth_multiple_challenges_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let challenge1 = auth.generate_challenge()?;
let challenge2 = auth.generate_challenge()?;
assert!(!challenge1.is_expired());
assert!(!challenge2.is_expired());
Ok(())
}
#[test]
fn test_zero_trust_auth_verify_challenge_age_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let challenge = auth.generate_challenge()?;
let is_valid = auth.verify_challenge_age(&challenge)?;
assert!(is_valid, "Freshly generated challenge should be valid");
Ok(())
}
#[test]
fn test_zero_trust_auth_start_continuous_verification_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let continuous = auth.start_continuous_verification();
let result = continuous.is_valid();
assert!(result.is_ok());
Ok(())
}
#[test]
fn test_challenge_is_not_expired_when_fresh_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let challenge = auth.generate_challenge()?;
assert!(!challenge.is_expired());
Ok(())
}
#[test]
fn test_zero_trust_session_new_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let session = ZeroTrustSession::new(auth);
assert!(!session.is_authenticated());
Ok(())
}
#[test]
fn test_zero_trust_session_initiate_authentication_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let mut session = ZeroTrustSession::new(auth);
let challenge = session.initiate_authentication()?;
assert!(!challenge.is_expired());
Ok(())
}
#[test]
fn test_zero_trust_session_is_not_authenticated_initially_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let session = ZeroTrustSession::new(auth);
assert!(!session.is_authenticated());
Ok(())
}
#[test]
fn test_proof_complexity_variants_are_correct() {
let _low = ProofComplexity::Low;
let _medium = ProofComplexity::Medium;
let _high = ProofComplexity::High;
assert_eq!(ProofComplexity::Medium, ProofComplexity::Medium);
}
#[test]
fn test_verified_session_with_multiple_instances_succeeds() -> Result<()> {
for _ in 0..3 {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
assert!(session.is_valid());
}
Ok(())
}
#[test]
fn test_zero_trust_config_variations_all_succeed_succeeds() -> Result<()> {
let configs = vec![
ZeroTrustConfig::new().with_timeout(5000),
ZeroTrustConfig::new().with_complexity(ProofComplexity::Low),
ZeroTrustConfig::new().with_complexity(ProofComplexity::High),
ZeroTrustConfig::new().with_continuous_verification(true),
ZeroTrustConfig::new().with_verification_interval(60000),
];
for config in configs {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let _challenge = auth.generate_challenge()?;
}
Ok(())
}
#[test]
fn test_trust_level_progression_is_correct() {
let levels = vec![
TrustLevel::Untrusted,
TrustLevel::Partial,
TrustLevel::Trusted,
TrustLevel::FullyTrusted,
];
for (i, level) in levels.iter().enumerate() {
assert_eq!(*level as usize, i);
}
}
#[test]
fn test_verified_session_multiple_sessions_all_succeed_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session1 = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let session2 = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
assert!(session1.is_valid());
assert!(session2.is_valid());
assert_ne!(session1.session_id(), session2.session_id());
Ok(())
}
#[test]
fn test_challenge_generation_produces_unique_challenges_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let mut challenges = Vec::new();
for _ in 0..5 {
challenges.push(auth.generate_challenge()?);
}
for challenge in &challenges {
assert!(!challenge.is_expired());
}
Ok(())
}
#[test]
fn test_continuous_session_validation_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_continuous_verification(true);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let continuous = auth.start_continuous_verification();
assert!(continuous.is_valid().is_ok());
Ok(())
}
#[test]
fn test_zero_trust_auth_with_all_complexity_levels_succeeds() -> Result<()> {
let complexities =
vec![ProofComplexity::Low, ProofComplexity::Medium, ProofComplexity::High];
for complexity in complexities {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_complexity(complexity);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge = auth.generate_challenge()?;
assert!(!challenge.is_expired());
}
Ok(())
}
#[test]
fn test_security_mode_default_is_unverified_succeeds() {
let mode = SecurityMode::default();
assert!(mode.is_unverified());
assert!(!mode.is_verified());
assert!(mode.session().is_none());
}
#[test]
fn test_security_mode_from_verified_session_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let mode: SecurityMode = SecurityMode::from(&session);
assert!(mode.is_verified());
assert!(mode.session().is_some());
Ok(())
}
#[test]
fn test_verified_session_from_unauthenticated_fails() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let session = ZeroTrustSession::new(auth);
assert!(!session.is_authenticated());
let result = session.into_verified();
assert!(result.is_err());
Ok(())
}
#[test]
fn test_zero_trust_session_verify_response_returns_error_without_challenge_fails() -> Result<()>
{
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let mut session = ZeroTrustSession::new(auth);
let fake_proof =
ZeroKnowledgeProof::new(vec![1, 2, 3], vec![0u8; 64], Utc::now(), ProofComplexity::Low);
let result = session.verify_response(&fake_proof);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_zero_trust_session_session_age_ms_is_accessible() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let session = ZeroTrustSession::new(auth);
let age = session.session_age_ms()?;
assert!(age < 5000, "Session age should be < 5 seconds, got {}ms", age);
Ok(())
}
#[test]
fn test_continuous_session_auth_public_key_is_accessible() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key.clone(), private_key)?;
let continuous = auth.start_continuous_verification();
assert_eq!(continuous.auth_public_key(), &public_key);
Ok(())
}
#[test]
fn test_continuous_session_update_verification_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let mut continuous = auth.start_continuous_verification();
assert!(continuous.is_valid()?);
continuous.update_verification()?;
assert!(continuous.is_valid()?);
Ok(())
}
#[test]
fn test_zero_knowledge_proof_has_valid_format_has_correct_size() {
let valid =
ZeroKnowledgeProof::new(vec![1, 2, 3], vec![0u8; 64], Utc::now(), ProofComplexity::Low);
assert!(valid.is_valid_format());
let empty_challenge =
ZeroKnowledgeProof::new(vec![], vec![0u8; 64], Utc::now(), ProofComplexity::Low);
assert!(!empty_challenge.is_valid_format());
let empty_proof =
ZeroKnowledgeProof::new(vec![1], vec![], Utc::now(), ProofComplexity::Low);
assert!(!empty_proof.is_valid_format());
}
#[test]
fn test_zero_trust_auth_reauthenticate_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
auth.reauthenticate()?;
Ok(())
}
#[test]
fn test_zero_trust_auth_verify_continuously_verified_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let status = auth.verify_continuously()?;
assert_eq!(status, VerificationStatus::Verified);
Ok(())
}
#[test]
fn test_zero_trust_auth_verify_continuously_with_cv_enabled_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new()
.with_continuous_verification(true)
.with_verification_interval(60000);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let status = auth.verify_continuously()?;
assert_eq!(status, VerificationStatus::Verified);
Ok(())
}
#[test]
fn test_generate_proof_returns_error_for_empty_challenge_fails() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let result = auth.generate_proof(&[]);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_verify_proof_returns_error_for_wrong_challenge_fails() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let challenge = auth.generate_challenge()?;
let proof = auth.generate_proof(challenge.data())?;
let different_challenge = vec![0xFF; 32];
let result = auth.verify_proof(&proof, &different_challenge)?;
assert!(!result);
Ok(())
}
#[test]
fn test_verify_proof_returns_error_for_short_proof_data_fails() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let challenge_data = vec![1u8; 32];
let short_proof = ZeroKnowledgeProof::new(
challenge_data.clone(),
vec![0u8; 10], Utc::now(),
ProofComplexity::Low,
);
let result = auth.verify_proof(&short_proof, &challenge_data)?;
assert!(!result);
Ok(())
}
#[test]
fn test_full_challenge_response_low_complexity_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_complexity(ProofComplexity::Low);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge = auth.generate_challenge()?;
let proof = auth.generate_proof(challenge.data())?;
let verified = auth.verify_proof(&proof, challenge.data())?;
assert!(verified);
Ok(())
}
#[test]
fn test_full_challenge_response_medium_complexity_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_complexity(ProofComplexity::Medium);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge = auth.generate_challenge()?;
let proof = auth.generate_proof(challenge.data())?;
let verified = auth.verify_proof(&proof, challenge.data())?;
assert!(verified);
Ok(())
}
#[test]
fn test_full_challenge_response_high_complexity_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_complexity(ProofComplexity::High);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge = auth.generate_challenge()?;
let proof = auth.generate_proof(challenge.data())?;
let verified = auth.verify_proof(&proof, challenge.data())?;
assert!(verified);
Ok(())
}
#[test]
fn test_proof_of_possession_roundtrip() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let pop = auth.generate_pop()?;
let verified = auth.verify_pop(&pop)?;
assert!(verified);
Ok(())
}
#[test]
fn test_full_session_flow_with_into_verified_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let auth = ZeroTrustAuth::new(public_key, private_key)?;
let mut session = ZeroTrustSession::new(auth);
let challenge = session.initiate_authentication()?;
let proof = session.auth.generate_proof(challenge.data())?;
let verified = session.verify_response(&proof)?;
assert!(verified);
assert!(session.is_authenticated());
let verified_session = session.into_verified()?;
assert!(verified_session.is_valid());
assert_eq!(verified_session.trust_level(), TrustLevel::Trusted);
Ok(())
}
#[test]
fn test_verified_session_debug_has_correct_format() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let debug = format!("{:?}", session);
assert!(debug.contains("VerifiedSession"));
Ok(())
}
#[test]
fn test_security_mode_debug_has_correct_format() -> Result<()> {
let mode = SecurityMode::Unverified;
let debug = format!("{:?}", mode);
assert!(debug.contains("Unverified"));
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let verified_mode = SecurityMode::Verified(&session);
let debug2 = format!("{:?}", verified_mode);
assert!(debug2.contains("Verified"));
Ok(())
}
#[test]
fn test_challenge_fields_are_accessible() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config =
ZeroTrustConfig::new().with_timeout(5000).with_complexity(ProofComplexity::High);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge = auth.generate_challenge()?;
assert_eq!(challenge.data().len(), 128); assert_eq!(challenge.timeout_ms(), 5000);
assert!(!challenge.is_expired());
let debug = format!("{:?}", challenge);
assert!(debug.contains("Challenge"));
Ok(())
}
#[test]
fn test_verified_session_expired_verify_valid_fails() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let expired_session = VerifiedSession {
session_id: *session.session_id(),
authenticated_at: session.authenticated_at(),
trust_level: session.trust_level(),
public_key: session.public_key().clone(),
expires_at: Utc::now() - Duration::seconds(1),
};
assert!(!expired_session.is_valid());
let result = expired_session.verify_valid();
assert!(result.is_err(), "Expired session should fail verify_valid");
match result {
Err(CoreError::SessionExpired) => {} other => panic!("Expected SessionExpired, got: {:?}", other),
}
Ok(())
}
#[test]
fn test_security_mode_validate_expired_session_fails() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let session = VerifiedSession::establish(public_key.as_slice(), private_key.as_ref())?;
let expired_session = VerifiedSession {
session_id: *session.session_id(),
authenticated_at: session.authenticated_at(),
trust_level: session.trust_level(),
public_key: session.public_key().clone(),
expires_at: Utc::now() - Duration::seconds(1),
};
let mode = SecurityMode::Verified(&expired_session);
let result = mode.validate();
assert!(result.is_err(), "Expired session in SecurityMode should fail validation");
Ok(())
}
#[test]
fn test_continuous_verification_pending_after_interval_succeeds() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config =
ZeroTrustConfig::new().with_continuous_verification(true).with_verification_interval(1);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
std::thread::sleep(std::time::Duration::from_millis(5));
let status = auth.verify_continuously()?;
assert_eq!(status, VerificationStatus::Pending, "Should be Pending after interval elapsed");
Ok(())
}
#[test]
fn test_challenge_generation_low_complexity_has_correct_size() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_complexity(ProofComplexity::Low);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge = auth.generate_challenge()?;
assert_eq!(challenge.data().len(), 32, "Low complexity = 32 bytes");
Ok(())
}
#[test]
fn test_challenge_generation_medium_complexity_has_correct_size() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_complexity(ProofComplexity::Medium);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge = auth.generate_challenge()?;
assert_eq!(challenge.data().len(), 64, "Medium complexity = 64 bytes");
Ok(())
}
#[test]
fn test_verify_proof_medium_short_proof_rejects_fails() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_complexity(ProofComplexity::Medium);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge_data = vec![1u8; 64];
let short_proof = ZeroKnowledgeProof::new(
challenge_data.clone(),
vec![0u8; 64],
Utc::now(),
ProofComplexity::Medium,
);
let result = auth.verify_proof(&short_proof, &challenge_data)?;
assert!(!result, "Medium-complexity proof without timestamp should fail");
Ok(())
}
#[test]
fn test_verify_proof_high_short_proof_rejects_fails() -> Result<()> {
let (public_key, private_key) = generate_keypair()?;
let config = ZeroTrustConfig::new().with_complexity(ProofComplexity::High);
let auth = ZeroTrustAuth::with_config(public_key, private_key, config)?;
let challenge_data = vec![1u8; 128];
let short_proof = ZeroKnowledgeProof::new(
challenge_data.clone(),
vec![0u8; 70],
Utc::now(),
ProofComplexity::High,
);
let result = auth.verify_proof(&short_proof, &challenge_data)?;
assert!(!result, "High-complexity proof too short should fail");
Ok(())
}
#[test]
fn test_zero_knowledge_proof_debug_and_clone_succeeds() {
let proof =
ZeroKnowledgeProof::new(vec![1, 2, 3], vec![0u8; 64], Utc::now(), ProofComplexity::Low);
let cloned = proof.clone();
assert_eq!(cloned.challenge(), proof.challenge());
assert_eq!(cloned.proof_data(), proof.proof_data());
let debug = format!("{:?}", proof);
assert!(debug.contains("ZeroKnowledgeProof"));
}
#[test]
fn test_proof_of_possession_data_debug_and_clone_succeeds() {
let pop =
ProofOfPossessionData::new(PublicKey::new(vec![1, 2, 3]), vec![0u8; 64], Utc::now());
let cloned = pop.clone();
assert_eq!(cloned.public_key(), pop.public_key());
let debug = format!("{:?}", pop);
assert!(debug.contains("ProofOfPossessionData"));
}
#[test]
fn test_challenge_timeout_ms_influences_challenge_timeout_succeeds() -> Result<()> {
let (public_key_a, private_key_a) = generate_keypair()?;
let config_a = ZeroTrustConfig::new().with_timeout(1000);
let auth_a = ZeroTrustAuth::with_config(public_key_a, private_key_a, config_a)?;
let challenge_a = auth_a.generate_challenge()?;
let (public_key_b, private_key_b) = generate_keypair()?;
let config_b = ZeroTrustConfig::new().with_timeout(9999);
let auth_b = ZeroTrustAuth::with_config(public_key_b, private_key_b, config_b)?;
let challenge_b = auth_b.generate_challenge()?;
assert_ne!(
challenge_a.timeout_ms(),
challenge_b.timeout_ms(),
"challenge_timeout_ms must influence the timeout embedded in generated challenges"
);
Ok(())
}
#[test]
fn test_proof_complexity_influences_challenge_data_size_has_correct_size() -> Result<()> {
let (public_key_a, private_key_a) = generate_keypair()?;
let config_a = ZeroTrustConfig::new().with_complexity(ProofComplexity::Low);
let auth_a = ZeroTrustAuth::with_config(public_key_a, private_key_a, config_a)?;
let challenge_a = auth_a.generate_challenge()?;
let (public_key_b, private_key_b) = generate_keypair()?;
let config_b = ZeroTrustConfig::new().with_complexity(ProofComplexity::High);
let auth_b = ZeroTrustAuth::with_config(public_key_b, private_key_b, config_b)?;
let challenge_b = auth_b.generate_challenge()?;
assert_ne!(
challenge_a.data().len(),
challenge_b.data().len(),
"proof_complexity must influence the size of generated challenge data"
);
assert_eq!(challenge_a.data().len(), 32, "Low complexity must produce 32-byte challenge");
assert_eq!(
challenge_b.data().len(),
128,
"High complexity must produce 128-byte challenge"
);
Ok(())
}
#[test]
fn test_proof_complexity_influences_proof_data_size_has_correct_size() -> Result<()> {
let (public_key_a, private_key_a) = generate_keypair()?;
let config_a = ZeroTrustConfig::new().with_complexity(ProofComplexity::Low);
let auth_a = ZeroTrustAuth::with_config(public_key_a, private_key_a, config_a)?;
let challenge_a = auth_a.generate_challenge()?;
let proof_a = auth_a.generate_proof(challenge_a.data())?;
let (public_key_b, private_key_b) = generate_keypair()?;
let config_b = ZeroTrustConfig::new().with_complexity(ProofComplexity::Medium);
let auth_b = ZeroTrustAuth::with_config(public_key_b, private_key_b, config_b)?;
let challenge_b = auth_b.generate_challenge()?;
let proof_b = auth_b.generate_proof(challenge_b.data())?;
assert_ne!(
proof_a.proof_data().len(),
proof_b.proof_data().len(),
"proof_complexity must influence the size of generated proof data"
);
Ok(())
}
#[test]
fn test_continuous_verification_influences_verify_continuously_succeeds() -> Result<()> {
let (public_key_a, private_key_a) = generate_keypair()?;
let config_a = ZeroTrustConfig::new()
.with_continuous_verification(false)
.with_verification_interval(1); let auth_a = ZeroTrustAuth::with_config(public_key_a, private_key_a, config_a)?;
let (public_key_b, private_key_b) = generate_keypair()?;
let config_b =
ZeroTrustConfig::new().with_continuous_verification(true).with_verification_interval(1);
let auth_b = ZeroTrustAuth::with_config(public_key_b, private_key_b, config_b)?;
std::thread::sleep(std::time::Duration::from_millis(5));
let status_a = auth_a.verify_continuously()?;
let status_b = auth_b.verify_continuously()?;
assert_eq!(
status_a,
VerificationStatus::Verified,
"continuous_verification=false must return Verified without interval check"
);
assert_eq!(
status_b,
VerificationStatus::Pending,
"continuous_verification=true with 1ms interval must return Pending after elapsed"
);
Ok(())
}
#[test]
fn test_verification_interval_ms_influences_continuous_session_validity_succeeds() -> Result<()>
{
let (public_key_a, private_key_a) = generate_keypair()?;
let config_a =
ZeroTrustConfig::new().with_continuous_verification(true).with_verification_interval(1);
let auth_a = ZeroTrustAuth::with_config(public_key_a, private_key_a, config_a)?;
let (public_key_b, private_key_b) = generate_keypair()?;
let config_b = ZeroTrustConfig::new()
.with_continuous_verification(true)
.with_verification_interval(3_600_000);
let auth_b = ZeroTrustAuth::with_config(public_key_b, private_key_b, config_b)?;
let session_a = auth_a.start_continuous_verification();
let session_b = auth_b.start_continuous_verification();
std::thread::sleep(std::time::Duration::from_millis(5));
let valid_a = session_a.is_valid()?;
let valid_b = session_b.is_valid()?;
assert_ne!(
valid_a, valid_b,
"verification_interval_ms must influence continuous session validity"
);
assert!(!valid_a, "1ms interval session must be invalid after 5ms sleep");
assert!(valid_b, "1-hour interval session must remain valid");
Ok(())
}
}