use super::super::crypto::{AeadTransform, CipherTransform, IntegrityTransform};
use crate::{CrafterError, Result};
pub const ENCR_NULL: u16 = 11;
pub const ENCR_AES_CBC: u16 = 12;
pub const ENCR_AES_CTR: u16 = 13;
pub const ENCR_AES_CCM_8: u16 = 14;
pub const ENCR_AES_GCM_16: u16 = 20;
pub const ENCR_CHACHA20_POLY1305: u16 = 28;
pub const AUTH_NONE: u16 = 0;
pub const AUTH_HMAC_SHA1_96: u16 = 2;
pub const AUTH_AES_XCBC_96: u16 = 5;
pub const AUTH_AES_128_GMAC: u16 = 9;
pub const AUTH_HMAC_SHA2_256_128: u16 = 12;
pub const AUTH_HMAC_SHA2_384_192: u16 = 13;
pub const AUTH_HMAC_SHA2_512_256: u16 = 14;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncryptionAlgorithm {
Null,
AesCbc,
AesCtr,
AesCcm8,
AesGcm16,
ChaCha20Poly1305,
Unknown(u16),
}
impl EncryptionAlgorithm {
pub const fn transform_id(self) -> u16 {
match self {
Self::Null => ENCR_NULL,
Self::AesCbc => ENCR_AES_CBC,
Self::AesCtr => ENCR_AES_CTR,
Self::AesCcm8 => ENCR_AES_CCM_8,
Self::AesGcm16 => ENCR_AES_GCM_16,
Self::ChaCha20Poly1305 => ENCR_CHACHA20_POLY1305,
Self::Unknown(id) => id,
}
}
pub const fn is_aead(self) -> bool {
matches!(
self,
Self::AesCcm8 | Self::AesGcm16 | Self::ChaCha20Poly1305
)
}
pub const fn key_len(self) -> Option<usize> {
match self {
Self::Null => None,
Self::AesCbc | Self::AesCtr | Self::AesCcm8 | Self::AesGcm16 => Some(16),
Self::ChaCha20Poly1305 => Some(32),
Self::Unknown(_) => None,
}
}
pub const fn iv_len(self) -> usize {
match self {
Self::Null => 0,
Self::AesCbc => 16,
Self::AesCtr | Self::AesCcm8 | Self::AesGcm16 | Self::ChaCha20Poly1305 => 8,
Self::Unknown(_) => 0,
}
}
pub const fn salt_len(self) -> usize {
match self {
Self::AesCtr | Self::AesGcm16 | Self::ChaCha20Poly1305 => 4,
Self::AesCcm8 => 3,
Self::Null | Self::AesCbc | Self::Unknown(_) => 0,
}
}
pub const fn block_size(self) -> usize {
match self {
Self::AesCbc => 16,
Self::Null
| Self::AesCtr
| Self::AesCcm8
| Self::AesGcm16
| Self::ChaCha20Poly1305
| Self::Unknown(_) => 1,
}
}
pub const fn icv_len(self) -> Option<usize> {
match self {
Self::AesGcm16 | Self::ChaCha20Poly1305 => Some(16),
Self::AesCcm8 => Some(8),
Self::Null | Self::AesCbc | Self::AesCtr | Self::Unknown(_) => None,
}
}
pub fn cipher_transform(self) -> Result<CipherTransform> {
match self {
Self::Null => Ok(CipherTransform::Null),
Self::AesCbc => Ok(CipherTransform::AesCbc),
Self::AesCtr => Ok(CipherTransform::AesCtr),
Self::AesCcm8 | Self::AesGcm16 | Self::ChaCha20Poly1305 => {
Err(CrafterError::invalid_field_value(
"ipsec.sa.encryption",
"AEAD algorithm has no separate cipher transform",
))
}
Self::Unknown(_) => Err(CrafterError::invalid_field_value(
"ipsec.sa.encryption",
"unknown encryption transform ID has no cipher transform",
)),
}
}
pub fn aead_transform(self) -> Result<AeadTransform> {
match self {
Self::AesGcm16 => Ok(AeadTransform::AesGcm16),
Self::AesCcm8 => Ok(AeadTransform::AesCcm8),
Self::ChaCha20Poly1305 => Ok(AeadTransform::ChaCha20Poly1305),
Self::Null | Self::AesCbc | Self::AesCtr => Err(CrafterError::invalid_field_value(
"ipsec.sa.encryption",
"non-AEAD algorithm has no AEAD transform",
)),
Self::Unknown(_) => Err(CrafterError::invalid_field_value(
"ipsec.sa.encryption",
"unknown encryption transform ID has no AEAD transform",
)),
}
}
}
impl From<u16> for EncryptionAlgorithm {
fn from(id: u16) -> Self {
match id {
ENCR_NULL => Self::Null,
ENCR_AES_CBC => Self::AesCbc,
ENCR_AES_CTR => Self::AesCtr,
ENCR_AES_CCM_8 => Self::AesCcm8,
ENCR_AES_GCM_16 => Self::AesGcm16,
ENCR_CHACHA20_POLY1305 => Self::ChaCha20Poly1305,
other => Self::Unknown(other),
}
}
}
impl From<EncryptionAlgorithm> for u16 {
fn from(algorithm: EncryptionAlgorithm) -> Self {
algorithm.transform_id()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntegrityAlgorithm {
None,
HmacSha1_96,
AesXcbc96,
AesGmac,
HmacSha2_256_128,
HmacSha2_384_192,
HmacSha2_512_256,
Unknown(u16),
}
impl IntegrityAlgorithm {
pub const fn transform_id(self) -> u16 {
match self {
Self::None => AUTH_NONE,
Self::HmacSha1_96 => AUTH_HMAC_SHA1_96,
Self::AesXcbc96 => AUTH_AES_XCBC_96,
Self::AesGmac => AUTH_AES_128_GMAC,
Self::HmacSha2_256_128 => AUTH_HMAC_SHA2_256_128,
Self::HmacSha2_384_192 => AUTH_HMAC_SHA2_384_192,
Self::HmacSha2_512_256 => AUTH_HMAC_SHA2_512_256,
Self::Unknown(id) => id,
}
}
pub const fn icv_len(self) -> Option<usize> {
match self {
Self::None => None,
Self::HmacSha1_96 => Some(12), Self::AesXcbc96 => Some(12), Self::AesGmac => Some(16), Self::HmacSha2_256_128 => Some(16), Self::HmacSha2_384_192 => Some(24), Self::HmacSha2_512_256 => Some(32), Self::Unknown(_) => None,
}
}
pub fn integrity_transform(self) -> Result<IntegrityTransform> {
match self {
Self::HmacSha1_96 => Ok(IntegrityTransform::HmacSha1_96),
Self::AesXcbc96 => Ok(IntegrityTransform::AesXcbcMac96),
Self::AesGmac => Ok(IntegrityTransform::AesGmac),
Self::HmacSha2_256_128 => Ok(IntegrityTransform::HmacSha2_256_128),
Self::HmacSha2_384_192 => Ok(IntegrityTransform::HmacSha2_384_192),
Self::HmacSha2_512_256 => Ok(IntegrityTransform::HmacSha2_512_256),
Self::None => Err(CrafterError::invalid_field_value(
"ipsec.sa.integrity",
"NONE integrity has no transform",
)),
Self::Unknown(_) => Err(CrafterError::invalid_field_value(
"ipsec.sa.integrity",
"unknown integrity transform ID has no transform",
)),
}
}
}
impl From<u16> for IntegrityAlgorithm {
fn from(id: u16) -> Self {
match id {
AUTH_NONE => Self::None,
AUTH_HMAC_SHA1_96 => Self::HmacSha1_96,
AUTH_AES_XCBC_96 => Self::AesXcbc96,
AUTH_AES_128_GMAC => Self::AesGmac,
AUTH_HMAC_SHA2_256_128 => Self::HmacSha2_256_128,
AUTH_HMAC_SHA2_384_192 => Self::HmacSha2_384_192,
AUTH_HMAC_SHA2_512_256 => Self::HmacSha2_512_256,
other => Self::Unknown(other),
}
}
}
impl From<IntegrityAlgorithm> for u16 {
fn from(algorithm: IntegrityAlgorithm) -> Self {
algorithm.transform_id()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encryption_transform_ids_match_iana() {
assert_eq!(EncryptionAlgorithm::Null.transform_id(), 11);
assert_eq!(EncryptionAlgorithm::AesCbc.transform_id(), 12);
assert_eq!(EncryptionAlgorithm::AesCtr.transform_id(), 13);
assert_eq!(EncryptionAlgorithm::AesCcm8.transform_id(), 14);
assert_eq!(EncryptionAlgorithm::AesGcm16.transform_id(), 20);
assert_eq!(EncryptionAlgorithm::ChaCha20Poly1305.transform_id(), 28);
}
#[test]
fn encryption_id_constants_match_manifest() {
assert_eq!(ENCR_NULL, 11);
assert_eq!(ENCR_AES_CBC, 12);
assert_eq!(ENCR_AES_CTR, 13);
assert_eq!(ENCR_AES_CCM_8, 14);
assert_eq!(ENCR_AES_GCM_16, 20);
assert_eq!(ENCR_CHACHA20_POLY1305, 28);
}
#[allow(clippy::unnecessary_fallible_conversions)]
#[test]
fn encryption_from_u16_round_trips() {
for algo in [
EncryptionAlgorithm::Null,
EncryptionAlgorithm::AesCbc,
EncryptionAlgorithm::AesCtr,
EncryptionAlgorithm::AesCcm8,
EncryptionAlgorithm::AesGcm16,
EncryptionAlgorithm::ChaCha20Poly1305,
] {
let id = algo.transform_id();
assert_eq!(EncryptionAlgorithm::from(id), algo);
assert_eq!(u16::from(algo), id);
assert_eq!(EncryptionAlgorithm::try_from(id).unwrap(), algo);
}
}
#[test]
fn encryption_unknown_id_is_preserved() {
let algo = EncryptionAlgorithm::from(99);
assert_eq!(algo, EncryptionAlgorithm::Unknown(99));
assert_eq!(algo.transform_id(), 99);
assert_eq!(u16::from(algo), 99);
assert!(algo.cipher_transform().is_err());
assert!(algo.aead_transform().is_err());
}
#[test]
fn encryption_aead_classification() {
assert!(!EncryptionAlgorithm::Null.is_aead());
assert!(!EncryptionAlgorithm::AesCbc.is_aead());
assert!(!EncryptionAlgorithm::AesCtr.is_aead());
assert!(EncryptionAlgorithm::AesCcm8.is_aead());
assert!(EncryptionAlgorithm::AesGcm16.is_aead());
assert!(EncryptionAlgorithm::ChaCha20Poly1305.is_aead());
assert!(!EncryptionAlgorithm::Unknown(99).is_aead());
}
#[test]
fn encryption_metadata_matches_rfcs() {
let null = EncryptionAlgorithm::Null;
assert_eq!(null.key_len(), None);
assert_eq!(null.iv_len(), 0);
assert_eq!(null.salt_len(), 0);
assert_eq!(null.block_size(), 1);
assert_eq!(null.icv_len(), None);
let cbc = EncryptionAlgorithm::AesCbc;
assert_eq!(cbc.key_len(), Some(16));
assert_eq!(cbc.iv_len(), 16);
assert_eq!(cbc.salt_len(), 0);
assert_eq!(cbc.block_size(), 16);
assert_eq!(cbc.icv_len(), None);
let ctr = EncryptionAlgorithm::AesCtr;
assert_eq!(ctr.key_len(), Some(16));
assert_eq!(ctr.iv_len(), 8);
assert_eq!(ctr.salt_len(), 4);
assert_eq!(ctr.block_size(), 1);
assert_eq!(ctr.icv_len(), None);
let ccm = EncryptionAlgorithm::AesCcm8;
assert_eq!(ccm.key_len(), Some(16));
assert_eq!(ccm.iv_len(), 8);
assert_eq!(ccm.salt_len(), 3);
assert_eq!(ccm.block_size(), 1);
assert_eq!(ccm.icv_len(), Some(8));
let gcm = EncryptionAlgorithm::AesGcm16;
assert_eq!(gcm.key_len(), Some(16));
assert_eq!(gcm.iv_len(), 8);
assert_eq!(gcm.salt_len(), 4);
assert_eq!(gcm.block_size(), 1);
assert_eq!(gcm.icv_len(), Some(16));
let cc = EncryptionAlgorithm::ChaCha20Poly1305;
assert_eq!(cc.key_len(), Some(32));
assert_eq!(cc.iv_len(), 8);
assert_eq!(cc.salt_len(), 4);
assert_eq!(cc.block_size(), 1);
assert_eq!(cc.icv_len(), Some(16));
}
#[test]
fn encryption_transform_resolution_matches_crypto_primitives() {
assert_eq!(
EncryptionAlgorithm::Null.cipher_transform().unwrap(),
CipherTransform::Null
);
assert_eq!(
EncryptionAlgorithm::AesCbc.cipher_transform().unwrap(),
CipherTransform::AesCbc
);
assert_eq!(
EncryptionAlgorithm::AesCtr.cipher_transform().unwrap(),
CipherTransform::AesCtr
);
assert_eq!(
EncryptionAlgorithm::AesGcm16.aead_transform().unwrap(),
AeadTransform::AesGcm16
);
assert_eq!(
EncryptionAlgorithm::AesCcm8.aead_transform().unwrap(),
AeadTransform::AesCcm8
);
assert_eq!(
EncryptionAlgorithm::ChaCha20Poly1305
.aead_transform()
.unwrap(),
AeadTransform::ChaCha20Poly1305
);
assert!(EncryptionAlgorithm::AesGcm16.cipher_transform().is_err());
assert!(EncryptionAlgorithm::AesCbc.aead_transform().is_err());
for algo in [
EncryptionAlgorithm::AesGcm16,
EncryptionAlgorithm::AesCcm8,
EncryptionAlgorithm::ChaCha20Poly1305,
] {
let aead = algo.aead_transform().unwrap();
assert_eq!(algo.icv_len(), Some(aead.icv_len()));
assert_eq!(algo.key_len(), Some(aead.key_len()));
assert_eq!(algo.iv_len(), aead.iv_len());
assert_eq!(algo.salt_len(), aead.salt_len());
}
for algo in [
EncryptionAlgorithm::AesCbc,
EncryptionAlgorithm::AesCtr,
EncryptionAlgorithm::Null,
] {
let cipher = algo.cipher_transform().unwrap();
assert_eq!(algo.iv_len(), cipher.iv_len());
assert_eq!(algo.block_size(), cipher.block_size());
}
}
#[test]
fn integrity_transform_ids_match_iana() {
assert_eq!(IntegrityAlgorithm::None.transform_id(), 0);
assert_eq!(IntegrityAlgorithm::HmacSha1_96.transform_id(), 2);
assert_eq!(IntegrityAlgorithm::AesXcbc96.transform_id(), 5);
assert_eq!(IntegrityAlgorithm::AesGmac.transform_id(), 9);
assert_eq!(IntegrityAlgorithm::HmacSha2_256_128.transform_id(), 12);
assert_eq!(IntegrityAlgorithm::HmacSha2_384_192.transform_id(), 13);
assert_eq!(IntegrityAlgorithm::HmacSha2_512_256.transform_id(), 14);
}
#[test]
fn integrity_id_constants_match_manifest() {
assert_eq!(AUTH_NONE, 0);
assert_eq!(AUTH_HMAC_SHA1_96, 2);
assert_eq!(AUTH_AES_XCBC_96, 5);
assert_eq!(AUTH_AES_128_GMAC, 9);
assert_eq!(AUTH_HMAC_SHA2_256_128, 12);
assert_eq!(AUTH_HMAC_SHA2_384_192, 13);
assert_eq!(AUTH_HMAC_SHA2_512_256, 14);
}
#[allow(clippy::unnecessary_fallible_conversions)]
#[test]
fn integrity_from_u16_round_trips() {
for algo in [
IntegrityAlgorithm::None,
IntegrityAlgorithm::HmacSha1_96,
IntegrityAlgorithm::AesXcbc96,
IntegrityAlgorithm::AesGmac,
IntegrityAlgorithm::HmacSha2_256_128,
IntegrityAlgorithm::HmacSha2_384_192,
IntegrityAlgorithm::HmacSha2_512_256,
] {
let id = algo.transform_id();
assert_eq!(IntegrityAlgorithm::from(id), algo);
assert_eq!(u16::from(algo), id);
assert_eq!(IntegrityAlgorithm::try_from(id).unwrap(), algo);
}
}
#[test]
fn integrity_unknown_id_is_preserved() {
let algo = IntegrityAlgorithm::from(200);
assert_eq!(algo, IntegrityAlgorithm::Unknown(200));
assert_eq!(algo.transform_id(), 200);
assert_eq!(u16::from(algo), 200);
assert_eq!(algo.icv_len(), None);
assert!(algo.integrity_transform().is_err());
}
#[test]
fn integrity_icv_lengths_match_rfcs() {
assert_eq!(IntegrityAlgorithm::None.icv_len(), None);
assert_eq!(IntegrityAlgorithm::HmacSha1_96.icv_len(), Some(12));
assert_eq!(IntegrityAlgorithm::AesXcbc96.icv_len(), Some(12));
assert_eq!(IntegrityAlgorithm::AesGmac.icv_len(), Some(16));
assert_eq!(IntegrityAlgorithm::HmacSha2_256_128.icv_len(), Some(16));
assert_eq!(IntegrityAlgorithm::HmacSha2_384_192.icv_len(), Some(24));
assert_eq!(IntegrityAlgorithm::HmacSha2_512_256.icv_len(), Some(32));
}
#[test]
fn integrity_transform_resolution_and_icv_agree() {
let cases = [
(
IntegrityAlgorithm::HmacSha1_96,
IntegrityTransform::HmacSha1_96,
),
(
IntegrityAlgorithm::AesXcbc96,
IntegrityTransform::AesXcbcMac96,
),
(IntegrityAlgorithm::AesGmac, IntegrityTransform::AesGmac),
(
IntegrityAlgorithm::HmacSha2_256_128,
IntegrityTransform::HmacSha2_256_128,
),
(
IntegrityAlgorithm::HmacSha2_384_192,
IntegrityTransform::HmacSha2_384_192,
),
(
IntegrityAlgorithm::HmacSha2_512_256,
IntegrityTransform::HmacSha2_512_256,
),
];
for (algo, transform) in cases {
assert_eq!(algo.integrity_transform().unwrap(), transform);
assert_eq!(algo.icv_len(), Some(transform.icv_len()));
}
assert!(IntegrityAlgorithm::None.integrity_transform().is_err());
}
}