[attestation] Support VM attestation of different kernels
This cl embeds different microdroid kernels in RKP VM and
verifies the kernel hash from a VM against this allowlist.
Bug: 326363997
Test: atest VmAttestationTests rialto_test
Change-Id: I6038ac814fe09bc1e2041edb7b7eb4c327d7548c
diff --git a/service_vm/fake_chain/src/client_vm.rs b/service_vm/fake_chain/src/client_vm.rs
index 44ea898..6f956a7 100644
--- a/service_vm/fake_chain/src/client_vm.rs
+++ b/service_vm/fake_chain/src/client_vm.rs
@@ -29,7 +29,7 @@
HIDDEN_SIZE,
};
use log::error;
-use microdroid_kernel_hashes::{INITRD_DEBUG_HASH, KERNEL_HASH};
+use microdroid_kernel_hashes::OS_HASHES;
type CborResult<T> = result::Result<T, ciborium::value::Error>;
@@ -176,6 +176,7 @@
}
fn kernel_code_hash() -> Result<[u8; HASH_SIZE]> {
- let code_hash = [KERNEL_HASH, INITRD_DEBUG_HASH].concat();
+ let os_hashes = &OS_HASHES[0];
+ let code_hash = [os_hashes.kernel, os_hashes.initrd_debug].concat();
hash(&code_hash)
}
diff --git a/service_vm/kernel/Android.bp b/service_vm/kernel/Android.bp
index 79158e6..fbfed8e 100644
--- a/service_vm/kernel/Android.bp
+++ b/service_vm/kernel/Android.bp
@@ -9,13 +9,19 @@
genrule {
name: "microdroid_kernel_hashes_rs",
- srcs: [":microdroid_kernel"],
+ srcs: [
+ ":microdroid_kernel",
+ ":microdroid_gki-android14-6.1_kernel",
+ ],
out: ["lib.rs"],
tools: [
"extract_microdroid_kernel_hashes",
"avbtool",
],
- cmd: "$(location extract_microdroid_kernel_hashes) $(location avbtool) $(in) > $(out)",
+ cmd: "$(location extract_microdroid_kernel_hashes) --avbtool $(location avbtool) " +
+ "--kernel $(location :microdroid_kernel) " +
+ "$(location :microdroid_gki-android14-6.1_kernel) " +
+ "> $(out)",
}
rust_library_rlib {
diff --git a/service_vm/kernel/extract_microdroid_kernel_hashes.py b/service_vm/kernel/extract_microdroid_kernel_hashes.py
index 148e8be..f2c6ae7 100644
--- a/service_vm/kernel/extract_microdroid_kernel_hashes.py
+++ b/service_vm/kernel/extract_microdroid_kernel_hashes.py
@@ -12,40 +12,60 @@
"""
#!/usr/bin/env python3
-import sys
+import argparse
+from collections import defaultdict
import subprocess
from typing import Dict
PARTITION_NAME_BOOT = 'boot'
PARTITION_NAME_INITRD_NORMAL = 'initrd_normal'
PARTITION_NAME_INITRD_DEBUG = 'initrd_debug'
+HASH_SIZE = 32
def main(args):
"""Main function."""
- avbtool = args[0]
- kernel_image_path = args[1]
- hashes = collect_hashes(avbtool, kernel_image_path)
+ avbtool = args.avbtool
+ num_kernel_images = len(args.kernel)
print("//! This file is generated by extract_microdroid_kernel_hashes.py.")
print("//! It contains the hashes of the kernel and initrds.\n")
print("#![no_std]\n#![allow(missing_docs)]\n")
- # Microdroid's kernel is just an empty file in unsupportive environments
- # such as x86, in this case the hashes should be empty.
- if hashes.keys() != {PARTITION_NAME_BOOT,
- PARTITION_NAME_INITRD_NORMAL,
- PARTITION_NAME_INITRD_DEBUG}:
- print("/// The kernel is empty, no hashes are available.")
- hashes[PARTITION_NAME_BOOT] = ""
- hashes[PARTITION_NAME_INITRD_NORMAL] = ""
- hashes[PARTITION_NAME_INITRD_DEBUG] = ""
+ print("pub const HASH_SIZE: usize = " + str(HASH_SIZE) + ";\n")
+ print("pub struct OsHashes {")
+ print(" pub kernel: [u8; HASH_SIZE],")
+ print(" pub initrd_normal: [u8; HASH_SIZE],")
+ print(" pub initrd_debug: [u8; HASH_SIZE],")
+ print("}\n")
- print("pub const KERNEL_HASH: &[u8] = &["
- f"{format_hex_string(hashes[PARTITION_NAME_BOOT])}];\n")
- print("pub const INITRD_NORMAL_HASH: &[u8] = &["
- f"{format_hex_string(hashes[PARTITION_NAME_INITRD_NORMAL])}];\n")
- print("pub const INITRD_DEBUG_HASH: &[u8] = &["
- f"{format_hex_string(hashes[PARTITION_NAME_INITRD_DEBUG])}];")
+ hashes = defaultdict(list)
+ for kernel_image_path in args.kernel:
+ collected_hashes = collect_hashes(avbtool, kernel_image_path)
+
+ if collected_hashes.keys() == {PARTITION_NAME_BOOT,
+ PARTITION_NAME_INITRD_NORMAL,
+ PARTITION_NAME_INITRD_DEBUG}:
+ for partition_name, v in collected_hashes.items():
+ hashes[partition_name].append(v)
+ else:
+ # Microdroid's kernel is just an empty file in unsupportive
+ # environments such as x86, in this case the hashes should be empty.
+ print("/// The kernel is empty, no hashes are available.")
+ hashes[PARTITION_NAME_BOOT].append("")
+ hashes[PARTITION_NAME_INITRD_NORMAL].append("")
+ hashes[PARTITION_NAME_INITRD_DEBUG].append("")
+
+ print("pub const OS_HASHES: [OsHashes; " + str(num_kernel_images) + "] = [")
+ for i in range(num_kernel_images):
+ print("OsHashes {")
+ print(" kernel: [" +
+ format_hex_string(hashes[PARTITION_NAME_BOOT][i]) + "],")
+ print(" initrd_normal: [" +
+ format_hex_string(hashes[PARTITION_NAME_INITRD_NORMAL][i]) + "],")
+ print(" initrd_debug: [" +
+ format_hex_string(hashes[PARTITION_NAME_INITRD_DEBUG][i]) + "],")
+ print("},")
+ print("];")
def collect_hashes(avbtool: str, kernel_image_path: str) -> Dict[str, str]:
"""Collects the hashes from the AVB footer of the kernel image."""
@@ -63,11 +83,22 @@
def format_hex_string(hex_string: str) -> str:
"""Formats a hex string into a Rust array."""
- assert len(hex_string) % 2 == 0, \
- "Hex string must have even length: " + hex_string
+ if not hex_string:
+ return "0x00, " * HASH_SIZE
+ assert len(hex_string) == HASH_SIZE * 2, \
+ "Hex string must have length " + str(HASH_SIZE * 2) + ": " + \
+ hex_string
return ", ".join(["\n0x" + hex_string[i:i+2] if i % 32 == 0
else "0x" + hex_string[i:i+2]
for i in range(0, len(hex_string), 2)])
+def parse_args():
+ """Parses the command line arguments."""
+ parser = argparse.ArgumentParser(
+ "Extracts the hashes from the kernels' AVB footer")
+ parser.add_argument('--avbtool', help='Path to the avbtool binary')
+ parser.add_argument('--kernel', help='Path to the kernel image', nargs='+')
+ return parser.parse_args()
+
if __name__ == '__main__':
- main(sys.argv[1:])
+ main(parse_args())
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index d4474cf..15a3bd0 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -29,7 +29,7 @@
use der::{Decode, Encode};
use diced_open_dice::{DiceArtifacts, HASH_SIZE};
use log::{error, info};
-use microdroid_kernel_hashes::{INITRD_DEBUG_HASH, INITRD_NORMAL_HASH, KERNEL_HASH};
+use microdroid_kernel_hashes::{HASH_SIZE as KERNEL_HASH_SIZE, OS_HASHES};
use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
use x509_cert::{certificate::Certificate, name::Name};
@@ -159,10 +159,10 @@
/// embedded during the build time.
fn validate_kernel_code_hash(dice_chain: &ClientVmDiceChain) -> Result<()> {
let kernel = dice_chain.microdroid_kernel();
- if expected_kernel_code_hash_normal()? == kernel.code_hash {
+ if matches_any_kernel_code_hash(&kernel.code_hash, /* is_debug= */ false)? {
return Ok(());
}
- if expected_kernel_code_hash_debug()? == kernel.code_hash {
+ if matches_any_kernel_code_hash(&kernel.code_hash, /* is_debug= */ true)? {
if dice_chain.all_entries_are_secure() {
error!("The Microdroid kernel has debug initrd but the DICE chain is secure");
return Err(RequestProcessingError::InvalidDiceChain);
@@ -173,18 +173,20 @@
Err(RequestProcessingError::InvalidDiceChain)
}
-fn expected_kernel_code_hash_normal() -> bssl_avf::Result<Vec<u8>> {
- let mut code_hash = [0u8; 64];
- code_hash[0..32].copy_from_slice(KERNEL_HASH);
- code_hash[32..].copy_from_slice(INITRD_NORMAL_HASH);
- Digester::sha512().digest(&code_hash)
-}
-
-fn expected_kernel_code_hash_debug() -> bssl_avf::Result<Vec<u8>> {
- let mut code_hash = [0u8; 64];
- code_hash[0..32].copy_from_slice(KERNEL_HASH);
- code_hash[32..].copy_from_slice(INITRD_DEBUG_HASH);
- Digester::sha512().digest(&code_hash)
+fn matches_any_kernel_code_hash(actual_code_hash: &[u8], is_debug: bool) -> bssl_avf::Result<bool> {
+ for os_hash in OS_HASHES {
+ let mut code_hash = [0u8; KERNEL_HASH_SIZE * 2];
+ code_hash[0..KERNEL_HASH_SIZE].copy_from_slice(&os_hash.kernel);
+ if is_debug {
+ code_hash[KERNEL_HASH_SIZE..].copy_from_slice(&os_hash.initrd_debug);
+ } else {
+ code_hash[KERNEL_HASH_SIZE..].copy_from_slice(&os_hash.initrd_normal);
+ }
+ if Digester::sha512().digest(&code_hash)? == actual_code_hash {
+ return Ok(true);
+ }
+ }
+ Ok(false)
}
fn expected_kernel_authority_hash(service_vm_entry: &Value) -> Result<[u8; HASH_SIZE]> {