microdroid_manager: Migate from ring to the openssl crate
Bug: 232676161
Test: atest MicrodroidTests
Change-Id: I3a1d5eb2529c397ae253b4bdab396db31db6b56b
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 203e889..8343691 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -24,14 +24,15 @@
"libidsig",
"libitertools",
"libkernlog",
+ "libkeystore2_crypto_rust",
"liblibc",
"liblog_rust",
"libmicrodroid_metadata",
"libmicrodroid_payload_config",
"libnix",
"libonce_cell",
+ "libopenssl",
"libprotobuf",
- "libring",
"librustutils",
"libserde",
"libserde_cbor",
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 267a0e3..76c8b23 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -39,8 +39,10 @@
use anyhow::{anyhow, bail, Context, Result};
use binder::wait_for_interface;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
-use ring::aead::{Aad, Algorithm, LessSafeKey, Nonce, UnboundKey, AES_256_GCM};
-use ring::hkdf::{Salt, HKDF_SHA256};
+use keystore2_crypto::ZVec;
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
use serde::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
@@ -62,8 +64,11 @@
/// UUID of the partition that microdroid manager uses
const MICRODROID_PARTITION_UUID: &str = "cf9afe9a-0662-11ec-a329-c32663a09d75";
-/// Encryption algorithm used to cipher payload
-static ENCRYPT_ALG: &Algorithm = &AES_256_GCM;
+/// Size of the AES256-GCM tag
+const AES_256_GCM_TAG_LENGTH: usize = 16;
+
+/// Size of the AES256-GCM nonce
+const AES_256_GCM_NONCE_LENGTH: usize = 12;
/// Handle to the instance disk
pub struct InstanceDisk {
@@ -118,28 +123,31 @@
let payload_offset = offset + PARTITION_HEADER_SIZE;
self.file.seek(SeekFrom::Start(payload_offset))?;
- // Read the 12-bytes nonce (unencrypted)
- let mut nonce = [0; 12];
+ // Read the nonce (unencrypted)
+ let mut nonce = [0; AES_256_GCM_NONCE_LENGTH];
self.file.read_exact(&mut nonce)?;
- let nonce = Nonce::assume_unique_for_key(nonce);
// Read the encrypted payload
- let payload_size = header.payload_size - 12; // we already have read the nonce
- let mut data = vec![0; payload_size as usize];
+ let payload_size =
+ header.payload_size as usize - AES_256_GCM_NONCE_LENGTH - AES_256_GCM_TAG_LENGTH;
+ let mut data = vec![0; payload_size];
self.file.read_exact(&mut data)?;
+ // Read the tag
+ let mut tag = [0; AES_256_GCM_TAG_LENGTH];
+ self.file.read_exact(&mut tag)?;
+
// Read the header as well because it's part of the signed data (though not encrypted).
let mut header = [0; PARTITION_HEADER_SIZE as usize];
self.file.seek(SeekFrom::Start(offset))?;
self.file.read_exact(&mut header)?;
- // Decrypt and authenticate the data (along with the header). The data is decrypted in
- // place. `open_in_place` returns slice to the decrypted part in the buffer.
- let plaintext_len = get_key()?.open_in_place(nonce, Aad::from(&header), &mut data)?.len();
- // Truncate to remove the tag
- data.truncate(plaintext_len);
+ // Decrypt and authenticate the data (along with the header).
+ let key = get_key()?;
+ let plaintext =
+ decrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &tag)?;
- let microdroid_data = serde_cbor::from_slice(data.as_slice())?;
+ let microdroid_data = serde_cbor::from_slice(plaintext.as_slice())?;
Ok(Some(microdroid_data))
}
@@ -148,12 +156,12 @@
pub fn write_microdroid_data(&mut self, microdroid_data: &MicrodroidData) -> Result<()> {
let (header, offset) = self.locate_microdroid_header()?;
- let mut data = serde_cbor::to_vec(microdroid_data)?;
+ let data = serde_cbor::to_vec(microdroid_data)?;
// By encrypting and signing the data, tag will be appended. The tag also becomes part of
- // the encrypted payload which will be written. In addition, a 12-bytes nonce will be
- // prepended (non-encrypted).
- let payload_size = (data.len() + ENCRYPT_ALG.tag_len() + 12) as u64;
+ // the encrypted payload which will be written. In addition, a nonce will be prepended
+ // (non-encrypted).
+ let payload_size = (AES_256_GCM_NONCE_LENGTH + data.len() + AES_256_GCM_TAG_LENGTH) as u64;
// If the partition exists, make sure we don't change the partition size. If not (i.e.
// partition is not found), write the header at the empty place.
@@ -172,16 +180,19 @@
self.file.read_exact(&mut header)?;
// Generate a nonce randomly and recorde it on the disk first.
- let nonce = Nonce::assume_unique_for_key(rand::random::<[u8; 12]>());
+ let nonce = rand::random::<[u8; AES_256_GCM_NONCE_LENGTH]>();
self.file.seek(SeekFrom::Start(offset + PARTITION_HEADER_SIZE))?;
self.file.write_all(nonce.as_ref())?;
- // Then encrypt and sign the data. The non-encrypted input data is copied to a vector
- // because it is encrypted in place, and also the tag is appended.
- get_key()?.seal_in_place_append_tag(nonce, Aad::from(&header), &mut data)?;
+ // Then encrypt and sign the data.
+ let key = get_key()?;
+ let mut tag = [0; AES_256_GCM_TAG_LENGTH];
+ let ciphertext =
+ encrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &mut tag)?;
- // Persist the encrypted payload data
- self.file.write_all(&data)?;
+ // Persist the encrypted payload data and the tag.
+ self.file.write_all(&ciphertext)?;
+ self.file.write_all(&tag)?;
ioutil::blkflsbuf(&mut self.file)?;
Ok(())
@@ -257,63 +268,21 @@
Ok(ret)
}
-struct ZeroOnDropKey(LessSafeKey);
-
-impl Drop for ZeroOnDropKey {
- fn drop(&mut self) {
- // Zeroize the key by overwriting it with a key constructed from zeros of same length
- // This works because the raw key bytes are allocated inside the struct, not on the heap
- let zero = [0; 32];
- let zero_key = LessSafeKey::new(UnboundKey::new(ENCRYPT_ALG, &zero).unwrap());
- unsafe {
- ::std::ptr::write_volatile::<LessSafeKey>(&mut self.0, zero_key);
- }
- }
-}
-
-impl std::ops::Deref for ZeroOnDropKey {
- type Target = LessSafeKey;
- fn deref(&self) -> &LessSafeKey {
- &self.0
- }
-}
-
/// Returns the key that is used to encrypt the microdroid manager partition. It is derived from
/// the sealing CDI of the previous stage, which is Android Boot Loader (ABL).
-fn get_key() -> Result<ZeroOnDropKey> {
+fn get_key() -> Result<ZVec> {
// Sealing CDI from the previous stage.
let diced = wait_for_interface::<dyn IDiceNode>("android.security.dice.IDiceNode")
.context("IDiceNode service not found")?;
let bcc_handover = diced.derive(&[]).context("Failed to get BccHandover")?;
-
- // Derive a key from the Sealing CDI
- // Step 1 is extraction: https://datatracker.ietf.org/doc/html/rfc5869#section-2.2 where a
- // pseduo random key (PRK) is extracted from (Input Keying Material - IKM, which is secret) and
- // optional salt.
- let salt = Salt::new(HKDF_SHA256, &[]); // use 0 as salt
- let prk = salt.extract(&bcc_handover.cdiSeal); // Sealing CDI as IKM
-
- // Step 2 is expansion: https://datatracker.ietf.org/doc/html/rfc5869#section-2.3 where the PRK
- // (optionally with the `info` which gives contextual information) is expanded into the output
- // keying material (OKM). Note that the process fails only when the size of OKM is longer than
- // 255 * SHA256_HASH_SIZE (32), which isn't the case here.
- let info = [b"microdroid_manager_key".as_ref()];
- let okm = prk.expand(&info, HKDF_SHA256).unwrap(); // doesn't fail as explained above
- let mut key = [0; 32];
- okm.fill(&mut key).unwrap(); // doesn't fail as explained above
-
- // The term LessSafe might be misleading here. LessSafe here just means that the API can
- // possibly accept same nonces for different messages. However, since we encrypt/decrypt only a
- // single message (the microdroid_manager partition payload) with a randomly generated nonce,
- // this is safe enough.
- let ret = ZeroOnDropKey(LessSafeKey::new(UnboundKey::new(ENCRYPT_ALG, &key).unwrap()));
-
- // Don't forget to zeroize the raw key array as well
- unsafe {
- ::std::ptr::write_volatile::<[u8; 32]>(&mut key, [0; 32]);
- }
-
- Ok(ret)
+ // Deterministically derive another key to use for encrypting the data, rather than using the
+ // CDI directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
+ // input key material is already cryptographically strong.
+ let salt = &[];
+ let info = b"microdroid_manager_key".as_ref();
+ let mut key = ZVec::new(32)?;
+ hkdf(&mut key, Md::sha256(), &bcc_handover.cdiSeal, salt, info)?;
+ Ok(key)
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index f9b4cf7..929a96b 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -34,9 +34,9 @@
use log::{error, info};
use microdroid_metadata::{write_metadata, Metadata};
use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
+use openssl::sha::Sha512;
use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
use rand::Fill;
-use ring::digest;
use rustutils::system_properties;
use rustutils::system_properties::PropertyWatcher;
use std::convert::TryInto;
@@ -148,8 +148,8 @@
fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
// Calculate compound digests of code and authorities
- let mut code_hash_ctx = digest::Context::new(&digest::SHA512);
- let mut authority_hash_ctx = digest::Context::new(&digest::SHA512);
+ let mut code_hash_ctx = Sha512::new();
+ let mut authority_hash_ctx = Sha512::new();
code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
for extra_apk in &verified_data.extra_apks_data {
@@ -160,8 +160,8 @@
code_hash_ctx.update(apex.root_digest.as_ref());
authority_hash_ctx.update(apex.public_key.as_ref());
}
- let code_hash = code_hash_ctx.finish().as_ref().try_into().unwrap();
- let authority_hash = authority_hash_ctx.finish().as_ref().try_into().unwrap();
+ let code_hash = code_hash_ctx.finish();
+ let authority_hash = authority_hash_ctx.finish();
// {
// -70002: "Microdroid payload",