#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Class {
Hash,
Xof,
Mac,
BlockCipher,
CipherMode,
Aead,
Kdf,
PasswordKdf,
Drbg,
KeyAgreement,
Kem,
Signature,
}
impl Class {
pub const fn id(self) -> &'static str {
match self {
Self::Hash => "hash",
Self::Xof => "xof",
Self::Mac => "mac",
Self::BlockCipher => "block-cipher",
Self::CipherMode => "cipher-mode",
Self::Aead => "aead",
Self::Kdf => "kdf",
Self::PasswordKdf => "password-kdf",
Self::Drbg => "drbg",
Self::KeyAgreement => "key-agreement",
Self::Kem => "kem",
Self::Signature => "signature",
}
}
pub const ALL: &'static [Class] = &[
Class::Hash,
Class::Xof,
Class::Mac,
Class::BlockCipher,
Class::CipherMode,
Class::Aead,
Class::Kdf,
Class::PasswordKdf,
Class::Drbg,
Class::KeyAgreement,
Class::Kem,
Class::Signature,
];
pub fn from_id(id: &str) -> Option<Class> {
Self::ALL.iter().copied().find(|c| c.id() == id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Purpose {
Integrity,
Confidentiality,
Authentication,
KeyDerivation,
PasswordHashing,
KeyEstablishment,
RandomGeneration,
NonRepudiation,
Commitment,
}
impl Purpose {
pub const fn id(self) -> &'static str {
match self {
Self::Integrity => "integrity",
Self::Confidentiality => "confidentiality",
Self::Authentication => "authentication",
Self::KeyDerivation => "key-derivation",
Self::PasswordHashing => "password-hashing",
Self::KeyEstablishment => "key-establishment",
Self::RandomGeneration => "random-generation",
Self::NonRepudiation => "non-repudiation",
Self::Commitment => "commitment",
}
}
pub const ALL: &'static [Purpose] = &[
Purpose::Integrity,
Purpose::Confidentiality,
Purpose::Authentication,
Purpose::KeyDerivation,
Purpose::PasswordHashing,
Purpose::KeyEstablishment,
Purpose::RandomGeneration,
Purpose::NonRepudiation,
Purpose::Commitment,
];
pub fn from_id(id: &str) -> Option<Purpose> {
Self::ALL.iter().copied().find(|p| p.id() == id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FipsStatus {
Approved,
AllowedAsComponent,
NotApproved,
Deprecated,
Disallowed,
}
impl FipsStatus {
pub const ALL: &'static [FipsStatus] = &[
FipsStatus::Approved,
FipsStatus::AllowedAsComponent,
FipsStatus::NotApproved,
FipsStatus::Deprecated,
FipsStatus::Disallowed,
];
pub const fn id(self) -> &'static str {
match self {
Self::Approved => "approved",
Self::AllowedAsComponent => "allowed-as-component",
Self::NotApproved => "not-approved",
Self::Deprecated => "deprecated",
Self::Disallowed => "disallowed",
}
}
pub const fn permitted_in_approved_mode(self) -> bool {
matches!(
self,
Self::Approved | Self::AllowedAsComponent | Self::Deprecated
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImplStatus {
Available,
Experimental,
Planned,
Excluded,
}
impl ImplStatus {
pub const ALL: &'static [ImplStatus] = &[
ImplStatus::Available,
ImplStatus::Experimental,
ImplStatus::Planned,
ImplStatus::Excluded,
];
pub const fn id(self) -> &'static str {
match self {
Self::Available => "available",
Self::Experimental => "experimental",
Self::Planned => "planned",
Self::Excluded => "excluded",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Performance {
Fast,
Moderate,
Slow,
DeliberatelySlow,
}
impl Performance {
pub const ALL: &'static [Performance] = &[
Performance::Fast,
Performance::Moderate,
Performance::Slow,
Performance::DeliberatelySlow,
];
pub const fn id(self) -> &'static str {
match self {
Self::Fast => "fast",
Self::Moderate => "moderate",
Self::Slow => "slow",
Self::DeliberatelySlow => "deliberately-slow",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Param {
pub name: &'static str,
pub unit: Unit,
pub min: u64,
pub max: u64,
pub recommended: u64,
pub note: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Unit {
Bytes,
Count,
}
impl Unit {
pub const ALL: &'static [Unit] = &[Unit::Bytes, Unit::Count];
pub const fn id(self) -> &'static str {
match self {
Self::Bytes => "bytes",
Self::Count => "count",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Constraint {
pub id: &'static str,
pub requirement: &'static str,
pub consequence: &'static str,
pub severity: Severity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
Critical,
Serious,
Advisory,
}
impl Severity {
pub const ALL: &'static [Severity] =
&[Severity::Critical, Severity::Serious, Severity::Advisory];
pub const fn id(self) -> &'static str {
match self {
Self::Critical => "critical",
Self::Serious => "serious",
Self::Advisory => "advisory",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Strength {
pub classical: u16,
pub quantum: u16,
}
impl Strength {
pub const fn symmetric(bits: u16) -> Strength {
Strength {
classical: bits,
quantum: bits / 2,
}
}
pub const fn classical_only(bits: u16) -> Strength {
Strength {
classical: bits,
quantum: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Relation {
BuiltOn,
Supersedes,
SupersededBy,
PairsWith,
Specializes,
}
impl Relation {
pub const ALL: &'static [Relation] = &[
Relation::BuiltOn,
Relation::Supersedes,
Relation::SupersededBy,
Relation::PairsWith,
Relation::Specializes,
];
pub const fn id(self) -> &'static str {
match self {
Self::BuiltOn => "built-on",
Self::Supersedes => "supersedes",
Self::SupersededBy => "superseded-by",
Self::PairsWith => "pairs-with",
Self::Specializes => "specializes",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Edge {
pub relation: Relation,
pub target: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct Entry {
pub id: &'static str,
pub name: &'static str,
pub aliases: &'static [&'static str],
pub summary: &'static str,
pub class: Class,
pub family: &'static str,
pub purposes: &'static [Purpose],
pub strength: Strength,
pub fips: FipsStatus,
pub status: ImplStatus,
pub standards: &'static [&'static str],
pub params: &'static [Param],
pub constraints: &'static [Constraint],
pub edges: &'static [Edge],
pub performance: Performance,
pub rust_path: &'static str,
pub example: &'static str,
pub notes: &'static str,
}
impl Entry {
pub fn serves(&self, purpose: Purpose) -> bool {
self.purposes.contains(&purpose)
}
pub fn matches_name(&self, needle: &str) -> bool {
if eq_ignore_case(self.id, needle) || eq_ignore_case(self.name, needle) {
return true;
}
self.aliases.iter().any(|a| eq_ignore_case(a, needle))
}
pub fn approved_mode_ok(&self) -> bool {
self.fips.permitted_in_approved_mode() && self.status == ImplStatus::Available
}
pub fn worst_constraint(&self) -> Option<&'static Constraint> {
self.constraints.iter().min_by_key(|c| c.severity)
}
}
fn eq_ignore_case(a: &str, b: &str) -> bool {
a.eq_ignore_ascii_case(b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_variant_lists_are_complete() {
#[allow(clippy::needless_match)]
fn identify_status(v: ImplStatus) -> ImplStatus {
match v {
ImplStatus::Available => ImplStatus::Available,
ImplStatus::Experimental => ImplStatus::Experimental,
ImplStatus::Planned => ImplStatus::Planned,
ImplStatus::Excluded => ImplStatus::Excluded,
}
}
#[allow(clippy::needless_match)]
fn identify_fips(v: FipsStatus) -> FipsStatus {
match v {
FipsStatus::Approved => FipsStatus::Approved,
FipsStatus::AllowedAsComponent => FipsStatus::AllowedAsComponent,
FipsStatus::NotApproved => FipsStatus::NotApproved,
FipsStatus::Deprecated => FipsStatus::Deprecated,
FipsStatus::Disallowed => FipsStatus::Disallowed,
}
}
#[allow(clippy::needless_match)]
fn identify_perf(v: Performance) -> Performance {
match v {
Performance::Fast => Performance::Fast,
Performance::Moderate => Performance::Moderate,
Performance::Slow => Performance::Slow,
Performance::DeliberatelySlow => Performance::DeliberatelySlow,
}
}
#[allow(clippy::needless_match)]
fn identify_severity(v: Severity) -> Severity {
match v {
Severity::Critical => Severity::Critical,
Severity::Serious => Severity::Serious,
Severity::Advisory => Severity::Advisory,
}
}
#[allow(clippy::needless_match)]
fn identify_unit(v: Unit) -> Unit {
match v {
Unit::Bytes => Unit::Bytes,
Unit::Count => Unit::Count,
}
}
#[allow(clippy::needless_match)]
fn identify_relation(v: Relation) -> Relation {
match v {
Relation::BuiltOn => Relation::BuiltOn,
Relation::Supersedes => Relation::Supersedes,
Relation::SupersededBy => Relation::SupersededBy,
Relation::PairsWith => Relation::PairsWith,
Relation::Specializes => Relation::Specializes,
}
}
assert_eq!(ImplStatus::ALL.len(), 4);
assert_eq!(FipsStatus::ALL.len(), 5);
assert_eq!(Performance::ALL.len(), 4);
assert_eq!(Severity::ALL.len(), 3);
assert_eq!(Unit::ALL.len(), 2);
assert_eq!(Relation::ALL.len(), 5);
for v in ImplStatus::ALL {
assert_eq!(identify_status(*v), *v);
}
for v in FipsStatus::ALL {
assert_eq!(identify_fips(*v), *v);
}
for v in Performance::ALL {
assert_eq!(identify_perf(*v), *v);
}
for v in Severity::ALL {
assert_eq!(identify_severity(*v), *v);
}
for v in Unit::ALL {
assert_eq!(identify_unit(*v), *v);
}
for v in Relation::ALL {
assert_eq!(identify_relation(*v), *v);
}
fn distinct(ids: &[&str]) -> bool {
ids.iter()
.enumerate()
.all(|(i, a)| !ids[i + 1..].contains(a))
}
assert!(distinct(
&ImplStatus::ALL.iter().map(|v| v.id()).collect::<Vec<_>>()
));
assert!(distinct(
&FipsStatus::ALL.iter().map(|v| v.id()).collect::<Vec<_>>()
));
assert!(distinct(
&Relation::ALL.iter().map(|v| v.id()).collect::<Vec<_>>()
));
}
#[test]
fn severity_is_ordered_worst_first() {
assert!(Severity::Critical < Severity::Serious);
assert!(Severity::Serious < Severity::Advisory);
assert_eq!(
[Severity::Advisory, Severity::Critical, Severity::Serious]
.iter()
.min(),
Some(&Severity::Critical),
"min() must mean worst, which is what worst_constraint relies on"
);
}
#[test]
fn class_ids_roundtrip() {
for c in Class::ALL {
assert_eq!(Class::from_id(c.id()), Some(*c));
}
assert_eq!(Class::from_id("nonsense"), None);
}
#[test]
fn purpose_ids_roundtrip() {
for p in Purpose::ALL {
assert_eq!(Purpose::from_id(p.id()), Some(*p));
}
}
#[test]
fn approved_mode_policy_is_explicit() {
assert!(FipsStatus::Approved.permitted_in_approved_mode());
assert!(FipsStatus::AllowedAsComponent.permitted_in_approved_mode());
assert!(FipsStatus::Deprecated.permitted_in_approved_mode());
assert!(!FipsStatus::NotApproved.permitted_in_approved_mode());
assert!(!FipsStatus::Disallowed.permitted_in_approved_mode());
}
#[test]
fn quantum_strength_models_grover_and_shor() {
assert_eq!(Strength::symmetric(256).quantum, 128);
assert_eq!(Strength::classical_only(128).quantum, 0);
}
#[test]
fn severity_orders_critical_first() {
assert!(Severity::Critical < Severity::Serious);
assert!(Severity::Serious < Severity::Advisory);
}
#[test]
fn case_insensitive_name_matching() {
assert!(eq_ignore_case("SHA-256", "sha-256"));
assert!(!eq_ignore_case("SHA-256", "sha-384"));
assert!(!eq_ignore_case("SHA-256", "sha-2560"));
}
}