[bssl] Implement AEAD BoringSSL wrapper

AEAD will be used to encrypt the private key in the RKP HAL.

Bug: 302286887
Test: atest libbssl_avf_nostd.test
Change-Id: I734cf5ecd8b938f2c47f439d6f51594ccafb4ff2
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 547ad43..80398c0 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -63,6 +63,9 @@
     EC_KEY_marshal_private_key,
     EC_KEY_new_by_curve_name,
     EC_POINT_get_affine_coordinates,
+    EVP_AEAD_CTX_new,
+    EVP_AEAD_CTX_open,
+    EVP_AEAD_CTX_seal,
     HKDF,
     HMAC,
 }
diff --git a/libs/bssl/src/aead.rs b/libs/bssl/src/aead.rs
new file mode 100644
index 0000000..a7d03b9
--- /dev/null
+++ b/libs/bssl/src/aead.rs
@@ -0,0 +1,160 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! Wrappers of the AEAD functions in BoringSSL aead.h.
+
+use crate::util::{check_int_result, to_call_failed_error};
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::{
+    EVP_AEAD_CTX_free, EVP_AEAD_CTX_new, EVP_AEAD_CTX_open, EVP_AEAD_CTX_seal,
+    EVP_AEAD_max_overhead, EVP_AEAD_nonce_length, EVP_aead_aes_256_gcm, EVP_AEAD, EVP_AEAD_CTX,
+    EVP_AEAD_DEFAULT_TAG_LENGTH,
+};
+use core::ptr::NonNull;
+
+/// Magic value indicating that the default tag length for an AEAD should be used to
+/// initialize `AeadCtx`.
+const AEAD_DEFAULT_TAG_LENGTH: usize = EVP_AEAD_DEFAULT_TAG_LENGTH as usize;
+
+/// Represents an AEAD algorithm.
+#[derive(Clone, Copy, Debug)]
+pub struct Aead(&'static EVP_AEAD);
+
+impl Aead {
+    /// This is AES-256 in Galois Counter Mode.
+    /// AES-GCM should only be used with 12-byte (96-bit) nonces as suggested in the
+    /// BoringSSL spec:
+    ///
+    /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html
+    pub fn aes_256_gcm() -> Self {
+        // SAFETY: This function does not access any Rust variables and simply returns
+        // a pointer to the static variable in BoringSSL.
+        let p = unsafe { EVP_aead_aes_256_gcm() };
+        // SAFETY: The returned pointer should always be valid and points to a static
+        // `EVP_AEAD`.
+        Self(unsafe { &*p })
+    }
+
+    /// Returns the maximum number of additional bytes added by the act of sealing data.
+    pub fn max_overhead(&self) -> usize {
+        // SAFETY: This function only reads from self.
+        unsafe { EVP_AEAD_max_overhead(self.0) }
+    }
+
+    /// Returns the length, in bytes, of the per-message nonce.
+    pub fn nonce_length(&self) -> usize {
+        // SAFETY: This function only reads from self.
+        unsafe { EVP_AEAD_nonce_length(self.0) }
+    }
+}
+
+/// Represents an AEAD algorithm configuration.
+pub struct AeadCtx {
+    ctx: NonNull<EVP_AEAD_CTX>,
+    aead: Aead,
+}
+
+impl Drop for AeadCtx {
+    fn drop(&mut self) {
+        // SAFETY: It is safe because the pointer has been created with `EVP_AEAD_CTX_new`
+        // and isn't used after this.
+        unsafe { EVP_AEAD_CTX_free(self.ctx.as_ptr()) }
+    }
+}
+
+impl AeadCtx {
+    /// Creates a new `AeadCtx` with the given `Aead` algorithm, `key` and `tag_len`.
+    ///
+    /// The default tag length will be used if `tag_len` is None.
+    pub fn new(aead: Aead, key: &[u8], tag_len: Option<usize>) -> Result<Self> {
+        let tag_len = tag_len.unwrap_or(AEAD_DEFAULT_TAG_LENGTH);
+        // SAFETY: This function only reads the given data and the returned pointer is
+        // checked below.
+        let ctx = unsafe { EVP_AEAD_CTX_new(aead.0, key.as_ptr(), key.len(), tag_len) };
+        let ctx = NonNull::new(ctx).ok_or(to_call_failed_error(ApiName::EVP_AEAD_CTX_new))?;
+        Ok(Self { ctx, aead })
+    }
+
+    /// Encrypts and authenticates `data` and writes the result to `out`.
+    /// The `out` length should be at least the `data` length plus the `max_overhead` of the
+    /// `aead` and the length of `nonce` should match the `nonce_length` of the `aead`.
+    ///  Otherwise, an error will be returned.
+    ///
+    /// The output is returned as a subslice of `out`.
+    pub fn seal<'b>(
+        &self,
+        data: &[u8],
+        nonce: &[u8],
+        ad: &[u8],
+        out: &'b mut [u8],
+    ) -> Result<&'b [u8]> {
+        let mut out_len = 0;
+        // SAFETY: Only reads from/writes to the provided slices.
+        let ret = unsafe {
+            EVP_AEAD_CTX_seal(
+                self.ctx.as_ptr(),
+                out.as_mut_ptr(),
+                &mut out_len,
+                out.len(),
+                nonce.as_ptr(),
+                nonce.len(),
+                data.as_ptr(),
+                data.len(),
+                ad.as_ptr(),
+                ad.len(),
+            )
+        };
+        check_int_result(ret, ApiName::EVP_AEAD_CTX_seal)?;
+        out.get(0..out_len).ok_or(to_call_failed_error(ApiName::EVP_AEAD_CTX_seal))
+    }
+
+    /// Authenticates `data` and decrypts it to `out`.
+    /// The `out` length should be at least the `data` length, and the length of `nonce` should
+    /// match the `nonce_length` of the `aead`.
+    /// Otherwise, an error will be returned.
+    ///
+    /// The output is returned as a subslice of `out`.
+    pub fn open<'b>(
+        &self,
+        data: &[u8],
+        nonce: &[u8],
+        ad: &[u8],
+        out: &'b mut [u8],
+    ) -> Result<&'b [u8]> {
+        let mut out_len = 0;
+        // SAFETY: Only reads from/writes to the provided slices.
+        // `data` and `out` are checked to be non-alias internally.
+        let ret = unsafe {
+            EVP_AEAD_CTX_open(
+                self.ctx.as_ptr(),
+                out.as_mut_ptr(),
+                &mut out_len,
+                out.len(),
+                nonce.as_ptr(),
+                nonce.len(),
+                data.as_ptr(),
+                data.len(),
+                ad.as_ptr(),
+                ad.len(),
+            )
+        };
+        check_int_result(ret, ApiName::EVP_AEAD_CTX_open)?;
+        out.get(0..out_len).ok_or(to_call_failed_error(ApiName::EVP_AEAD_CTX_open))
+    }
+
+    /// Returns the `Aead` represented by this `AeadCtx`.
+    pub fn aead(&self) -> Aead {
+        self.aead
+    }
+}
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index 8b38f5b..898e16c 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -18,6 +18,7 @@
 
 extern crate alloc;
 
+mod aead;
 mod cbb;
 mod digest;
 mod ec_key;
@@ -28,6 +29,7 @@
 
 pub use bssl_avf_error::{ApiName, CipherError, Error, ReasonCode, Result};
 
+pub use aead::{Aead, AeadCtx};
 pub use cbb::CbbFixed;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
diff --git a/libs/bssl/tests/aead_test.rs b/libs/bssl/tests/aead_test.rs
new file mode 100644
index 0000000..8ac3f12
--- /dev/null
+++ b/libs/bssl/tests/aead_test.rs
@@ -0,0 +1,148 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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 bssl_avf::{Aead, AeadCtx, ApiName, CipherError, Error, ReasonCode, Result};
+
+/// The following vectors are generated randomly with:
+/// `hexdump -vn32 -e'32/1 "0x%02x, " 1 "\n"' /dev/urandom`
+const KEY1: [u8; 32] = [
+    0xdb, 0x16, 0xcc, 0xbf, 0xf0, 0xc4, 0xbc, 0x93, 0xc3, 0x5f, 0x11, 0xc5, 0xfa, 0xae, 0x03, 0x6c,
+    0x75, 0x40, 0x1f, 0x60, 0xb6, 0x3e, 0xb9, 0x2a, 0x6c, 0x84, 0x06, 0x4b, 0x36, 0x7f, 0xed, 0xdb,
+];
+const KEY2: [u8; 32] = [
+    0xaa, 0x57, 0x7a, 0x1a, 0x8b, 0xa2, 0x59, 0x3b, 0xad, 0x5f, 0x4d, 0x29, 0xe1, 0x0c, 0xaa, 0x85,
+    0xde, 0xf9, 0xad, 0xad, 0x8c, 0x11, 0x0c, 0x2e, 0x13, 0x43, 0xd7, 0xdf, 0x2a, 0x43, 0xb9, 0xdd,
+];
+/// The following vectors are generated randomly with:
+/// Generated with `hexdump -vn12 -e'12/1 "0x%02x, " 1 "\n"' /dev/urandom`
+const AES_256_GCM_NONCE1: [u8; 12] =
+    [0x56, 0x96, 0x73, 0xe1, 0xc6, 0x3d, 0xca, 0x9a, 0x2f, 0xad, 0x3b, 0xeb];
+const AES_256_GCM_NONCE2: [u8; 12] =
+    [0xa0, 0x27, 0xea, 0x3a, 0x29, 0xfa, 0x8a, 0x49, 0x35, 0x07, 0x32, 0xec];
+const MESSAGE: &[u8] = b"aead_aes_256_gcm test message";
+
+#[test]
+fn aes_256_gcm_encrypts_and_decrypts_successfully() -> Result<()> {
+    let ciphertext = aes_256_gcm_encrypt(MESSAGE)?;
+    let tag_len = None;
+
+    let ad = &[];
+    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let mut out = vec![0u8; ciphertext.len()];
+
+    let plaintext = aead_ctx.open(&ciphertext, &AES_256_GCM_NONCE1, ad, &mut out)?;
+
+    assert_eq!(MESSAGE, plaintext);
+    Ok(())
+}
+
+#[test]
+fn aes_256_gcm_fails_to_encrypt_with_invalid_nonce() -> Result<()> {
+    let tag_len = None;
+    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let nonce = &[];
+    let ad = &[];
+    let mut out = vec![0u8; MESSAGE.len() + aead_ctx.aead().max_overhead()];
+
+    let err = aead_ctx.seal(MESSAGE, nonce, ad, &mut out).unwrap_err();
+
+    let expected_err = Error::CallFailed(
+        ApiName::EVP_AEAD_CTX_seal,
+        ReasonCode::Cipher(CipherError::InvalidNonceSize),
+    );
+    assert_eq!(expected_err, err);
+    Ok(())
+}
+
+#[test]
+fn aes_256_gcm_fails_to_decrypt_with_wrong_key() -> Result<()> {
+    let ciphertext = aes_256_gcm_encrypt(MESSAGE)?;
+    let tag_len = None;
+
+    let ad = &[];
+    let aead_ctx2 = AeadCtx::new(Aead::aes_256_gcm(), &KEY2, tag_len)?;
+    let mut plaintext = vec![0u8; ciphertext.len()];
+
+    let err = aead_ctx2.open(&ciphertext, &AES_256_GCM_NONCE1, ad, &mut plaintext).unwrap_err();
+
+    let expected_err =
+        Error::CallFailed(ApiName::EVP_AEAD_CTX_open, ReasonCode::Cipher(CipherError::BadDecrypt));
+    assert_eq!(expected_err, err);
+    Ok(())
+}
+
+#[test]
+fn aes_256_gcm_fails_to_decrypt_with_different_ad() -> Result<()> {
+    let ciphertext = aes_256_gcm_encrypt(MESSAGE)?;
+    let tag_len = None;
+
+    let ad2 = &[1];
+    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let mut plaintext = vec![0u8; ciphertext.len()];
+
+    let err = aead_ctx.open(&ciphertext, &AES_256_GCM_NONCE1, ad2, &mut plaintext).unwrap_err();
+
+    let expected_err =
+        Error::CallFailed(ApiName::EVP_AEAD_CTX_open, ReasonCode::Cipher(CipherError::BadDecrypt));
+    assert_eq!(expected_err, err);
+    Ok(())
+}
+
+#[test]
+fn aes_256_gcm_fails_to_decrypt_with_different_nonce() -> Result<()> {
+    let ciphertext = aes_256_gcm_encrypt(MESSAGE)?;
+    let tag_len = None;
+
+    let ad = &[];
+    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let mut plaintext = vec![0u8; ciphertext.len()];
+
+    let err = aead_ctx.open(&ciphertext, &AES_256_GCM_NONCE2, ad, &mut plaintext).unwrap_err();
+
+    let expected_err =
+        Error::CallFailed(ApiName::EVP_AEAD_CTX_open, ReasonCode::Cipher(CipherError::BadDecrypt));
+    assert_eq!(expected_err, err);
+    Ok(())
+}
+
+#[test]
+fn aes_256_gcm_fails_to_decrypt_corrupted_ciphertext() -> Result<()> {
+    let mut ciphertext = aes_256_gcm_encrypt(MESSAGE)?;
+    ciphertext[1] = !ciphertext[1];
+    let tag_len = None;
+
+    let ad = &[];
+    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let mut plaintext = vec![0u8; ciphertext.len()];
+
+    let err = aead_ctx.open(&ciphertext, &AES_256_GCM_NONCE1, ad, &mut plaintext).unwrap_err();
+
+    let expected_err =
+        Error::CallFailed(ApiName::EVP_AEAD_CTX_open, ReasonCode::Cipher(CipherError::BadDecrypt));
+    assert_eq!(expected_err, err);
+    Ok(())
+}
+
+fn aes_256_gcm_encrypt(message: &[u8]) -> Result<Vec<u8>> {
+    let tag_len = None;
+    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let mut out = vec![0u8; message.len() + aead_ctx.aead().max_overhead()];
+
+    assert_eq!(aead_ctx.aead().nonce_length(), AES_256_GCM_NONCE1.len());
+    let ad = &[];
+
+    let ciphertext = aead_ctx.seal(message, &AES_256_GCM_NONCE1, ad, &mut out)?;
+    assert_ne!(message, ciphertext);
+    Ok(ciphertext.to_vec())
+}
diff --git a/libs/bssl/tests/tests.rs b/libs/bssl/tests/tests.rs
index 1077787..4c0b0b0 100644
--- a/libs/bssl/tests/tests.rs
+++ b/libs/bssl/tests/tests.rs
@@ -14,5 +14,6 @@
 
 //! API tests of the crate `bssl_avf`.
 
+mod aead_test;
 mod hkdf_test;
 mod hmac_test;