Skip to content

Commit

Permalink
expose hmac and compatibility test it in the tuta-sdk
Browse files Browse the repository at this point in the history
TODO: make decisions about the somewhat redundant concats...
  • Loading branch information
vaf-hub committed Jan 9, 2025
1 parent 4dc57d3 commit 863249a
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 22 deletions.
1 change: 1 addition & 0 deletions tuta-sdk/rust/sdk/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub use sha::sha256;
pub use tuta_crypt::PQKeyPairs;

pub mod aes;
pub mod hmac;

mod sha;

Expand Down
52 changes: 30 additions & 22 deletions tuta-sdk/rust/sdk/src/crypto/aes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Contains code to handle AES128/AES256 encryption and decryption
use crate::crypto::hmac::{HmacError, MAC_SIZE};
use crate::crypto::randomizer_facade::RandomizerFacade;
use crate::join_slices;
use crate::util::{array_cast_size, array_cast_slice, ArrayCastingError};
Expand Down Expand Up @@ -231,7 +232,7 @@ pub enum AesDecryptError {
#[error("PaddingError")]
PaddingError(#[from] UnpadError),
#[error("HmacError")]
HmacError,
HmacError(#[from] HmacError),
}

/// Result of decryption operation.
Expand Down Expand Up @@ -302,9 +303,6 @@ pub const AES_256_KEY_SIZE: usize = 32;
/// The size of an AES initialisation vector in bytes
pub const IV_BYTE_SIZE: usize = 16;

/// Size of HMAC authentication added to the ciphertext
const MAC_SIZE: usize = 32;

/// Encrypts a plaintext without adding padding and returns the encrypted text as a vector
fn encrypt_unpadded_vec_mut<C: BlockCipher + BlockEncryptMut>(
encryptor: &mut cbc::Encryptor<C>,
Expand Down Expand Up @@ -333,13 +331,24 @@ type Aes256SubKeys = AesSubKeys<Aes256Key>;

impl<Key: AesKey> AesSubKeys<Key> {
fn compute_mac(&self, iv: &[u8], ciphertext: &[u8]) -> [u8; MAC_SIZE] {
use hmac::Mac;
use sha2::Sha256;

let mut hmac = hmac::Hmac::<Sha256>::new_from_slice(self.m_key.get_bytes()).unwrap();
hmac.update(iv);
hmac.update(ciphertext);
hmac.finalize().into_bytes().into()
use crate::crypto::hmac::hmac_sha256;
// TODO get rid of the concat?
hmac_sha256(self.m_key.get_bytes(), [iv, ciphertext].concat().as_slice())
}

fn verify_mac(
&self,
iv: &[u8],
ciphertext: &[u8],
tag: [u8; MAC_SIZE],
) -> Result<(), HmacError> {
use crate::crypto::hmac::verify_hmac_sha256;
verify_hmac_sha256(
self.m_key.get_bytes(),
// TODO get rid of the concat?
[iv, ciphertext].concat().as_slice(),
tag,
)
}
}

Expand Down Expand Up @@ -431,7 +440,7 @@ impl<'a> CiphertextWithAuthentication<'a> {

// Incorrect size for Hmac
if bytes.len() <= IV_BYTE_SIZE + MAC_SIZE {
return Err(AesDecryptError::HmacError);
return Err(AesDecryptError::HmacError(HmacError));
}

// Split `bytes` into the MAC and combined ciphertext with iv
Expand All @@ -442,8 +451,8 @@ impl<'a> CiphertextWithAuthentication<'a> {

// Extract the iv from the ciphertext and return the extracted components
let (iv, ciphertext) = ciphertext_without_mac.split_at(IV_BYTE_SIZE);
let mac: [u8; MAC_SIZE] =
array_cast_slice(provided_mac_bytes, "MAC").map_err(|_| AesDecryptError::HmacError)?;
let mac: [u8; MAC_SIZE] = array_cast_slice(provided_mac_bytes, "MAC")
.map_err(|_| AesDecryptError::HmacError(HmacError))?;
Ok(Some(CiphertextWithAuthentication {
iv,
ciphertext,
Expand All @@ -463,10 +472,6 @@ impl<'a> CiphertextWithAuthentication<'a> {
}
}

fn matches<Key: AesKey>(&self, subkeys: &AesSubKeys<Key>) -> bool {
self.mac == subkeys.compute_mac(self.iv, self.ciphertext)
}

fn serialize(&self) -> Vec<u8> {
// - marker that HMAC is there (a single byte with "1" in the front, this makes the length
// un-even)
Expand Down Expand Up @@ -499,17 +504,20 @@ fn aes_decrypt<Key: AesKey>(
let (key, iv_bytes, encrypted_bytes) =
if let Some(ciphertext_with_auth) = CiphertextWithAuthentication::parse(encrypted_bytes)? {
let subkeys = key.derive_subkeys();
if !ciphertext_with_auth.matches(&subkeys) {
return Err(AesDecryptError::HmacError);
}

subkeys.verify_mac(
ciphertext_with_auth.iv,
ciphertext_with_auth.ciphertext,
ciphertext_with_auth.mac,
)?;

(
subkeys.c_key,
ciphertext_with_auth.iv,
ciphertext_with_auth.ciphertext,
)
} else if enforce_mac == EnforceMac::EnforceMac {
return Err(AesDecryptError::HmacError);
return Err(AesDecryptError::HmacError(HmacError));
} else {
// Separate and check both the initialisation vector
let (iv_bytes, cipher_text) = encrypted_bytes.split_at(IV_BYTE_SIZE);
Expand Down
12 changes: 12 additions & 0 deletions tuta-sdk/rust/sdk/src/crypto/compatibility_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ pub struct HkdfTest {
pub length_in_bytes: usize,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HmacTest {
#[serde(with = "const_hex")]
pub key_hex: Vec<u8>,
#[serde(with = "const_hex")]
pub data_hex: Vec<u8>,
#[serde(with = "const_hex")]
pub hmac_sha256_tag_hex: Vec<u8>,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Argon2Test {
Expand Down Expand Up @@ -152,6 +163,7 @@ pub struct CompatibilityTestData {
pub aes128_mac_tests: Vec<Aes128MacTest>,
pub aes256_tests: Vec<AesTest>,
pub hkdf_tests: Vec<HkdfTest>,
pub hmac_sha256_tests: Vec<HmacTest>,
pub argon2id_tests: Vec<Argon2Test>,
pub x25519_tests: Vec<X25519Test>,
pub kyber_encryption_tests: Vec<KyberEncryptionTest>,
Expand Down
41 changes: 41 additions & 0 deletions tuta-sdk/rust/sdk/src/crypto/hmac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use hmac::Mac;
use sha2::Sha256;

/// Size of HMAC authentication added to the ciphertext
pub const MAC_SIZE: usize = 32;

#[derive(thiserror::Error, Debug)]
#[error("HmacError")]
pub struct HmacError;

#[must_use]
pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; MAC_SIZE] {
let mut hmac = hmac::Hmac::<Sha256>::new_from_slice(key).unwrap();
hmac.update(data);
hmac.finalize().into_bytes().into()
}

pub fn verify_hmac_sha256(key: &[u8], data: &[u8], tag: [u8; MAC_SIZE]) -> Result<(), HmacError> {
if tag != hmac_sha256(key, data) {
Err(HmacError)
} else {
Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::crypto::compatibility_test_utils::get_compatibility_test_data;
use crate::crypto::hmac::verify_hmac_sha256;
use crate::crypto::hmac::{hmac_sha256, MAC_SIZE};

#[test]
fn compatibility_test() {
for td in get_compatibility_test_data().hmac_sha256_tests {
let result = hmac_sha256(&td.key_hex, &td.data_hex);
assert_eq!(td.hmac_sha256_tag_hex, result);
let tag: [u8; MAC_SIZE] = td.hmac_sha256_tag_hex.as_slice().try_into().unwrap();
verify_hmac_sha256(&td.key_hex, &td.data_hex, tag).unwrap();
}
}
}

0 comments on commit 863249a

Please sign in to comment.