#![deny(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]
pub use crate::types::types::*;
pub use crate::unified_api::crypto_types::{
DecryptKey, EncryptKey, EncryptedOutput, EncryptionScheme, HybridComponents,
};
use crate::unified_api::zero_trust::VerifiedSession;
fn default_compliance_for_use_case(use_case: UseCase) -> ComplianceMode {
match use_case {
UseCase::GovernmentClassified
| UseCase::HealthcareRecords
| UseCase::PaymentCard
| UseCase::FinancialTransactions => ComplianceMode::Fips140_3,
UseCase::SecureMessaging
| UseCase::EmailEncryption
| UseCase::VpnTunnel
| UseCase::ApiSecurity
| UseCase::FileStorage
| UseCase::DatabaseEncryption
| UseCase::CloudStorage
| UseCase::BackupArchive
| UseCase::ConfigSecrets
| UseCase::Authentication
| UseCase::SessionToken
| UseCase::DigitalCertificate
| UseCase::KeyExchange
| UseCase::LegalDocuments
| UseCase::BlockchainTransaction
| UseCase::IoTDevice
| UseCase::FirmwareSigning
| UseCase::AuditLog => ComplianceMode::Default,
}
}
#[derive(Debug, Clone)]
pub struct CryptoConfig<'a> {
session: Option<&'a VerifiedSession>,
selection: AlgorithmSelection,
compliance: ComplianceMode,
compliance_explicit: bool,
}
impl<'a> Default for CryptoConfig<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> CryptoConfig<'a> {
#[must_use]
pub fn new() -> Self {
Self {
session: None,
selection: AlgorithmSelection::default(),
compliance: ComplianceMode::Default,
compliance_explicit: false,
}
}
#[must_use]
pub fn session(mut self, session: &'a VerifiedSession) -> Self {
self.session = Some(session);
self
}
#[must_use]
pub fn use_case(mut self, use_case: UseCase) -> Self {
if !self.compliance_explicit {
self.compliance = default_compliance_for_use_case(use_case);
}
self.selection = AlgorithmSelection::UseCase(use_case);
self
}
#[must_use]
pub fn security_level(mut self, level: SecurityLevel) -> Self {
self.selection = AlgorithmSelection::SecurityLevel(level);
self
}
#[must_use]
pub fn force_scheme(mut self, scheme: CryptoScheme) -> Self {
self.selection = AlgorithmSelection::ForcedScheme(scheme);
self
}
#[must_use]
pub fn compliance(mut self, mode: ComplianceMode) -> Self {
self.compliance = mode;
self.compliance_explicit = true;
self
}
#[must_use]
pub fn get_session(&self) -> Option<&'a VerifiedSession> {
self.session
}
#[must_use]
pub fn get_selection(&self) -> &AlgorithmSelection {
&self.selection
}
#[must_use]
pub fn get_compliance(&self) -> &ComplianceMode {
&self.compliance
}
#[must_use]
pub fn is_verified(&self) -> bool {
self.session.is_some()
}
pub fn validate_scheme_compliance(
&self,
scheme: &str,
) -> crate::unified_api::error::Result<()> {
use crate::unified_api::error::CoreError;
const FIPS_BANNED: &[&str] = &["chacha20-poly1305"];
const CNSA_BANNED_CLASSICAL: &[&str] = &["ed25519", "aes-256-gcm"];
match self.compliance {
ComplianceMode::Default => Ok(()),
ComplianceMode::Fips140_3 => {
if FIPS_BANNED.contains(&scheme) {
return Err(CoreError::ComplianceViolation(format!(
"Scheme '{scheme}' is not FIPS 140-3 approved. \
Use AES-256-GCM or a FIPS-validated algorithm.",
)));
}
Ok(())
}
ComplianceMode::Cnsa2_0 => {
if CNSA_BANNED_CLASSICAL.contains(&scheme) {
return Err(CoreError::ComplianceViolation(format!(
"Scheme '{scheme}' is not permitted under CNSA 2.0 (post-quantum required). \
Use ML-KEM, ML-DSA, SLH-DSA, FN-DSA, or a hybrid scheme.",
)));
}
Ok(())
}
}
}
pub fn validate(&self) -> crate::unified_api::error::Result<()> {
use crate::unified_api::error::CoreError;
if let Some(session) = self.session {
session.verify_valid()?;
}
if self.compliance.requires_fips() && !fips_available() {
return Err(CoreError::FeatureNotAvailable(format!(
"{:?} compliance requires the `fips` feature. \
Rebuild with: latticearc = {{ features = [\"fips\"] }}",
self.compliance
)));
}
if matches!(self.compliance, ComplianceMode::Cnsa2_0)
&& let AlgorithmSelection::SecurityLevel(ref level) = self.selection
&& !matches!(level, SecurityLevel::Quantum)
{
return Err(CoreError::ConfigurationError(
"CNSA 2.0 compliance requires SecurityLevel::Quantum (PQ-only)".to_string(),
));
}
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_zeroized_bytes_new_stores_data_succeeds() {
let data = vec![1u8, 2, 3, 4, 5];
let zb = ZeroizedBytes::new(data.clone());
assert_eq!(zb.as_slice(), &data);
}
#[test]
fn test_security_level_default_is_standard_succeeds() {
assert_eq!(SecurityLevel::default(), SecurityLevel::High);
}
#[test]
fn test_performance_preference_default_is_balanced_succeeds() {
assert_eq!(PerformancePreference::default(), PerformancePreference::Balanced);
}
#[test]
fn test_algorithm_selection_default_is_automatic_succeeds() {
let sel = AlgorithmSelection::default();
assert_eq!(sel, AlgorithmSelection::SecurityLevel(SecurityLevel::High));
}
#[test]
fn test_crypto_config_new_sets_fields_succeeds() {
let config = CryptoConfig::new();
assert!(!config.is_verified());
assert!(config.get_session().is_none());
assert_eq!(*config.get_selection(), AlgorithmSelection::SecurityLevel(SecurityLevel::High));
}
#[test]
fn test_crypto_config_default_sets_expected_fields_succeeds() {
let config = CryptoConfig::default();
assert!(!config.is_verified());
}
#[test]
fn test_crypto_config_use_case_sets_use_case_field_succeeds() {
let config = CryptoConfig::new().use_case(UseCase::SecureMessaging);
assert_eq!(*config.get_selection(), AlgorithmSelection::UseCase(UseCase::SecureMessaging));
}
#[test]
fn test_crypto_config_security_level_sets_security_field_succeeds() {
let config = CryptoConfig::new().security_level(SecurityLevel::Maximum);
assert_eq!(
*config.get_selection(),
AlgorithmSelection::SecurityLevel(SecurityLevel::Maximum)
);
}
#[test]
fn test_crypto_config_validate_no_session_succeeds() {
let config = CryptoConfig::new();
assert!(config.validate().is_ok());
}
#[test]
fn test_crypto_config_clone_debug_work_correctly_succeeds() {
let config = CryptoConfig::new().use_case(UseCase::Authentication);
let cloned = config.clone();
assert_eq!(cloned.get_selection(), config.get_selection());
let debug = format!("{:?}", config);
assert!(debug.contains("CryptoConfig"));
}
#[test]
fn test_crypto_config_compliance_default_is_standard_succeeds() {
let config = CryptoConfig::new();
assert_eq!(*config.get_compliance(), ComplianceMode::Default);
}
#[test]
fn test_crypto_config_compliance_builder_sets_compliance_field_succeeds() {
let config = CryptoConfig::new().compliance(ComplianceMode::Fips140_3);
assert_eq!(*config.get_compliance(), ComplianceMode::Fips140_3);
}
#[test]
fn test_crypto_config_compliance_getter_returns_compliance_field_succeeds() {
let config = CryptoConfig::new().compliance(ComplianceMode::Cnsa2_0);
assert_eq!(*config.get_compliance(), ComplianceMode::Cnsa2_0);
}
#[test]
fn test_use_case_auto_compliance_government_is_correct() {
let config = CryptoConfig::new().use_case(UseCase::GovernmentClassified);
assert_eq!(*config.get_compliance(), ComplianceMode::Fips140_3);
}
#[test]
fn test_use_case_auto_compliance_healthcare_is_correct() {
let config = CryptoConfig::new().use_case(UseCase::HealthcareRecords);
assert_eq!(*config.get_compliance(), ComplianceMode::Fips140_3);
}
#[test]
fn test_use_case_auto_compliance_payment_is_correct() {
let config = CryptoConfig::new().use_case(UseCase::PaymentCard);
assert_eq!(*config.get_compliance(), ComplianceMode::Fips140_3);
}
#[test]
fn test_use_case_auto_compliance_financial_is_correct() {
let config = CryptoConfig::new().use_case(UseCase::FinancialTransactions);
assert_eq!(*config.get_compliance(), ComplianceMode::Fips140_3);
}
#[test]
fn test_use_case_auto_compliance_messaging_is_default() {
let config = CryptoConfig::new().use_case(UseCase::SecureMessaging);
assert_eq!(*config.get_compliance(), ComplianceMode::Default);
}
#[test]
fn test_use_case_auto_compliance_explicit_override_is_correct() {
let config = CryptoConfig::new()
.compliance(ComplianceMode::Default)
.use_case(UseCase::GovernmentClassified);
assert_eq!(*config.get_compliance(), ComplianceMode::Default);
}
#[test]
fn test_cnsa_requires_quantum_is_correct() {
let config = CryptoConfig::new()
.compliance(ComplianceMode::Cnsa2_0)
.security_level(SecurityLevel::High);
let result = config.validate();
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("CNSA 2.0"));
assert!(err_msg.contains("Quantum"));
}
#[cfg(not(feature = "fips"))]
#[test]
fn test_fips_compliance_without_feature_is_correct() {
let config = CryptoConfig::new().compliance(ComplianceMode::Fips140_3);
let result = config.validate();
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("fips"));
assert!(err_msg.contains("Rebuild"));
}
#[test]
fn test_default_compliance_allows_all_schemes_is_correct() {
let config = CryptoConfig::new(); assert!(config.validate_scheme_compliance("aes-256-gcm").is_ok());
assert!(config.validate_scheme_compliance("ed25519").is_ok());
assert!(config.validate_scheme_compliance("ml-dsa-65").is_ok());
assert!(config.validate_scheme_compliance("chacha20-poly1305").is_ok());
assert!(config.validate_scheme_compliance("hybrid-ml-dsa-65-ed25519").is_ok());
}
#[test]
fn test_fips_rejects_chacha_fails() {
let config = CryptoConfig::new().compliance(ComplianceMode::Fips140_3);
let result = config.validate_scheme_compliance("chacha20-poly1305");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("FIPS 140-3"));
assert!(err_msg.contains("chacha"));
}
#[test]
fn test_fips_allows_aes_gcm_succeeds() {
let config = CryptoConfig::new().compliance(ComplianceMode::Fips140_3);
assert!(config.validate_scheme_compliance("aes-256-gcm").is_ok());
}
#[test]
fn test_fips_allows_pq_schemes_succeeds() {
let config = CryptoConfig::new().compliance(ComplianceMode::Fips140_3);
assert!(config.validate_scheme_compliance("ml-kem-768").is_ok());
assert!(config.validate_scheme_compliance("ml-dsa-65").is_ok());
assert!(config.validate_scheme_compliance("slh-dsa-shake-128s").is_ok());
assert!(config.validate_scheme_compliance("hybrid-ml-dsa-65-ed25519").is_ok());
}
#[test]
fn test_cnsa_rejects_ed25519_fails() {
let config = CryptoConfig::new().compliance(ComplianceMode::Cnsa2_0);
let result = config.validate_scheme_compliance("ed25519");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("CNSA 2.0"));
assert!(err_msg.contains("ed25519"));
}
#[test]
fn test_cnsa_rejects_standalone_aes_gcm_fails() {
let config = CryptoConfig::new().compliance(ComplianceMode::Cnsa2_0);
let result = config.validate_scheme_compliance("aes-256-gcm");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("CNSA 2.0"));
}
#[test]
fn test_cnsa_allows_pq_schemes_succeeds() {
let config = CryptoConfig::new().compliance(ComplianceMode::Cnsa2_0);
assert!(config.validate_scheme_compliance("ml-kem-1024").is_ok());
assert!(config.validate_scheme_compliance("ml-dsa-87").is_ok());
assert!(config.validate_scheme_compliance("slh-dsa-shake-256s").is_ok());
assert!(config.validate_scheme_compliance("fn-dsa").is_ok());
}
#[test]
fn test_cnsa_allows_hybrid_schemes_succeeds() {
let config = CryptoConfig::new().compliance(ComplianceMode::Cnsa2_0);
assert!(config.validate_scheme_compliance("hybrid-ml-dsa-65-ed25519").is_ok());
assert!(config.validate_scheme_compliance("hybrid-ml-kem-768-x25519-aes-256-gcm").is_ok());
}
#[test]
fn test_force_scheme_builder_sets_selection_succeeds() {
let config = CryptoConfig::new().force_scheme(CryptoScheme::PostQuantum);
assert_eq!(
*config.get_selection(),
AlgorithmSelection::ForcedScheme(CryptoScheme::PostQuantum)
);
}
#[test]
fn test_force_scheme_overrides_use_case_is_correct() {
let config = CryptoConfig::new()
.use_case(UseCase::FileStorage)
.force_scheme(CryptoScheme::Symmetric);
assert_eq!(
*config.get_selection(),
AlgorithmSelection::ForcedScheme(CryptoScheme::Symmetric)
);
}
#[test]
fn test_force_scheme_overrides_security_level_is_correct() {
let config = CryptoConfig::new()
.security_level(SecurityLevel::Maximum)
.force_scheme(CryptoScheme::Hybrid);
assert_eq!(*config.get_selection(), AlgorithmSelection::ForcedScheme(CryptoScheme::Hybrid));
}
}