[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;