// Copyright 2023 The BoringSSL Authors
//
// 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
//
//     https://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.

//! Advanced Encryption Standard.
//!
//! AES is a 128-bit block cipher that supports key sizes of 128, 192, or 256
//! bits. (Although 192 isn't supported here.)
//!
//! Each key defines a permutation of the set of 128-bit blocks and AES can
//! perform the forward and reverse permutation. (These directions are
//! arbitrarily labeled "encryption" and "decryption".)
//!
//! AES requires relatively expensive preprocessing of keys and thus the
//! processed form of the key is represented here using [`EncryptKey`] and
//! [`DecryptKey`].
//!
//! ```
//! use bssl_crypto::aes;
//!
//! let key_bytes = bssl_crypto::rand_array();
//! let enc_key = aes::EncryptKey::new_256(&key_bytes);
//! let block = [0u8; aes::BLOCK_SIZE];
//! let mut transformed_block = enc_key.encrypt(&block);
//!
//! let dec_key = aes::DecryptKey::new_256(&key_bytes);
//! dec_key.decrypt_in_place(&mut transformed_block);
//! assert_eq!(block, transformed_block);
//! ```
//!
//! AES is a low-level primitive and must be used in a more complex construction
//! in nearly every case. See the `aead` crate for usable encryption and
//! decryption primitives.

use crate::{initialized_struct_fallible, FfiMutSlice, FfiSlice};
use core::ffi::c_uint;

/// AES block size in bytes.
pub const BLOCK_SIZE: usize = bssl_sys::AES_BLOCK_SIZE as usize;

/// A single AES block.
pub type Block = [u8; BLOCK_SIZE];

/// An initialized key which can be used for encrypting.
pub struct EncryptKey(bssl_sys::AES_KEY);

impl EncryptKey {
    /// Initializes an encryption key from an appropriately sized array of bytes
    // for AES-128 operations.
    pub fn new_128(key: &[u8; 16]) -> Self {
        new_encrypt_key(key.as_slice())
    }

    /// Initializes an encryption key from an appropriately sized array of bytes
    // for AES-256 operations.
    pub fn new_256(key: &[u8; 32]) -> Self {
        new_encrypt_key(key.as_slice())
    }

    /// Return the encrypted version of the given block.
    pub fn encrypt(&self, block: &Block) -> Block {
        let mut ret = *block;
        self.encrypt_in_place(&mut ret);
        ret
    }

    /// Replace `block` with its encrypted version.
    pub fn encrypt_in_place(&self, block: &mut Block) {
        // Safety:
        // - block is always a valid size and key is guaranteed to already be initialized.
        unsafe { bssl_sys::AES_encrypt(block.as_ffi_ptr(), block.as_mut_ffi_ptr(), &self.0) }
    }
}

/// An initialized key which can be used for decrypting
pub struct DecryptKey(bssl_sys::AES_KEY);

impl DecryptKey {
    /// Initializes a decryption key from an appropriately sized array of bytes for AES-128 operations.
    pub fn new_128(key: &[u8; 16]) -> DecryptKey {
        new_decrypt_key(key.as_slice())
    }

    /// Initializes a decryption key from an appropriately sized array of bytes for AES-256 operations.
    pub fn new_256(key: &[u8; 32]) -> DecryptKey {
        new_decrypt_key(key.as_slice())
    }

    /// Return the decrypted version of the given block.
    pub fn decrypt(&self, block: &Block) -> Block {
        let mut ret = *block;
        self.decrypt_in_place(&mut ret);
        ret
    }

    /// Replace `block` with its decrypted version.
    pub fn decrypt_in_place(&self, block: &mut Block) {
        // Safety:
        // - block is always a valid size and key is guaranteed to already be initialized.
        unsafe { bssl_sys::AES_decrypt(block.as_ffi_ptr(), block.as_mut_ffi_ptr(), &self.0) }
    }
}

/// This should only be publicly exposed by wrapper types with the correct key lengths
#[allow(clippy::unwrap_used)]
fn new_encrypt_key(key: &[u8]) -> EncryptKey {
    EncryptKey(
        unsafe {
            initialized_struct_fallible(|aes_key| {
                // The return value of this function differs from the usual BoringSSL
                // convention.
                bssl_sys::AES_set_encrypt_key(key.as_ffi_ptr(), key.len() as c_uint * 8, aes_key)
                    == 0
            })
        }
        // unwrap: this function only fails if `key` is the wrong length, which
        // must be prevented by the pub functions that call this.
        .unwrap(),
    )
}

/// This should only be publicly exposed by wrapper types with the correct key lengths.
#[allow(clippy::unwrap_used)]
fn new_decrypt_key(key: &[u8]) -> DecryptKey {
    DecryptKey(
        unsafe {
            initialized_struct_fallible(|aes_key| {
                // The return value of this function differs from the usual BoringSSL
                // convention.
                bssl_sys::AES_set_decrypt_key(key.as_ffi_ptr(), key.len() as c_uint * 8, aes_key)
                    == 0
            })
        }
        // unwrap: this function only fails if `key` is the wrong length, which
        // must be prevented by the pub functions that call this.
        .unwrap(),
    )
}

#[cfg(test)]
mod tests {
    use crate::{
        aes::{DecryptKey, EncryptKey},
        test_helpers::decode_hex,
    };

    #[test]
    fn aes_128() {
        // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.1
        let key = decode_hex("2b7e151628aed2a6abf7158809cf4f3c");
        let plaintext = decode_hex("6bc1bee22e409f96e93d7e117393172a");
        let ciphertext = decode_hex("3ad77bb40d7a3660a89ecaf32466ef97");
        assert_eq!(ciphertext, EncryptKey::new_128(&key).encrypt(&plaintext));
        assert_eq!(plaintext, DecryptKey::new_128(&key).decrypt(&ciphertext));
    }

    #[test]
    fn aes_256() {
        // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.5
        let key = decode_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
        let plaintext = decode_hex("6bc1bee22e409f96e93d7e117393172a");
        let ciphertext = decode_hex("f3eed1bdb5d2a03c064b5a7e3db181f8");
        assert_eq!(ciphertext, EncryptKey::new_256(&key).encrypt(&plaintext));
        assert_eq!(plaintext, DecryptKey::new_256(&key).decrypt(&ciphertext));
    }
}
