no-way 0.4.1

A library to work with Javascript Object Signing and Encryption(JOSE), including JSON Web Tokens (JWT), JSON Web Signature (JWS) and JSON Web Encryption (JWE).
Documentation
use std::marker::PhantomData;

use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use cipher::block_padding::Pkcs7;
use hmac::{Hmac, Mac};

use super::{Algorithm, EncryptionResult, CEA};
use crate::errors::Error;

/// [Content Encryption with `AES_CBC_HMAC_SHA2`](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2)
///
/// See
/// * [`A128CBC_HS256`] - Content Encryption with `AES_128_CBC_HMAC_SHA_256`
/// * [`A192CBC_HS384`] - Content Encryption with `AES_192_CBC_HMAC_SHA_384`
/// * [`A256CBC_HS512`] - Content Encryption with `AES_256_CBC_HMAC_SHA_512`
pub struct AesCbcHmacSha2<Aes, Sha> {
    _sha: PhantomData<Sha>,
    _aes: PhantomData<Aes>,
}

impl<Aes, Sha> Clone for AesCbcHmacSha2<Aes, Sha> {
    fn clone(&self) -> Self {
        *self
    }
}
impl<Aes, Sha> Copy for AesCbcHmacSha2<Aes, Sha> {}

impl<Aes, Sha> PartialEq for AesCbcHmacSha2<Aes, Sha> {
    fn eq(&self, _other: &Self) -> bool {
        true
    }
}
impl<Aes, Sha> Eq for AesCbcHmacSha2<Aes, Sha> {}

impl<Aes, Sha> std::fmt::Debug for AesCbcHmacSha2<Aes, Sha>
where
    Self: CEA,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(Self::ENC.as_str())
    }
}

#[allow(non_camel_case_types)]
/// [Content Encryption with `AES_128_CBC_HMAC_SHA_256`](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.3)
pub type A128CBC_HS256 = AesCbcHmacSha2<aes::Aes128, sha2::Sha256>;
#[allow(non_camel_case_types)]
/// [Content Encryption with `AES_192_CBC_HMAC_SHA_384`](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.4)
pub type A192CBC_HS384 = AesCbcHmacSha2<aes::Aes192, sha2::Sha384>;
#[allow(non_camel_case_types)]
/// [Content Encryption with `AES_256_CBC_HMAC_SHA_512`](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.5)
pub type A256CBC_HS512 = AesCbcHmacSha2<aes::Aes256, sha2::Sha512>;

macro_rules! aes_cbc {
    ($id:ident, $sha:ty, $aes:ty, $key_len:expr) => {
        impl CEA for $id {
            const ENC: Algorithm = Algorithm::$id;
            type IV = [u8; 128 / 8];

            fn generate_cek() -> Vec<u8> {
                let mut rng = rand::thread_rng();
                let mut key = vec![0; $key_len * 2];
                rand::Rng::fill(&mut rng, key.as_mut_slice());
                key
            }

            fn generate_iv() -> [u8; 128 / 8] {
                let mut rng = rand::thread_rng();
                let mut key = [0; 128 / 8];
                rand::Rng::fill(&mut rng, key.as_mut_slice());
                key
            }

            fn encrypt(
                cek: &[u8],
                payload: &[u8],
                iv: [u8; 128 / 8],
                aad: &[u8],
            ) -> Result<EncryptionResult, Error> {
                if cek.len() != $key_len * 2 {
                    return Err(Error::UnspecifiedCryptographicError);
                }

                let (mac_key, enc_key) = cek.split_at($key_len);

                let bs = <$aes as cipher::BlockSizeUser>::block_size();
                let padded_len = bs * (payload.len() / bs + 1);

                let mut output = EncryptionResult::new([aad.len(), iv.len(), padded_len, $key_len]);
                output[0].copy_from_slice(aad);
                output[1].copy_from_slice(&iv);
                output[2][..payload.len()].copy_from_slice(payload);

                // encrypt the payload using aes-cbc
                cbc::Encryptor::<$aes>::new_from_slices(enc_key, &iv)?
                    .encrypt_padded_mut::<Pkcs7>(&mut output[2], payload.len())
                    .expect("enough space for encrypting is allocated");

                // compute the hmac
                let tag = Hmac::<$sha>::new_from_slice(mac_key)?
                    .chain_update(&output[0..3])
                    .chain_update(&(output[0].len() as u64 * 8).to_be_bytes())
                    .finalize()
                    .into_bytes();

                output[3].copy_from_slice(&tag[..$key_len]);

                Ok(output)
            }

            fn decrypt<'res>(
                cek: &[u8],
                res: &'res mut EncryptionResult,
            ) -> Result<&'res [u8], Error> {
                let [aad, iv, _, tag] = res.split();

                if cek.len() != $key_len * 2 || tag.len() != $key_len {
                    return Err(Error::UnspecifiedCryptographicError);
                }
                let (mac_key, enc_key) = cek.split_at($key_len);

                // validate the hmac
                Hmac::<$sha>::new_from_slice(mac_key)?
                    .chain_update(&res[0..3])
                    .chain_update(&(aad.len() as u64 * 8).to_be_bytes())
                    .verify_truncated_left(tag)?;

                // decrypt the payload using aes-cbc
                let pt = cbc::Decryptor::<$aes>::new_from_slices(enc_key, iv)?
                    .decrypt_padded_mut::<Pkcs7>(&mut res[2])?;
                Ok(pt)
            }
        }
    };
}

aes_cbc!(A128CBC_HS256, sha2::Sha256, aes::Aes128, 128 / 8);
aes_cbc!(A192CBC_HS384, sha2::Sha384, aes::Aes192, 192 / 8);
aes_cbc!(A256CBC_HS512, sha2::Sha512, aes::Aes256, 256 / 8);

#[cfg(test)]
mod tests {
    use hex_literal::hex;

    use super::*;

    #[test]
    fn aes128sha256() {
        let key = &hex!(
            "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"
            "10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f"
        );
        let enc = &hex!(
            "c8 0e df a3 2d df 39 d5 ef 00 c0 b4 68 83 42 79"
            "a2 e4 6a 1b 80 49 f7 92 f7 6b fe 54 b9 03 a9 c9"
            "a9 4a c9 b4 7a d2 65 5c 5f 10 f9 ae f7 14 27 e2"
            "fc 6f 9b 3f 39 9a 22 14 89 f1 63 62 c7 03 23 36"
            "09 d4 5a c6 98 64 e3 32 1c f8 29 35 ac 40 96 c8"
            "6e 13 33 14 c5 40 19 e8 ca 79 80 df a4 b9 cf 1b"
            "38 4c 48 6f 3a 54 c5 10 78 15 8e e5 d7 9d e5 9f"
            "bd 34 d8 48 b3 d6 95 50 a6 76 46 34 44 27 ad e5"
            "4b 88 51 ff b5 98 f7 f8 00 74 b9 47 3c 82 e2 db"
        );
        let tag = &hex!("65 2c 3f a3 6b 0a 7c 5b 32 19 fa b3 a3 0b c1 c4");

        cea_round_trip::<A128CBC_HS256>(key, enc, tag);
    }

    #[test]
    fn aes192sha384() {
        let key = &hex!(
            "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"
            "10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f"
            "20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f"
        );
        let enc = &hex!(
            "ea 65 da 6b 59 e6 1e db 41 9b e6 2d 19 71 2a e5"
            "d3 03 ee b5 00 52 d0 df d6 69 7f 77 22 4c 8e db"
            "00 0d 27 9b dc 14 c1 07 26 54 bd 30 94 42 30 c6"
            "57 be d4 ca 0c 9f 4a 84 66 f2 2b 22 6d 17 46 21"
            "4b f8 cf c2 40 0a dd 9f 51 26 e4 79 66 3f c9 0b"
            "3b ed 78 7a 2f 0f fc bf 39 04 be 2a 64 1d 5c 21"
            "05 bf e5 91 ba e2 3b 1d 74 49 e5 32 ee f6 0a 9a"
            "c8 bb 6c 6b 01 d3 5d 49 78 7b cd 57 ef 48 49 27"
            "f2 80 ad c9 1a c0 c4 e7 9c 7b 11 ef c6 00 54 e3"
        );
        let tag = &hex!(
            "84 90 ac 0e 58 94 9b fe 51 87 5d 73 3f 93 ac 20"
            "75 16 80 39 cc c7 33 d7"
        );

        cea_round_trip::<A192CBC_HS384>(key, enc, tag);
    }

    #[test]
    fn aes256sha512() {
        let key = &hex!(
            "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"
            "10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f"
            "20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f"
            "30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f"
        );
        let enc = &hex!(
            "4a ff aa ad b7 8c 31 c5 da 4b 1b 59 0d 10 ff bd"
            "3d d8 d5 d3 02 42 35 26 91 2d a0 37 ec bc c7 bd"
            "82 2c 30 1d d6 7c 37 3b cc b5 84 ad 3e 92 79 c2"
            "e6 d1 2a 13 74 b7 7f 07 75 53 df 82 94 10 44 6b"
            "36 eb d9 70 66 29 6a e6 42 7e a7 5c 2e 08 46 a1"
            "1a 09 cc f5 37 0d c8 0b fe cb ad 28 c7 3f 09 b3"
            "a3 b7 5e 66 2a 25 94 41 0a e4 96 b2 e2 e6 60 9e"
            "31 e6 e0 2c c8 37 f0 53 d2 1f 37 ff 4f 51 95 0b"
            "be 26 38 d0 9d d7 a4 93 09 30 80 6d 07 03 b1 f6"
        );
        let tag = &hex!(
            "4d d3 b4 c0 88 a7 f4 5c 21 68 39 64 5b 20 12 bf"
            "2e 62 69 a8 c5 6a 81 6d bc 1b 26 77 61 95 5b c5"
        );

        cea_round_trip::<A256CBC_HS512>(key, enc, tag);
    }

    fn cea_round_trip<C: CEA<IV = [u8; 16]>>(key: &[u8], enc: &[u8], tag: &[u8]) {
        let payload = hex!(
            "41 20 63 69 70 68 65 72 20 73 79 73 74 65 6d 20"
            "6d 75 73 74 20 6e 6f 74 20 62 65 20 72 65 71 75"
            "69 72 65 64 20 74 6f 20 62 65 20 73 65 63 72 65"
            "74 2c 20 61 6e 64 20 69 74 20 6d 75 73 74 20 62"
            "65 20 61 62 6c 65 20 74 6f 20 66 61 6c 6c 20 69"
            "6e 74 6f 20 74 68 65 20 68 61 6e 64 73 20 6f 66"
            "20 74 68 65 20 65 6e 65 6d 79 20 77 69 74 68 6f"
            "75 74 20 69 6e 63 6f 6e 76 65 6e 69 65 6e 63 65"
        );
        let iv = hex!("1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04");
        let aad = hex!(
            "54 68 65 20 73 65 63 6f 6e 64 20 70 72 69 6e 63"
            "69 70 6c 65 20 6f 66 20 41 75 67 75 73 74 65 20"
            "4b 65 72 63 6b 68 6f 66 66 73"
        );

        let mut res = C::encrypt(key, &payload, iv, &aad).unwrap();
        let [aad1, iv1, payload1, tag1] = res.split();

        assert_eq!(aad1, aad);
        assert_eq!(iv1, iv);
        assert_eq!(payload1, enc);
        assert_eq!(tag1, tag);

        let output = C::decrypt(key, &mut res).unwrap();
        assert_eq!(output, payload);
    }
}