[rkp] Encrypt/decrypt the private key with AES-256-GCM
This cl implements the private key encryption and decryption
with AES-256-GCM.
The KEK is derived from the sealing CDI with a random salt
generated with TRNG.
The test is added to the busy town config at cl/570947834.
Bug: 279425980
Test: atest rialto_test
Test: atest libservice_vm_requests.test
Change-Id: I214ee37c64cb8508083b02376c8a398ca6049e3b
diff --git a/TEST_MAPPING b/TEST_MAPPING
index adf6309..f5d2dda 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -99,6 +99,9 @@
"path": "packages/modules/Virtualization/rialto"
},
{
+ "path": "packages/modules/Virtualization/service_vm/requests"
+ },
+ {
"path": "packages/modules/Virtualization/vm"
},
{
diff --git a/libs/bssl/error/src/code.rs b/libs/bssl/error/src/code.rs
index 7fb36c4..9b661e9 100644
--- a/libs/bssl/error/src/code.rs
+++ b/libs/bssl/error/src/code.rs
@@ -91,6 +91,12 @@
InvalidNonce,
}
+impl From<CipherError> for ReasonCode {
+ fn from(e: CipherError) -> ReasonCode {
+ ReasonCode::Cipher(e)
+ }
+}
+
impl fmt::Display for CipherError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "An error occurred in a Cipher function: {self:?}")
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 80398c0..3766c41 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -68,4 +68,5 @@
EVP_AEAD_CTX_seal,
HKDF,
HMAC,
+ RAND_bytes,
}
diff --git a/libs/bssl/src/aead.rs b/libs/bssl/src/aead.rs
index a7d03b9..74bde84 100644
--- a/libs/bssl/src/aead.rs
+++ b/libs/bssl/src/aead.rs
@@ -23,6 +23,11 @@
};
use core::ptr::NonNull;
+/// BoringSSL spec recommends to use 12-byte nonces.
+///
+/// https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html#EVP_aead_aes_256_gcm
+pub const AES_GCM_NONCE_LENGTH: usize = 12;
+
/// 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;
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index 898e16c..ba4ec1f 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -25,13 +25,15 @@
mod err;
mod hkdf;
mod hmac;
+mod rand;
mod util;
pub use bssl_avf_error::{ApiName, CipherError, Error, ReasonCode, Result};
-pub use aead::{Aead, AeadCtx};
+pub use aead::{Aead, AeadCtx, AES_GCM_NONCE_LENGTH};
pub use cbb::CbbFixed;
pub use digest::Digester;
pub use ec_key::{EcKey, ZVec};
pub use hkdf::hkdf;
pub use hmac::hmac_sha256;
+pub use rand::rand_bytes;
diff --git a/libs/bssl/src/rand.rs b/libs/bssl/src/rand.rs
new file mode 100644
index 0000000..9343284
--- /dev/null
+++ b/libs/bssl/src/rand.rs
@@ -0,0 +1,26 @@
+// 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 randon number generations functions in BoringSSL rand.h.
+
+use crate::util::check_int_result;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::RAND_bytes;
+
+/// Fills the given `dest` with random data.
+pub fn rand_bytes(dest: &mut [u8]) -> Result<()> {
+ // SAFETY: This function only writes to the given buffer within its bounds.
+ let ret = unsafe { RAND_bytes(dest.as_mut_ptr(), dest.len()) };
+ check_int_result(ret, ApiName::RAND_bytes)
+}
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 6a6dcf4..ee7ecb4 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -77,8 +77,9 @@
info!("Received response: {response:?}.");
match response {
- Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { maced_public_key, .. }) => {
- assert_array_has_nonzero(&maced_public_key[..]);
+ Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { maced_public_key, key_blob }) => {
+ assert_array_has_nonzero(&maced_public_key);
+ assert_array_has_nonzero(&key_blob);
Ok(maced_public_key)
}
_ => bail!("Incorrect response type: {response:?}"),
diff --git a/service_vm/requests/Android.bp b/service_vm/requests/Android.bp
index 4b9b46f..f85064a 100644
--- a/service_vm/requests/Android.bp
+++ b/service_vm/requests/Android.bp
@@ -3,7 +3,7 @@
}
rust_defaults {
- name: "libservice_vm_requests_defaults",
+ name: "libservice_vm_requests_nostd_defaults",
crate_name: "service_vm_requests",
defaults: ["avf_build_flags_rust"],
srcs: ["src/lib.rs"],
@@ -11,11 +11,6 @@
apex_available: [
"com.android.virt",
],
-}
-
-rust_library_rlib {
- name: "libservice_vm_requests_nostd",
- defaults: ["libservice_vm_requests_defaults"],
no_stdlibs: true,
stdlibs: [
"libcore.rust_sysroot",
@@ -32,3 +27,14 @@
"libzeroize_nostd",
],
}
+
+rust_library_rlib {
+ name: "libservice_vm_requests_nostd",
+ defaults: ["libservice_vm_requests_nostd_defaults"],
+}
+
+rust_test {
+ name: "libservice_vm_requests.test",
+ defaults: ["libservice_vm_requests_nostd_defaults"],
+ test_suites: ["general-tests"],
+}
diff --git a/service_vm/requests/TEST_MAPPING b/service_vm/requests/TEST_MAPPING
new file mode 100644
index 0000000..c95f9e3
--- /dev/null
+++ b/service_vm/requests/TEST_MAPPING
@@ -0,0 +1,9 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+ "avf-presubmit" : [
+ {
+ "name" : "libservice_vm_requests.test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/service_vm/requests/src/keyblob.rs b/service_vm/requests/src/keyblob.rs
new file mode 100644
index 0000000..5558836
--- /dev/null
+++ b/service_vm/requests/src/keyblob.rs
@@ -0,0 +1,158 @@
+// 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.
+
+//! Handles the encryption and decryption of the key blob.
+
+use crate::cbor;
+use alloc::vec;
+use alloc::vec::Vec;
+use bssl_avf::{hkdf, rand_bytes, Aead, AeadCtx, Digester, AES_GCM_NONCE_LENGTH};
+use core::result;
+use serde::{Deserialize, Serialize};
+use service_vm_comm::RequestProcessingError;
+// TODO(b/241428146): This will be used once the retrieval mechanism is available.
+#[cfg(test)]
+use zeroize::Zeroizing;
+
+type Result<T> = result::Result<T, RequestProcessingError>;
+
+/// The KEK (Key Encryption Key) info is used as information to derive the KEK using HKDF.
+const KEK_INFO: &[u8] = b"rialto keyblob kek";
+
+/// An all-zero nonce is utilized to encrypt the private key. This is because each key
+/// undergoes encryption using a distinct KEK, which is derived from a secret and a random
+/// salt. Since the uniqueness of the IV/key combination is already guaranteed by the uniqueness
+/// of the KEK, there is no need for an additional random nonce.
+const PRIVATE_KEY_NONCE: &[u8; AES_GCM_NONCE_LENGTH] = &[0; AES_GCM_NONCE_LENGTH];
+
+/// Since Rialto functions as both the sender and receiver of the message, no additional data is
+/// needed.
+const PRIVATE_KEY_AD: &[u8] = &[];
+
+// Encrypted key blob.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub(crate) enum EncryptedKeyBlob {
+ /// Version 1 key blob.
+ V1(EncryptedKeyBlobV1),
+}
+
+/// Encrypted key blob version 1.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub(crate) struct EncryptedKeyBlobV1 {
+ /// Salt used to derive the KEK.
+ kek_salt: [u8; 32],
+
+ /// Private key encrypted with AES-256-GCM.
+ encrypted_private_key: Vec<u8>,
+}
+
+impl EncryptedKeyBlob {
+ pub(crate) fn new(private_key: &[u8], kek_secret: &[u8]) -> Result<Self> {
+ EncryptedKeyBlobV1::new(private_key, kek_secret).map(Self::V1)
+ }
+
+ // TODO(b/241428146): Use this function to decrypt the retrieved keyblob once the retrieval
+ // mechanism is available.
+ #[cfg(test)]
+ pub(crate) fn decrypt_private_key(&self, kek_secret: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
+ match self {
+ Self::V1(blob) => blob.decrypt_private_key(kek_secret),
+ }
+ }
+
+ // TODO(b/241428146): This function will be used once the retrieval mechanism is available.
+ #[cfg(test)]
+ pub(crate) fn from_cbor_slice(slice: &[u8]) -> coset::Result<Self> {
+ cbor::deserialize(slice)
+ }
+
+ pub(crate) fn to_cbor_vec(&self) -> coset::Result<Vec<u8>> {
+ cbor::serialize(&self)
+ }
+}
+
+impl EncryptedKeyBlobV1 {
+ fn new(private_key: &[u8], kek_secret: &[u8]) -> Result<Self> {
+ let mut kek_salt = [0u8; 32];
+ rand_bytes(&mut kek_salt)?;
+ let kek = hkdf::<32>(kek_secret, &kek_salt, KEK_INFO, Digester::sha512())?;
+
+ let tag_len = None;
+ let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), kek.as_slice(), tag_len)?;
+ let mut out = vec![0u8; private_key.len() + aead_ctx.aead().max_overhead()];
+ let ciphertext = aead_ctx.seal(private_key, PRIVATE_KEY_NONCE, PRIVATE_KEY_AD, &mut out)?;
+
+ Ok(Self { kek_salt, encrypted_private_key: ciphertext.to_vec() })
+ }
+
+ #[cfg(test)]
+ fn decrypt_private_key(&self, kek_secret: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
+ let kek = hkdf::<32>(kek_secret, &self.kek_salt, KEK_INFO, Digester::sha512())?;
+ let mut out = Zeroizing::new(vec![0u8; self.encrypted_private_key.len()]);
+ let tag_len = None;
+ let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), kek.as_slice(), tag_len)?;
+ let plaintext = aead_ctx.open(
+ &self.encrypted_private_key,
+ PRIVATE_KEY_NONCE,
+ PRIVATE_KEY_AD,
+ &mut out,
+ )?;
+ Ok(Zeroizing::new(plaintext.to_vec()))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use bssl_avf::{ApiName, CipherError, Error};
+
+ /// The test data are generated randomly with /dev/urandom.
+ const TEST_KEY: [u8; 32] = [
+ 0x76, 0xf7, 0xd5, 0x36, 0x1f, 0x78, 0x58, 0x2e, 0x55, 0x2f, 0x88, 0x9d, 0xa3, 0x3e, 0xba,
+ 0xfb, 0xc1, 0x2b, 0x17, 0x85, 0x24, 0xdc, 0x0e, 0xc4, 0xbf, 0x6d, 0x2e, 0xe8, 0xa8, 0x36,
+ 0x93, 0x62,
+ ];
+ const TEST_SECRET1: [u8; 32] = [
+ 0xac, 0xb1, 0x6b, 0xdf, 0x45, 0x30, 0x20, 0xa5, 0x60, 0x6d, 0x81, 0x07, 0x30, 0x68, 0x6e,
+ 0x01, 0x3d, 0x5e, 0x86, 0xd6, 0xc6, 0x17, 0xfa, 0xd6, 0xe0, 0xff, 0xd4, 0xf0, 0xb0, 0x7c,
+ 0x5c, 0x8f,
+ ];
+ const TEST_SECRET2: [u8; 32] = [
+ 0x04, 0x6e, 0xca, 0x30, 0x5e, 0x6c, 0x8f, 0xe5, 0x1a, 0x47, 0x12, 0xbc, 0x45, 0xd7, 0xa8,
+ 0x38, 0xfb, 0x06, 0xc6, 0x44, 0xa1, 0x21, 0x40, 0x0b, 0x48, 0x88, 0xe2, 0x31, 0x64, 0x42,
+ 0x9d, 0x1c,
+ ];
+
+ #[test]
+ fn decrypting_keyblob_succeeds_with_the_same_kek() -> Result<()> {
+ let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?;
+ let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?;
+ let decrypted_key = encrypted_key_blob.decrypt_private_key(&TEST_SECRET1)?;
+
+ assert_eq!(TEST_KEY, decrypted_key.as_slice());
+ Ok(())
+ }
+
+ #[test]
+ fn decrypting_keyblob_fails_with_a_different_kek() -> Result<()> {
+ let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?;
+ let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?;
+ let err = encrypted_key_blob.decrypt_private_key(&TEST_SECRET2).unwrap_err();
+
+ let expected_err: RequestProcessingError =
+ Error::CallFailed(ApiName::EVP_AEAD_CTX_open, CipherError::BadDecrypt.into()).into();
+ assert_eq!(expected_err, err);
+ Ok(())
+ }
+}
diff --git a/service_vm/requests/src/lib.rs b/service_vm/requests/src/lib.rs
index eec0253..6fa6e0b 100644
--- a/service_vm/requests/src/lib.rs
+++ b/service_vm/requests/src/lib.rs
@@ -20,6 +20,7 @@
mod api;
mod cbor;
+mod keyblob;
mod pub_key;
mod rkp;
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index bbb688e..2d80f13 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -16,6 +16,7 @@
//! service VM via the RKP (Remote Key Provisioning) server.
use crate::cbor;
+use crate::keyblob::EncryptedKeyBlob;
use crate::pub_key::{build_maced_public_key, validate_public_key};
use alloc::string::String;
use alloc::vec;
@@ -37,7 +38,7 @@
0x82, 0x80, 0xFA, 0xD3, 0xA8, 0x0A, 0x9A, 0x4B, 0xF7, 0xA5, 0x7D, 0x7B, 0xE9, 0xC3, 0xAB, 0x13,
0x89, 0xDC, 0x7B, 0x46, 0xEE, 0x71, 0x22, 0xB4, 0x5F, 0x4C, 0x3F, 0xE2, 0x40, 0x04, 0x3B, 0x6C,
];
-const HMAC_KEY_INFO: &[u8] = b"rialto hmac key";
+const HMAC_KEY_INFO: &[u8] = b"rialto hmac wkey";
const HMAC_KEY_LENGTH: usize = 32;
pub(super) fn generate_ecdsa_p256_key_pair(
@@ -45,13 +46,12 @@
) -> Result<EcdsaP256KeyPair> {
let hmac_key = derive_hmac_key(dice_artifacts)?;
let ec_key = EcKey::new_p256()?;
+
let maced_public_key = build_maced_public_key(ec_key.cose_public_key()?, hmac_key.as_ref())?;
+ let key_blob =
+ EncryptedKeyBlob::new(ec_key.private_key()?.as_slice(), dice_artifacts.cdi_seal())?;
- // TODO(b/279425980): Encrypt the private key in a key blob.
- // Remove the printing of the private key.
- log::debug!("Private key: {:?}", ec_key.private_key()?.as_slice());
-
- let key_pair = EcdsaP256KeyPair { maced_public_key, key_blob: Vec::new() };
+ let key_pair = EcdsaP256KeyPair { maced_public_key, key_blob: key_blob.to_cbor_vec()? };
Ok(key_pair)
}