#[cfg(feature = "alloc")]
use alloc::{
format,
string::{
String,
ToString,
},
};
use crate::types::*;
#[derive(Debug, Clone, PartialEq)]
pub enum HpkeError {
KemError {
algorithm: HpkeKem,
operation: KemOperation,
cause: String,
},
KdfError {
algorithm: HpkeKdf,
operation: KdfOperation,
cause: String,
},
AeadError {
algorithm: HpkeAead,
operation: AeadOperation,
cause: String,
},
SecurityError {
validation: SecurityValidation,
cause: String,
},
ProtocolError {
stage: ProtocolStage,
cause: String,
},
ConfigError {
setting: String,
cause: String,
},
CryptoError(String),
InvalidInput {
parameter: String,
value: String,
expected: String,
},
FeatureNotEnabled {
feature: String,
},
NotImplemented {
feature: String,
},
InconsistentPsk,
}
#[derive(Debug, Clone, PartialEq)]
pub enum KemOperation {
KeyGeneration,
Encapsulation,
Decapsulation,
KeyValidation,
}
#[derive(Debug, Clone, PartialEq)]
pub enum KdfOperation {
Extract,
Expand,
Validation,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AeadOperation {
Seal,
Open,
KeyValidation,
NonceValidation,
CiphertextValidation,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SecurityValidation {
KeyLength,
NonceLength,
CiphertextLength,
ZeroKeyRejection,
ConstantTimeComparison,
InputSanitization,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ProtocolStage {
KeySchedule,
SuiteIdConstruction,
LabeledExtract,
LabeledExpand,
ContextSetup,
MessageSealing,
MessageOpening,
KeyExport,
}
impl HpkeError {
pub fn kem_error(
algorithm: HpkeKem,
operation: KemOperation,
cause: impl Into<String>,
) -> Self {
Self::KemError {
algorithm,
operation,
cause: cause.into(),
}
}
pub fn kdf_error(
algorithm: HpkeKdf,
operation: KdfOperation,
cause: impl Into<String>,
) -> Self {
Self::KdfError {
algorithm,
operation,
cause: cause.into(),
}
}
pub fn aead_error(
algorithm: HpkeAead,
operation: AeadOperation,
cause: impl Into<String>,
) -> Self {
Self::AeadError {
algorithm,
operation,
cause: cause.into(),
}
}
pub fn security_error(validation: SecurityValidation, cause: impl Into<String>) -> Self {
Self::SecurityError {
validation,
cause: cause.into(),
}
}
pub fn protocol_error(stage: ProtocolStage, cause: impl Into<String>) -> Self {
Self::ProtocolError {
stage,
cause: cause.into(),
}
}
pub fn invalid_input(
parameter: impl Into<String>,
value: impl Into<String>,
expected: impl Into<String>,
) -> Self {
Self::InvalidInput {
parameter: parameter.into(),
value: value.into(),
expected: expected.into(),
}
}
pub fn feature_not_enabled(feature: impl Into<String>) -> Self {
Self::FeatureNotEnabled {
feature: feature.into(),
}
}
pub fn not_implemented(feature: impl Into<String>) -> Self {
Self::NotImplemented {
feature: feature.into(),
}
}
}
impl core::fmt::Display for HpkeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
HpkeError::KemError {
algorithm,
operation,
cause,
} => {
write!(f, "KEM error in {:?} {:?}: {}", algorithm, operation, cause)
}
HpkeError::KdfError {
algorithm,
operation,
cause,
} => {
write!(f, "KDF error in {:?} {:?}: {}", algorithm, operation, cause)
}
HpkeError::AeadError {
algorithm,
operation,
cause,
} => {
write!(
f,
"AEAD error in {:?} {:?}: {}",
algorithm, operation, cause
)
}
HpkeError::SecurityError { validation, cause } => {
write!(
f,
"Security validation error in {:?}: {}",
validation, cause
)
}
HpkeError::ProtocolError { stage, cause } => {
write!(f, "Protocol error in {:?}: {}", stage, cause)
}
HpkeError::ConfigError { setting, cause } => {
write!(f, "Configuration error for {}: {}", setting, cause)
}
HpkeError::CryptoError(msg) => {
write!(f, "Cryptographic error: {}", msg)
}
HpkeError::InvalidInput {
parameter,
value,
expected,
} => {
write!(
f,
"Invalid input for {}: got '{}', expected {}",
parameter, value, expected
)
}
HpkeError::FeatureNotEnabled { feature } => {
write!(f, "Feature not enabled: {}", feature)
}
HpkeError::NotImplemented { feature } => {
write!(f, "Not implemented: {}", feature)
}
HpkeError::InconsistentPsk => {
write!(f, "Inconsistent PSK usage")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for HpkeError {}
impl From<HpkeError> for lib_q_core::Error {
fn from(err: HpkeError) -> Self {
match err {
HpkeError::KemError {
algorithm,
operation,
cause,
} => lib_q_core::Error::InternalError {
operation: format!("KEM {:?} {:?}", algorithm, operation),
details: cause,
},
HpkeError::KdfError {
algorithm,
operation,
cause,
} => lib_q_core::Error::InternalError {
operation: format!("KDF {:?} {:?}", algorithm, operation),
details: cause,
},
HpkeError::AeadError {
algorithm,
operation,
cause,
} => lib_q_core::Error::InternalError {
operation: format!("AEAD {:?} {:?}", algorithm, operation),
details: cause,
},
HpkeError::SecurityError { validation, cause } => lib_q_core::Error::InternalError {
operation: format!("Security {:?}", validation),
details: cause,
},
HpkeError::ProtocolError { stage, cause } => lib_q_core::Error::InternalError {
operation: format!("Protocol {:?}", stage),
details: cause,
},
HpkeError::ConfigError { setting, cause } => lib_q_core::Error::InternalError {
operation: format!("Config {}", setting),
details: cause,
},
HpkeError::CryptoError(msg) => lib_q_core::Error::InternalError {
operation: "Cryptographic operation".to_string(),
details: msg,
},
HpkeError::InvalidInput {
parameter,
value,
expected,
} => lib_q_core::Error::InternalError {
operation: format!("Input validation for {}", parameter),
details: format!("got '{}', expected {}", value, expected),
},
HpkeError::FeatureNotEnabled { feature } => lib_q_core::Error::InternalError {
operation: "Feature check".to_string(),
details: format!("Feature not enabled: {}", feature),
},
HpkeError::NotImplemented { feature } => lib_q_core::Error::InternalError {
operation: "Implementation".to_string(),
details: format!("Not implemented: {}", feature),
},
HpkeError::InconsistentPsk => lib_q_core::Error::InternalError {
operation: "PSK validation".to_string(),
details: "Inconsistent PSK usage".to_string(),
},
}
}
}
pub type HpkeResult<T> = Result<T, HpkeError>;
#[macro_export]
macro_rules! kem_err {
($alg:expr, $op:expr, $msg:expr) => {
HpkeError::kem_error($alg, $op, $msg)
};
}
#[macro_export]
macro_rules! kdf_err {
($alg:expr, $op:expr, $msg:expr) => {
HpkeError::kdf_error($alg, $op, $msg)
};
}
#[macro_export]
macro_rules! aead_err {
($alg:expr, $op:expr, $msg:expr) => {
HpkeError::aead_error($alg, $op, $msg)
};
}
#[macro_export]
macro_rules! security_err {
($validation:expr, $msg:expr) => {
HpkeError::security_error($validation, $msg)
};
}
#[macro_export]
macro_rules! protocol_err {
($stage:expr, $msg:expr) => {
HpkeError::protocol_error($stage, $msg)
};
}
#[cfg(test)]
mod tests {
use alloc::vec;
use lib_q_core::Error as CoreError;
use super::*;
#[test]
fn display_formats_all_error_variants() {
let cases = vec![
HpkeError::kem_error(HpkeKem::MlKem512, KemOperation::Encapsulation, "kem fail"),
HpkeError::kdf_error(HpkeKdf::HkdfShake256, KdfOperation::Expand, "kdf fail"),
HpkeError::aead_error(HpkeAead::Saturnin256, AeadOperation::Open, "aead fail"),
HpkeError::security_error(SecurityValidation::NonceLength, "security fail"),
HpkeError::protocol_error(ProtocolStage::ContextSetup, "protocol fail"),
HpkeError::ConfigError {
setting: "mode".into(),
cause: "bad".into(),
},
HpkeError::CryptoError("crypto fail".into()),
HpkeError::invalid_input("nonce", "3", "16 bytes"),
HpkeError::feature_not_enabled("saturnin"),
HpkeError::not_implemented("auth mode"),
HpkeError::InconsistentPsk,
];
for err in cases {
let text = err.to_string();
assert!(!text.is_empty());
}
}
#[test]
fn conversion_to_core_error_maps_all_variants() {
let cases = vec![
HpkeError::kem_error(HpkeKem::MlKem768, KemOperation::Decapsulation, "kem"),
HpkeError::kdf_error(HpkeKdf::HkdfSha3_512, KdfOperation::Validation, "kdf"),
HpkeError::aead_error(
HpkeAead::DuplexSpongeAead,
AeadOperation::CiphertextValidation,
"aead",
),
HpkeError::security_error(SecurityValidation::KeyLength, "security"),
HpkeError::protocol_error(ProtocolStage::MessageOpening, "protocol"),
HpkeError::ConfigError {
setting: "suite".into(),
cause: "unsupported".into(),
},
HpkeError::CryptoError("crypto".into()),
HpkeError::invalid_input("key", "short", "32 bytes"),
HpkeError::feature_not_enabled("ml-kem"),
HpkeError::not_implemented("export"),
HpkeError::InconsistentPsk,
];
for err in cases {
let converted: CoreError = err.into();
match converted {
CoreError::InternalError { operation, details } => {
assert!(!operation.is_empty());
assert!(!details.is_empty());
}
other => panic!("unexpected conversion result: {other:?}"),
}
}
}
}