// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::ctap::status_code::Ctap2StatusCode; use alloc::vec; use alloc::vec::Vec; use crypto::cbc::{cbc_decrypt, cbc_encrypt}; use crypto::rng256::Rng256; /// Wraps the AES256-CBC encryption to match what we need in CTAP. pub fn aes256_cbc_encrypt( rng: &mut dyn Rng256, aes_enc_key: &crypto::aes256::EncryptionKey, plaintext: &[u8], embeds_iv: bool, ) -> Result, Ctap2StatusCode> { if plaintext.len() % 16 != 0 { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } let iv = if embeds_iv { let random_bytes = rng.gen_uniform_u8x32(); *array_ref!(random_bytes, 0, 16) } else { [0u8; 16] }; let mut blocks = Vec::with_capacity(plaintext.len() / 16); // TODO(https://github.com/rust-lang/rust/issues/74985) Use array_chunks when stable. for block in plaintext.chunks_exact(16) { blocks.push(*array_ref!(block, 0, 16)); } cbc_encrypt(aes_enc_key, iv, &mut blocks); let mut ciphertext = if embeds_iv { iv.to_vec() } else { vec![] }; ciphertext.extend(blocks.iter().flatten()); Ok(ciphertext) } /// Wraps the AES256-CBC decryption to match what we need in CTAP. pub fn aes256_cbc_decrypt( aes_enc_key: &crypto::aes256::EncryptionKey, ciphertext: &[u8], embeds_iv: bool, ) -> Result, Ctap2StatusCode> { if ciphertext.len() % 16 != 0 { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } let mut block_len = ciphertext.len() / 16; // TODO(https://github.com/rust-lang/rust/issues/74985) Use array_chunks when stable. let mut block_iter = ciphertext.chunks_exact(16); let iv = if embeds_iv { block_len -= 1; let iv_block = block_iter .next() .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?; *array_ref!(iv_block, 0, 16) } else { [0u8; 16] }; let mut blocks = Vec::with_capacity(block_len); for block in block_iter { blocks.push(*array_ref!(block, 0, 16)); } let aes_dec_key = crypto::aes256::DecryptionKey::new(aes_enc_key); cbc_decrypt(&aes_dec_key, iv, &mut blocks); Ok(blocks.iter().flatten().cloned().collect::>()) } #[cfg(test)] mod test { use super::*; use crypto::rng256::ThreadRng256; #[test] fn test_encrypt_decrypt_with_iv() { let mut rng = ThreadRng256 {}; let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let ciphertext = aes256_cbc_encrypt(&mut rng, &aes_enc_key, &plaintext, true).unwrap(); let decrypted = aes256_cbc_decrypt(&aes_enc_key, &ciphertext, true).unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_encrypt_decrypt_without_iv() { let mut rng = ThreadRng256 {}; let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let ciphertext = aes256_cbc_encrypt(&mut rng, &aes_enc_key, &plaintext, false).unwrap(); let decrypted = aes256_cbc_decrypt(&aes_enc_key, &ciphertext, false).unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_correct_iv_usage() { let mut rng = ThreadRng256 {}; let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let mut ciphertext_no_iv = aes256_cbc_encrypt(&mut rng, &aes_enc_key, &plaintext, false).unwrap(); let mut ciphertext_with_iv = vec![0u8; 16]; ciphertext_with_iv.append(&mut ciphertext_no_iv); let decrypted = aes256_cbc_decrypt(&aes_enc_key, &ciphertext_with_iv, true).unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_iv_manipulation_property() { let mut rng = ThreadRng256 {}; let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let mut ciphertext = aes256_cbc_encrypt(&mut rng, &aes_enc_key, &plaintext, true).unwrap(); let mut expected_plaintext = plaintext; for i in 0..16 { ciphertext[i] ^= 0xBB; expected_plaintext[i] ^= 0xBB; } let decrypted = aes256_cbc_decrypt(&aes_enc_key, &ciphertext, true).unwrap(); assert_eq!(decrypted, expected_plaintext); } #[test] fn test_chaining() { let mut rng = ThreadRng256 {}; let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let ciphertext1 = aes256_cbc_encrypt(&mut rng, &aes_enc_key, &plaintext, true).unwrap(); let ciphertext2 = aes256_cbc_encrypt(&mut rng, &aes_enc_key, &plaintext, true).unwrap(); assert_eq!(ciphertext1.len(), 80); assert_eq!(ciphertext2.len(), 80); // The ciphertext should mutate in all blocks with a different IV. let block_iter1 = ciphertext1.chunks_exact(16); let block_iter2 = ciphertext2.chunks_exact(16); for (block1, block2) in block_iter1.zip(block_iter2) { assert_ne!(block1, block2); } } }