#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![warn(clippy::all)]
use core::sync::atomic::{AtomicU8, Ordering};
use ic_core::{ensure, Error, ErrorKind, Result};
use ic_ontology::{FipsStatus, ImplStatus};
pub mod selftest;
pub use selftest::{run_all_self_tests, SelfTestReport, TestOutcome};
const STATE_UNINITIALIZED: u8 = 0;
const STATE_TESTING: u8 = 1;
const STATE_OPERATIONAL_UNRESTRICTED: u8 = 2;
const STATE_OPERATIONAL_APPROVED: u8 = 3;
const STATE_ERROR: u8 = 4;
static STATE: AtomicU8 = AtomicU8::new(STATE_UNINITIALIZED);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Approved,
Unrestricted,
}
impl Mode {
pub const fn id(self) -> &'static str {
match self {
Self::Approved => "approved",
Self::Unrestricted => "unrestricted",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State {
Uninitialized,
SelfTestInProgress,
Operational(Mode),
Error,
}
impl State {
pub const fn id(self) -> &'static str {
match self {
Self::Uninitialized => "uninitialized",
Self::SelfTestInProgress => "self-test-in-progress",
Self::Operational(Mode::Approved) => "operational-approved",
Self::Operational(Mode::Unrestricted) => "operational-unrestricted",
Self::Error => "error",
}
}
}
fn decode(raw: u8) -> State {
match raw {
STATE_TESTING => State::SelfTestInProgress,
STATE_OPERATIONAL_UNRESTRICTED => State::Operational(Mode::Unrestricted),
STATE_OPERATIONAL_APPROVED => State::Operational(Mode::Approved),
STATE_ERROR => State::Error,
_ => State::Uninitialized,
}
}
pub fn state() -> State {
decode(STATE.load(Ordering::SeqCst))
}
pub fn mode() -> Option<Mode> {
match state() {
State::Operational(m) => Some(m),
_ => None,
}
}
pub fn initialize() -> Result<SelfTestReport> {
match state() {
State::Error => {
return Err(Error::new(
ErrorKind::ModuleErrorState,
"module in error state",
))
}
State::Operational(_) => return Ok(run_all_self_tests()),
_ => {}
}
STATE.store(STATE_TESTING, Ordering::SeqCst);
let report = run_all_self_tests();
if report.failed > 0 {
STATE.store(STATE_ERROR, Ordering::SeqCst);
return Err(Error::new(
ErrorKind::SelfTestFailed,
"pre-operational self-test failed; module latched in error state",
));
}
STATE.store(STATE_OPERATIONAL_UNRESTRICTED, Ordering::SeqCst);
Ok(report)
}
pub fn set_mode(new_mode: Mode) -> Result<()> {
match state() {
State::Operational(_) => {
STATE.store(
match new_mode {
Mode::Approved => STATE_OPERATIONAL_APPROVED,
Mode::Unrestricted => STATE_OPERATIONAL_UNRESTRICTED,
},
Ordering::SeqCst,
);
Ok(())
}
State::Error => Err(Error::new(
ErrorKind::ModuleErrorState,
"module in error state",
)),
_ => Err(Error::new(
ErrorKind::ModuleErrorState,
"call initialize() before selecting a mode",
)),
}
}
pub fn enter_error_state() {
STATE.store(STATE_ERROR, Ordering::SeqCst);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServiceIndicator {
Approved,
ApprovedAsComponent,
NotApproved,
}
impl ServiceIndicator {
pub const fn id(self) -> &'static str {
match self {
Self::Approved => "approved",
Self::ApprovedAsComponent => "approved-as-component",
Self::NotApproved => "not-approved",
}
}
}
pub fn check(algorithm_id: &str) -> Result<ServiceIndicator> {
match state() {
State::Operational(_) => {}
State::Error => {
return Err(Error::new(
ErrorKind::ModuleErrorState,
"module in error state",
))
}
_ => {
return Err(Error::new(
ErrorKind::ModuleErrorState,
"module not initialized; call ic_fips::initialize()",
))
}
}
let entry = ic_ontology::get(algorithm_id)
.ok_or(Error::new(ErrorKind::Unsupported, "unknown algorithm"))?;
ensure!(
entry.status == ImplStatus::Available,
Unsupported,
"algorithm is described by the ontology but not implemented in this build"
);
let approved_mode = mode() == Some(Mode::Approved);
if approved_mode && !entry.fips.permitted_in_approved_mode() {
return Err(Error::new(
ErrorKind::NotApprovedInFipsMode,
"algorithm is not approved; select an approved alternative or leave approved mode",
));
}
Ok(match entry.fips {
FipsStatus::Approved | FipsStatus::Deprecated => ServiceIndicator::Approved,
FipsStatus::AllowedAsComponent => ServiceIndicator::ApprovedAsComponent,
FipsStatus::NotApproved | FipsStatus::Disallowed => ServiceIndicator::NotApproved,
})
}
pub fn guarded<T, F: FnOnce() -> T>(algorithm_id: &str, op: F) -> Result<(T, ServiceIndicator)> {
let indicator = check(algorithm_id)?;
Ok((op(), indicator))
}
pub const VALIDATION_STATEMENT: &str = "\
IronCrypto implements the FIPS 140-3 operational discipline (approved-mode \
policy, pre-operational and conditional self-tests, a latching error state, and \
service indicators). It has NOT been submitted to or validated by the CMVP, and \
holds no certificate number. Do not represent it as FIPS validated.";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn module_lifecycle_and_policy() {
assert_eq!(state(), State::Uninitialized);
assert_eq!(
check("sha2-256").unwrap_err().kind(),
ErrorKind::ModuleErrorState
);
assert_eq!(
set_mode(Mode::Approved).unwrap_err().kind(),
ErrorKind::ModuleErrorState
);
let report = initialize().unwrap();
assert!(report.passed > 0);
assert_eq!(report.failed, 0);
assert_eq!(state(), State::Operational(Mode::Unrestricted));
assert_eq!(check("sha2-256").unwrap(), ServiceIndicator::Approved);
assert_eq!(
check("chacha20-poly1305").unwrap(),
ServiceIndicator::NotApproved
);
assert_eq!(
check("aes-256").unwrap(),
ServiceIndicator::ApprovedAsComponent
);
assert_eq!(
check("nonsense").unwrap_err().kind(),
ErrorKind::Unsupported
);
assert!(check("ml-kem-768").is_ok());
assert!(check("ml-dsa-65").is_ok());
assert_eq!(
check("aes-256-gcm-siv").unwrap(),
ServiceIndicator::NotApproved
);
assert_eq!(check("md5").unwrap_err().kind(), ErrorKind::Unsupported);
assert_eq!(check("sha-1").unwrap_err().kind(), ErrorKind::Unsupported);
set_mode(Mode::Approved).unwrap();
assert_eq!(mode(), Some(Mode::Approved));
assert_eq!(check("aes-256-gcm").unwrap(), ServiceIndicator::Approved);
assert_eq!(
check("chacha20-poly1305").unwrap_err().kind(),
ErrorKind::NotApprovedInFipsMode
);
assert_eq!(
check("ed25519").unwrap_err().kind(),
ErrorKind::NotApprovedInFipsMode
);
assert_eq!(
check("x25519").unwrap_err().kind(),
ErrorKind::NotApprovedInFipsMode
);
let (n, ind) = guarded("sha2-256", || 42).unwrap();
assert_eq!(n, 42);
assert_eq!(ind, ServiceIndicator::Approved);
assert!(guarded("chacha20-poly1305", || 42).is_err());
initialize().unwrap();
assert_eq!(mode(), Some(Mode::Approved));
set_mode(Mode::Unrestricted).unwrap();
assert!(check("chacha20-poly1305").is_ok());
enter_error_state();
assert_eq!(state(), State::Error);
assert_eq!(
check("sha2-256").unwrap_err().kind(),
ErrorKind::ModuleErrorState
);
assert_eq!(
initialize().unwrap_err().kind(),
ErrorKind::ModuleErrorState
);
assert_eq!(
set_mode(Mode::Unrestricted).unwrap_err().kind(),
ErrorKind::ModuleErrorState
);
assert_eq!(mode(), None);
}
#[test]
fn state_identifiers_are_distinct() {
let ids = [
State::Uninitialized.id(),
State::SelfTestInProgress.id(),
State::Operational(Mode::Approved).id(),
State::Operational(Mode::Unrestricted).id(),
State::Error.id(),
];
for (i, a) in ids.iter().enumerate() {
for b in ids.iter().skip(i + 1) {
assert_ne!(a, b);
}
}
}
#[test]
fn validation_statement_denies_certification() {
assert!(VALIDATION_STATEMENT.contains("NOT been submitted"));
assert!(!ic_ontology::runtime::has("fips-validated"));
}
}