use aes::cipher::generic_array::GenericArray;
use aes::cipher::{BlockEncrypt, KeyInit as AesKeyInit};
use aes::Aes128;
use aes_gcm::aead::AeadInPlace;
use aes_gcm::{AesGcm, KeyInit as GcmKeyInit};
use cipher::consts::U12;
use hmac::{Hmac, Mac};
use sha1::Sha1;
use sha2::{Sha256, Sha384, Sha512};
use subtle::ConstantTimeEq;
use crate::{CrafterError, Result};
const AES_BLOCK_LEN: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntegrityTransform {
HmacSha1_96,
HmacSha2_256_128,
HmacSha2_384_192,
HmacSha2_512_256,
AesXcbcMac96,
AesGmac,
}
impl IntegrityTransform {
pub const fn icv_len(self) -> usize {
match self {
Self::HmacSha1_96 => 12, Self::HmacSha2_256_128 => 16, Self::HmacSha2_384_192 => 24, Self::HmacSha2_512_256 => 32, Self::AesXcbcMac96 => 12, Self::AesGmac => 16, }
}
pub const fn key_len(self) -> Option<usize> {
match self {
Self::HmacSha1_96
| Self::HmacSha2_256_128
| Self::HmacSha2_384_192
| Self::HmacSha2_512_256 => None,
Self::AesXcbcMac96 => Some(16),
Self::AesGmac => None,
}
}
pub fn compute(self, key: &[u8], message: &[u8]) -> Result<Vec<u8>> {
match self {
Self::HmacSha1_96 => hmac_sha1(key, message, self.icv_len()),
Self::HmacSha2_256_128 => hmac_sha256(key, message, self.icv_len()),
Self::HmacSha2_384_192 => hmac_sha384(key, message, self.icv_len()),
Self::HmacSha2_512_256 => hmac_sha512(key, message, self.icv_len()),
Self::AesXcbcMac96 => {
let mac = aes_xcbc_mac(key, message)?;
Ok(mac[..self.icv_len()].to_vec())
}
Self::AesGmac => aes_gmac(key, message),
}
}
pub fn verify(self, key: &[u8], message: &[u8], tag: &[u8]) -> Result<bool> {
let expected = self.compute(key, message)?;
if expected.len() != tag.len() {
return Ok(false);
}
Ok(expected.ct_eq(tag).into())
}
}
fn hmac_sha1(key: &[u8], message: &[u8], icv_len: usize) -> Result<Vec<u8>> {
let mut mac = <Hmac<Sha1> as Mac>::new_from_slice(key).map_err(|_| {
CrafterError::invalid_field_value(
"ipsec.integrity.hmac_sha1.key",
"HMAC-SHA1 key is invalid",
)
})?;
mac.update(message);
Ok(mac.finalize().into_bytes()[..icv_len].to_vec())
}
fn hmac_sha256(key: &[u8], message: &[u8], icv_len: usize) -> Result<Vec<u8>> {
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(key).map_err(|_| {
CrafterError::invalid_field_value(
"ipsec.integrity.hmac_sha256.key",
"HMAC-SHA256 key is invalid",
)
})?;
mac.update(message);
Ok(mac.finalize().into_bytes()[..icv_len].to_vec())
}
fn hmac_sha384(key: &[u8], message: &[u8], icv_len: usize) -> Result<Vec<u8>> {
let mut mac = <Hmac<Sha384> as Mac>::new_from_slice(key).map_err(|_| {
CrafterError::invalid_field_value(
"ipsec.integrity.hmac_sha384.key",
"HMAC-SHA384 key is invalid",
)
})?;
mac.update(message);
Ok(mac.finalize().into_bytes()[..icv_len].to_vec())
}
fn hmac_sha512(key: &[u8], message: &[u8], icv_len: usize) -> Result<Vec<u8>> {
let mut mac = <Hmac<Sha512> as Mac>::new_from_slice(key).map_err(|_| {
CrafterError::invalid_field_value(
"ipsec.integrity.hmac_sha512.key",
"HMAC-SHA512 key is invalid",
)
})?;
mac.update(message);
Ok(mac.finalize().into_bytes()[..icv_len].to_vec())
}
fn aes_xcbc_mac(key: &[u8], message: &[u8]) -> Result<[u8; AES_BLOCK_LEN]> {
if key.len() != AES_BLOCK_LEN {
return Err(CrafterError::invalid_field_value(
"ipsec.integrity.aes_xcbc.key",
"AES-XCBC-MAC requires a 16-octet AES-128 key",
));
}
let cipher = Aes128::new(GenericArray::from_slice(key));
let k1 = aes_encrypt_block(&cipher, &[0x01; AES_BLOCK_LEN]);
let k2 = aes_encrypt_block(&cipher, &[0x02; AES_BLOCK_LEN]);
let k3 = aes_encrypt_block(&cipher, &[0x03; AES_BLOCK_LEN]);
let k1_cipher = Aes128::new(GenericArray::from_slice(&k1));
let mut e = [0u8; AES_BLOCK_LEN];
if message.is_empty() {
let mut block = [0u8; AES_BLOCK_LEN];
block[0] = 0x80;
xor_into(&mut block, &k3);
return Ok(aes_encrypt_block(&k1_cipher, &block));
}
let full_blocks = message.len() / AES_BLOCK_LEN;
let remainder = message.len() % AES_BLOCK_LEN;
let n_minus_1 = if remainder == 0 {
full_blocks - 1
} else {
full_blocks
};
for i in 0..n_minus_1 {
let start = i * AES_BLOCK_LEN;
let mut block = [0u8; AES_BLOCK_LEN];
block.copy_from_slice(&message[start..start + AES_BLOCK_LEN]);
xor_into(&mut e, &block);
e = aes_encrypt_block(&k1_cipher, &e);
}
let last_start = n_minus_1 * AES_BLOCK_LEN;
let last = &message[last_start..];
let mut final_block = [0u8; AES_BLOCK_LEN];
if last.len() == AES_BLOCK_LEN {
final_block.copy_from_slice(last);
xor_into(&mut final_block, &k2);
} else {
final_block[..last.len()].copy_from_slice(last);
final_block[last.len()] = 0x80;
xor_into(&mut final_block, &k3);
}
xor_into(&mut e, &final_block);
Ok(aes_encrypt_block(&k1_cipher, &e))
}
fn aes_encrypt_block(cipher: &Aes128, block: &[u8; AES_BLOCK_LEN]) -> [u8; AES_BLOCK_LEN] {
let mut buf = GenericArray::clone_from_slice(block);
cipher.encrypt_block(&mut buf);
let mut out = [0u8; AES_BLOCK_LEN];
out.copy_from_slice(&buf);
out
}
fn xor_into(dst: &mut [u8; AES_BLOCK_LEN], src: &[u8; AES_BLOCK_LEN]) {
for (d, s) in dst.iter_mut().zip(src.iter()) {
*d ^= *s;
}
}
fn aes_gmac(key: &[u8], message: &[u8]) -> Result<Vec<u8>> {
const AES128_KEY_LEN: usize = 16;
const SALT_LEN: usize = 4;
const IV_LEN: usize = 8;
const EXPECTED: usize = AES128_KEY_LEN + SALT_LEN + IV_LEN;
if key.len() != EXPECTED {
return Err(CrafterError::invalid_field_value(
"ipsec.integrity.aes_gmac.key",
"AES-GMAC requires aes_key(16) || salt(4) || iv(8)",
));
}
let aes_key = &key[..AES128_KEY_LEN];
let nonce = &key[AES128_KEY_LEN..];
type Gmac128 = AesGcm<Aes128, U12>;
let gmac = <Gmac128 as GcmKeyInit>::new(GenericArray::from_slice(aes_key));
let tag = gmac
.encrypt_in_place_detached(GenericArray::from_slice(nonce), message, &mut [])
.map_err(|_| {
CrafterError::invalid_field_value("ipsec.integrity.aes_gmac", "GMAC computation failed")
})?;
Ok(tag.to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(s: &str) -> Vec<u8> {
let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
assert!(s.len() % 2 == 0, "hex string must have even length");
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("valid hex"))
.collect()
}
#[test]
fn hmac_sha1_96_rfc2202_case2() {
let key = b"Jefe";
let message = b"what do ya want for nothing?";
let full = hex("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");
let tag = IntegrityTransform::HmacSha1_96
.compute(key, message)
.unwrap();
assert_eq!(IntegrityTransform::HmacSha1_96.icv_len(), 12);
assert_eq!(tag, full[..12]);
assert!(IntegrityTransform::HmacSha1_96
.verify(key, message, &tag)
.unwrap());
let mut bad = tag.clone();
bad[0] ^= 0x01;
assert!(!IntegrityTransform::HmacSha1_96
.verify(key, message, &bad)
.unwrap());
}
#[test]
fn hmac_sha2_256_128_rfc4868_case2() {
let key = b"Jefe";
let message = b"what do ya want for nothing?";
let full = hex("5bdcc146bf60754e6a042426089575c7\
5a003f089d2739839dec58b964ec3843");
let tag = IntegrityTransform::HmacSha2_256_128
.compute(key, message)
.unwrap();
assert_eq!(IntegrityTransform::HmacSha2_256_128.icv_len(), 16);
assert_eq!(tag, full[..16]);
assert!(IntegrityTransform::HmacSha2_256_128
.verify(key, message, &tag)
.unwrap());
}
#[test]
fn hmac_sha2_384_192_rfc4868_case2() {
let key = b"Jefe";
let message = b"what do ya want for nothing?";
let full = hex("af45d2e376484031617f78d2b58a6b1b\
9c7ef464f5a01b47e42ec3736322445e\
8e2240ca5e69e2c78b3239ecfab21649");
let tag = IntegrityTransform::HmacSha2_384_192
.compute(key, message)
.unwrap();
assert_eq!(IntegrityTransform::HmacSha2_384_192.icv_len(), 24);
assert_eq!(tag, full[..24]);
assert!(IntegrityTransform::HmacSha2_384_192
.verify(key, message, &tag)
.unwrap());
}
#[test]
fn hmac_sha2_512_256_rfc4868_case2() {
let key = b"Jefe";
let message = b"what do ya want for nothing?";
let full = hex("164b7a7bfcf819e2e395fbe73b56e0a3\
87bd64222e831fd610270cd7ea250554\
9758bf75c05a994a6d034f65f8f0e6fd\
caeab1a34d4a6b4b636e070a38bce737");
let tag = IntegrityTransform::HmacSha2_512_256
.compute(key, message)
.unwrap();
assert_eq!(IntegrityTransform::HmacSha2_512_256.icv_len(), 32);
assert_eq!(tag, full[..32]);
assert!(IntegrityTransform::HmacSha2_512_256
.verify(key, message, &tag)
.unwrap());
}
const XCBC_KEY: &str = "000102030405060708090a0b0c0d0e0f";
#[test]
fn aes_xcbc_mac_rfc3566_case1_len0() {
let key = hex(XCBC_KEY);
let full = aes_xcbc_mac(&key, &[]).unwrap();
assert_eq!(full.to_vec(), hex("75f0251d528ac01c4573dfd584d79f29"));
}
#[test]
fn aes_xcbc_mac_rfc3566_case2_len3() {
let key = hex(XCBC_KEY);
let message = hex("000102");
let full = aes_xcbc_mac(&key, &message).unwrap();
assert_eq!(full.to_vec(), hex("5b376580ae2f19afe7219ceef172756f"));
let icv = IntegrityTransform::AesXcbcMac96
.compute(&key, &message)
.unwrap();
assert_eq!(IntegrityTransform::AesXcbcMac96.icv_len(), 12);
assert_eq!(icv, full[..12].to_vec());
assert!(IntegrityTransform::AesXcbcMac96
.verify(&key, &message, &icv)
.unwrap());
}
#[test]
fn aes_xcbc_mac_rfc3566_case3_len16() {
let key = hex(XCBC_KEY);
let message = hex("000102030405060708090a0b0c0d0e0f");
let full = aes_xcbc_mac(&key, &message).unwrap();
assert_eq!(full.to_vec(), hex("d2a246fa349b68a79998a4394ff7a263"));
}
#[test]
fn aes_xcbc_mac_rfc3566_case5_len20() {
let key = hex(XCBC_KEY);
let message = hex("000102030405060708090a0b0c0d0e0f10111213");
let full = aes_xcbc_mac(&key, &message).unwrap();
assert_eq!(full.to_vec(), hex("47f51b4564966215b8985c63055ed308"));
}
#[test]
fn aes_xcbc_mac_rfc3566_case7_len32() {
let key = hex(XCBC_KEY);
let message = hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
let full = aes_xcbc_mac(&key, &message).unwrap();
assert_eq!(full.to_vec(), hex("f54f0ec8d2b9f3d36807734bd5283fd4"));
}
#[test]
fn aes_gmac_gcm_appendix_b_case1_empty() {
let aes_key = hex("00000000000000000000000000000000");
let nonce = hex("000000000000000000000000");
let mut key = Vec::new();
key.extend_from_slice(&aes_key);
key.extend_from_slice(&nonce);
let icv = IntegrityTransform::AesGmac.compute(&key, &[]).unwrap();
assert_eq!(IntegrityTransform::AesGmac.icv_len(), 16);
assert_eq!(icv, hex("58e2fccefa7e3061367f1d57a4e7455a"));
assert!(IntegrityTransform::AesGmac.verify(&key, &[], &icv).unwrap());
}
#[test]
fn aes_gmac_gcm_appendix_b_case4_aad() {
let aes_key = hex("feffe9928665731c6d6a8f9467308308");
let nonce = hex("cafebabefacedbaddecaf888"); let aad = hex("feedfacedeadbeeffeedfacedeadbeefabaddad2");
let mut key = Vec::new();
key.extend_from_slice(&aes_key);
key.extend_from_slice(&nonce);
let icv = IntegrityTransform::AesGmac.compute(&key, &aad).unwrap();
assert_eq!(icv, hex("346434fd51d5cd0c5887ec63e39b907a"));
assert!(IntegrityTransform::AesGmac
.verify(&key, &aad, &icv)
.unwrap());
let mut bad_aad = aad.clone();
bad_aad[0] ^= 0x01;
assert!(!IntegrityTransform::AesGmac
.verify(&key, &bad_aad, &icv)
.unwrap());
}
#[test]
fn aes_gmac_roundtrip_and_key_guard() {
let key = hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b");
assert_eq!(key.len(), 16 + 4 + 8);
let message = b"GMAC authenticates this AAD";
let icv = IntegrityTransform::AesGmac.compute(&key, message).unwrap();
assert!(IntegrityTransform::AesGmac
.verify(&key, message, &icv)
.unwrap());
let short = hex("00112233");
assert!(IntegrityTransform::AesGmac
.compute(&short, message)
.is_err());
}
#[test]
fn aes_xcbc_key_guard() {
let bad = hex("0011223344");
assert!(aes_xcbc_mac(&bad, b"x").is_err());
}
}