use hmac::{Hmac, Mac};
use sha2::Sha256;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Suite {
AesGcm256Sha256Dh19,
AesCbc256Sha256Dh19,
}
#[derive(Debug, Clone, Copy)]
pub struct SuiteParams {
pub suite: Suite,
pub encr_id: u16,
pub encr_keylen_bits: u16,
pub encr_key_bytes: usize,
pub encr_salt_bytes: usize,
pub encr_iv_bytes: usize,
pub encr_icv_bytes: usize,
pub aead: bool,
pub integ_id: Option<u16>,
pub integ_key_bytes: usize,
pub prf_bytes: usize,
}
impl Suite {
pub const fn params(self) -> SuiteParams {
match self {
Suite::AesGcm256Sha256Dh19 => SuiteParams {
suite: self,
encr_id: 20, encr_keylen_bits: 256,
encr_key_bytes: 32,
encr_salt_bytes: 4,
encr_iv_bytes: 8,
encr_icv_bytes: 16,
aead: true,
integ_id: None,
integ_key_bytes: 0,
prf_bytes: 32,
},
Suite::AesCbc256Sha256Dh19 => SuiteParams {
suite: self,
encr_id: 12, encr_keylen_bits: 256,
encr_key_bytes: 32,
encr_salt_bytes: 0,
encr_iv_bytes: 16,
encr_icv_bytes: 16,
aead: false,
integ_id: Some(12), integ_key_bytes: 32,
prf_bytes: 32,
},
}
}
pub fn supported() -> &'static [Suite] {
&[Suite::AesGcm256Sha256Dh19, Suite::AesCbc256Sha256Dh19]
}
}
impl SuiteParams {
pub const fn sk_e_len(&self) -> usize {
self.encr_key_bytes + self.encr_salt_bytes
}
pub const fn keymat_len(&self) -> usize {
self.prf_bytes + self.integ_key_bytes * 2 + self.sk_e_len() * 2 + self.prf_bytes * 2 }
pub const fn child_per_dir_len(&self) -> usize {
self.sk_e_len() + self.integ_key_bytes
}
}
#[allow(missing_docs)]
pub mod suite {
use super::Suite;
const P: super::SuiteParams = Suite::AesGcm256Sha256Dh19.params();
pub const ENCR_KEY_LEN: usize = P.encr_key_bytes;
pub const ENCR_SALT_LEN: usize = P.encr_salt_bytes;
pub const ENCR_IV_LEN: usize = P.encr_iv_bytes;
pub const ENCR_ICV_LEN: usize = P.encr_icv_bytes;
pub const PRF_KEY_LEN: usize = P.prf_bytes;
pub const SK_D_LEN: usize = P.prf_bytes;
pub const SK_P_LEN: usize = P.prf_bytes;
pub const SK_E_LEN: usize = P.encr_key_bytes + P.encr_salt_bytes;
pub const KEYMAT_LEN: usize = P.keymat_len();
}
type HmacSha256 = Hmac<Sha256>;
pub fn prf(key: &[u8], data: &[u8]) -> [u8; 32] {
let mut mac =
<HmacSha256 as hmac::KeyInit>::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(data);
mac.finalize().into_bytes().into()
}
pub fn prf_plus(key: &[u8], data: &[u8], out_len: usize) -> Vec<u8> {
let mut out = Vec::with_capacity(out_len);
let mut t: Vec<u8> = Vec::new();
let mut counter: u8 = 1;
while out.len() < out_len {
let mut input = Vec::with_capacity(t.len() + data.len() + 1);
input.extend_from_slice(&t);
input.extend_from_slice(data);
input.push(counter);
t = prf(key, &input).to_vec();
out.extend_from_slice(&t);
counter = counter.checked_add(1).expect("prf+ exceeds 255 iterations");
}
out.truncate(out_len);
out
}
pub struct DhEphemeral {
secret: p256::ecdh::EphemeralSecret,
}
impl DhEphemeral {
pub fn generate() -> Self {
Self {
secret: p256::ecdh::EphemeralSecret::random(&mut rand_core_06::OsRng),
}
}
pub fn public_share(&self) -> [u8; 64] {
use p256::elliptic_curve::sec1::ToEncodedPoint;
let pk = self.secret.public_key();
let encoded = pk.to_encoded_point(false); let bytes = encoded.as_bytes();
debug_assert_eq!(bytes.len(), 65);
let mut out = [0u8; 64];
out.copy_from_slice(&bytes[1..]);
out
}
pub fn diffie_hellman(self, peer: &[u8]) -> Result<[u8; 32], CryptoError> {
if peer.len() != 64 {
return Err(CryptoError::InvalidPeerShare);
}
let mut uncompressed = [0u8; 65];
uncompressed[0] = 0x04;
uncompressed[1..].copy_from_slice(peer);
let pk = p256::PublicKey::from_sec1_bytes(&uncompressed)
.map_err(|_| CryptoError::InvalidPeerShare)?;
let shared = self.secret.diffie_hellman(&pk);
let bytes = shared.raw_secret_bytes();
let mut out = [0u8; 32];
out.copy_from_slice(bytes);
Ok(out)
}
}
use aes_gcm::{
Aes256Gcm,
aead::{AeadInOut, KeyInit, Nonce, Tag, inout::InOutBuf},
};
pub fn aes_gcm_seal(
key: &[u8],
salt: &[u8],
iv: &[u8],
aad: &[u8],
buf: &mut Vec<u8>,
) -> Result<[u8; 16], CryptoError> {
if key.len() != 32 || salt.len() != 4 || iv.len() != 8 {
return Err(CryptoError::AeadFailed);
}
let cipher = Aes256Gcm::new_from_slice(key).expect("32-byte key");
let mut nonce_bytes = [0u8; 12];
nonce_bytes[..4].copy_from_slice(salt);
nonce_bytes[4..].copy_from_slice(iv);
let nonce = Nonce::<Aes256Gcm>::from(nonce_bytes);
let tag = cipher
.encrypt_inout_detached(&nonce, aad, InOutBuf::from(buf.as_mut_slice()))
.map_err(|_| CryptoError::AeadFailed)?;
Ok(tag.into())
}
pub fn aes_gcm_open(
key: &[u8],
salt: &[u8],
iv: &[u8],
aad: &[u8],
buf: &mut Vec<u8>,
tag: &[u8],
) -> Result<(), CryptoError> {
if key.len() != 32 || salt.len() != 4 || iv.len() != 8 || tag.len() != 16 {
return Err(CryptoError::AeadFailed);
}
let cipher = Aes256Gcm::new_from_slice(key).expect("32-byte key");
let mut nonce_bytes = [0u8; 12];
nonce_bytes[..4].copy_from_slice(salt);
nonce_bytes[4..].copy_from_slice(iv);
let nonce = Nonce::<Aes256Gcm>::from(nonce_bytes);
let mut tag_bytes = [0u8; 16];
tag_bytes.copy_from_slice(tag);
let tag = Tag::<Aes256Gcm>::from(tag_bytes);
cipher
.decrypt_inout_detached(&nonce, aad, InOutBuf::from(buf.as_mut_slice()), &tag)
.map_err(|_| CryptoError::AeadFailed)
}
use aes::Aes256;
use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::NoPadding};
type Aes256CbcEnc = cbc::Encryptor<Aes256>;
type Aes256CbcDec = cbc::Decryptor<Aes256>;
pub fn aes_cbc_256_encrypt(
key: &[u8; 32],
iv: &[u8; 16],
plaintext: &[u8],
) -> Result<Vec<u8>, CryptoError> {
if !plaintext.len().is_multiple_of(16) {
return Err(CryptoError::AeadFailed);
}
let cipher = Aes256CbcEnc::new(key.into(), iv.into());
let mut buf = plaintext.to_vec();
let len = buf.len();
cipher
.encrypt_padded::<NoPadding>(&mut buf, len)
.map_err(|_| CryptoError::AeadFailed)?;
Ok(buf)
}
pub fn aes_cbc_256_decrypt(
key: &[u8; 32],
iv: &[u8; 16],
ciphertext: &[u8],
) -> Result<Vec<u8>, CryptoError> {
if !ciphertext.len().is_multiple_of(16) {
return Err(CryptoError::AeadFailed);
}
let cipher = Aes256CbcDec::new(key.into(), iv.into());
let mut buf = ciphertext.to_vec();
cipher
.decrypt_padded::<NoPadding>(&mut buf)
.map_err(|_| CryptoError::AeadFailed)?;
Ok(buf)
}
pub fn hmac_sha256_128(key: &[u8], data: &[u8]) -> [u8; 16] {
let full = prf(key, data);
let mut out = [0u8; 16];
out.copy_from_slice(&full[..16]);
out
}
pub fn derive_auth_key(psk: &[u8]) -> [u8; 32] {
prf(psk, b"Key Pad for IKEv2")
}
#[derive(Debug, thiserror::Error)]
pub enum CryptoError {
#[error("invalid peer DH share")]
InvalidPeerShare,
#[error("AEAD encryption or decryption failed")]
AeadFailed,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aes_cbc_round_trip() {
let key = [0x42u8; 32];
let iv = [0x11u8; 16];
let plaintext = b"hello, ipsec, this is a 32-byteX";
assert_eq!(plaintext.len() % 16, 0);
let ct = aes_cbc_256_encrypt(&key, &iv, plaintext).unwrap();
assert_eq!(ct.len(), plaintext.len());
assert_ne!(&ct[..], &plaintext[..]);
let pt = aes_cbc_256_decrypt(&key, &iv, &ct).unwrap();
assert_eq!(&pt[..], &plaintext[..]);
}
#[test]
fn aes_cbc_rejects_unaligned() {
let key = [0u8; 32];
let iv = [0u8; 16];
let unaligned = [0u8; 17];
assert!(aes_cbc_256_encrypt(&key, &iv, &unaligned).is_err());
assert!(aes_cbc_256_decrypt(&key, &iv, &unaligned).is_err());
}
#[test]
fn hmac_sha256_128_truncates_to_16() {
let key = b"some-key";
let data = b"some-data";
let full = prf(key, data);
let trunc = hmac_sha256_128(key, data);
assert_eq!(&trunc[..], &full[..16]);
}
#[test]
fn hmac_known_vector() {
let key = [0x0bu8; 20];
let data = b"Hi There";
let out = prf(&key, data);
let expected = [
0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b,
0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
0x2e, 0x32, 0xcf, 0xf7,
];
assert_eq!(out, expected);
}
#[test]
fn prf_plus_chains() {
let key = b"some-prf-key";
let seed = b"some-seed-data";
let out = prf_plus(key, seed, 100);
assert_eq!(out.len(), 100);
let mut expected_t1 = Vec::from(&seed[..]);
expected_t1.push(0x01);
let t1 = prf(key, &expected_t1);
assert_eq!(&out[..32], &t1[..]);
let mut t2_input = Vec::from(&t1[..]);
t2_input.extend_from_slice(seed);
t2_input.push(0x02);
let t2 = prf(key, &t2_input);
assert_eq!(&out[32..64], &t2[..]);
}
#[test]
fn ecdh_round_trip() {
let a = DhEphemeral::generate();
let b = DhEphemeral::generate();
let a_pub = a.public_share();
let b_pub = b.public_share();
let ab = a.diffie_hellman(&b_pub).unwrap();
let ba = b.diffie_hellman(&a_pub).unwrap();
assert_eq!(ab, ba);
}
#[test]
fn aes_gcm_round_trip() {
let key = [0x42u8; 32];
let salt = [0x11u8; 4];
let iv = [0x22u8; 8];
let aad = b"associated-data";
let plaintext = b"hello, ipsec";
let mut buf = plaintext.to_vec();
let tag = aes_gcm_seal(&key, &salt, &iv, aad, &mut buf).unwrap();
assert_ne!(&buf[..], &plaintext[..]);
aes_gcm_open(&key, &salt, &iv, aad, &mut buf, &tag).unwrap();
assert_eq!(&buf[..], &plaintext[..]);
}
#[test]
fn aes_gcm_aad_tamper_fails() {
let key = [0x42u8; 32];
let salt = [0x11u8; 4];
let iv = [0x22u8; 8];
let mut buf = b"hello".to_vec();
let tag = aes_gcm_seal(&key, &salt, &iv, b"good-aad", &mut buf).unwrap();
assert!(aes_gcm_open(&key, &salt, &iv, b"BAD-aad", &mut buf, &tag).is_err());
}
}