[attestation] Validate client VM's Microdroid kernel in service VM

This cl verifies the authority hash and code of the Microdroid kernel
against the digest of the hashes extracted during the build time.

Test: atest rialto_test
Bug: 271275206
Change-Id: I9f509bdd0d7bb996722ccce6182417668088ef6a
diff --git a/service_vm/fake_chain/Android.bp b/service_vm/fake_chain/Android.bp
index ebc185d..2bc7b4e 100644
--- a/service_vm/fake_chain/Android.bp
+++ b/service_vm/fake_chain/Android.bp
@@ -24,6 +24,7 @@
     visibility: [
         "//packages/modules/Virtualization/rialto:__subpackages__",
     ],
+    prefer_rlib: true,
     rustlibs: [
         "libcstr",
     ],
@@ -40,6 +41,7 @@
         "libcoset",
         "libdiced_open_dice",
         "liblog_rust",
+        "libmicrodroid_kernel_hashes",
     ],
 }
 
diff --git a/service_vm/fake_chain/src/client_vm.rs b/service_vm/fake_chain/src/client_vm.rs
index eb8654b..44ea898 100644
--- a/service_vm/fake_chain/src/client_vm.rs
+++ b/service_vm/fake_chain/src/client_vm.rs
@@ -24,21 +24,16 @@
 use coset::CborSerializable;
 use cstr::cstr;
 use diced_open_dice::{
-    retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
+    hash, retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
     DiceConfigValues, DiceError, DiceMode, InputValues, OwnedDiceArtifacts, Result, HASH_SIZE,
     HIDDEN_SIZE,
 };
 use log::error;
+use microdroid_kernel_hashes::{INITRD_DEBUG_HASH, KERNEL_HASH};
 
 type CborResult<T> = result::Result<T, ciborium::value::Error>;
 
 /// All the following data are generated with urandom.
-const CODE_HASH_KERNEL: [u8; HASH_SIZE] = [
-    0xc8, 0x54, 0x6c, 0xad, 0x9d, 0xe7, 0x25, 0xc7, 0x2b, 0xed, 0x07, 0xe1, 0xe9, 0x1a, 0xb0, 0xd0,
-    0xa7, 0x7f, 0x43, 0xb9, 0xe4, 0x56, 0x79, 0x0d, 0x7d, 0xd8, 0xc5, 0xdd, 0xad, 0x0d, 0x31, 0x85,
-    0xaf, 0x94, 0x02, 0xd8, 0x9d, 0x70, 0xab, 0xba, 0xac, 0xc7, 0x12, 0x80, 0xec, 0x7b, 0x9b, 0x65,
-    0xec, 0x6b, 0xdd, 0x64, 0x94, 0xd0, 0x9a, 0x3a, 0x09, 0xf2, 0x49, 0xdb, 0x60, 0x3c, 0x50, 0x30,
-];
 const CODE_HASH_PAYLOAD: [u8; HASH_SIZE] = [
     0x08, 0x78, 0xc2, 0x5b, 0xe7, 0xea, 0x3d, 0x62, 0x70, 0x22, 0xd9, 0x1c, 0x4f, 0x3c, 0x2e, 0x2f,
     0x0f, 0x97, 0xa4, 0x6f, 0x6d, 0xd5, 0xe6, 0x4a, 0x6d, 0xbe, 0x34, 0x2e, 0x56, 0x04, 0xaf, 0xef,
@@ -111,7 +106,7 @@
     // so the authority hash is the same.
     let authority_hash = service_vm::AUTHORITY_HASH_SERVICE_VM;
     let input_values = InputValues::new(
-        CODE_HASH_KERNEL,
+        kernel_code_hash()?,
         Config::Descriptor(config_descriptor.as_slice()),
         authority_hash,
         DiceMode::kDiceModeDebug,
@@ -179,3 +174,8 @@
         },
     ]
 }
+
+fn kernel_code_hash() -> Result<[u8; HASH_SIZE]> {
+    let code_hash = [KERNEL_HASH, INITRD_DEBUG_HASH].concat();
+    hash(&code_hash)
+}
diff --git a/service_vm/kernel/Android.bp b/service_vm/kernel/Android.bp
new file mode 100644
index 0000000..79158e6
--- /dev/null
+++ b/service_vm/kernel/Android.bp
@@ -0,0 +1,31 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_binary_host {
+    name: "extract_microdroid_kernel_hashes",
+    srcs: ["extract_microdroid_kernel_hashes.py"],
+}
+
+genrule {
+    name: "microdroid_kernel_hashes_rs",
+    srcs: [":microdroid_kernel"],
+    out: ["lib.rs"],
+    tools: [
+        "extract_microdroid_kernel_hashes",
+        "avbtool",
+    ],
+    cmd: "$(location extract_microdroid_kernel_hashes) $(location avbtool) $(in) > $(out)",
+}
+
+rust_library_rlib {
+    name: "libmicrodroid_kernel_hashes",
+    srcs: [":microdroid_kernel_hashes_rs"],
+    crate_name: "microdroid_kernel_hashes",
+    prefer_rlib: true,
+    no_stdlibs: true,
+    stdlibs: [
+        "libcompiler_builtins.rust_sysroot",
+        "libcore.rust_sysroot",
+    ],
+}
diff --git a/service_vm/kernel/extract_microdroid_kernel_hashes.py b/service_vm/kernel/extract_microdroid_kernel_hashes.py
new file mode 100644
index 0000000..538c787
--- /dev/null
+++ b/service_vm/kernel/extract_microdroid_kernel_hashes.py
@@ -0,0 +1,62 @@
+"""Extracts the following hashes from the AVB footer of Microdroid's kernel:
+
+- kernel hash
+- initrd_normal hash
+- initrd_debug hash
+
+The hashes are written to stdout as a Rust file.
+"""
+#!/usr/bin/env python3
+
+import sys
+import subprocess
+from typing import Dict
+
+PARTITION_NAME_BOOT = 'boot'
+PARTITION_NAME_INITRD_NORMAL = 'initrd_normal'
+PARTITION_NAME_INITRD_DEBUG = 'initrd_debug'
+
+def main(args):
+    """Main function."""
+    avbtool = args[0]
+    kernel_image_path = args[1]
+    hashes = collect_hashes(avbtool, kernel_image_path)
+    assert hashes.keys() == {PARTITION_NAME_BOOT,
+                             PARTITION_NAME_INITRD_NORMAL,
+                             PARTITION_NAME_INITRD_DEBUG}, hashes.keys()
+
+    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")
+
+    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])}];\n")
+
+def collect_hashes(avbtool: str, kernel_image_path: str) -> Dict[str, str]:
+    """Collects the hashes from the AVB footer of the kernel image."""
+    hashes = {}
+    with subprocess.Popen(
+        [avbtool, 'print_partition_digests', '--image', kernel_image_path],
+        stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc:
+        stdout, _ = proc.communicate()
+        for line in stdout.decode("utf-8").split("\n"):
+            line = line.replace(" ", "").split(":")
+            if len(line) == 2:
+                partition_name, hash_ = line
+                hashes[partition_name] = hash_
+    return hashes
+
+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
+    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)])
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/service_vm/requests/Android.bp b/service_vm/requests/Android.bp
index 52b54b4..57da012 100644
--- a/service_vm/requests/Android.bp
+++ b/service_vm/requests/Android.bp
@@ -24,6 +24,7 @@
         "libder_nostd",
         "libdiced_open_dice_nostd",
         "liblog_rust_nostd",
+        "libmicrodroid_kernel_hashes",
         "libserde_nostd",
         "libservice_vm_comm_nostd",
         "libspki_nostd",
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index cfdac2d..5b4735d 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -16,15 +16,20 @@
 //! client VM.
 
 use crate::cert;
-use crate::dice::{validate_client_vm_dice_chain_prefix_match, ClientVmDiceChain};
+use crate::dice::{
+    validate_client_vm_dice_chain_prefix_match, ClientVmDiceChain, DiceChainEntryPayload,
+};
 use crate::keyblob::decrypt_private_key;
 use alloc::vec::Vec;
-use bssl_avf::{rand_bytes, sha256, EcKey, PKey};
+use bssl_avf::{rand_bytes, sha256, Digester, EcKey, PKey};
+use cbor_util::value_to_array;
+use ciborium::value::Value;
 use core::result;
-use coset::{CborSerializable, CoseSign};
+use coset::{AsCborValue, CborSerializable, CoseSign, CoseSign1};
 use der::{Decode, Encode};
-use diced_open_dice::DiceArtifacts;
+use diced_open_dice::{DiceArtifacts, HASH_SIZE};
 use log::error;
+use microdroid_kernel_hashes::{INITRD_DEBUG_HASH, INITRD_NORMAL_HASH, KERNEL_HASH};
 use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
 use x509_cert::{certificate::Certificate, name::Name};
 
@@ -47,13 +52,22 @@
     // Validates the prefix of the Client VM DICE chain in the CSR.
     let service_vm_dice_chain =
         dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?;
+    let service_vm_dice_chain =
+        value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
     let client_vm_dice_chain =
-        validate_client_vm_dice_chain_prefix_match(&csr.dice_cert_chain, service_vm_dice_chain)?;
+        value_to_array(Value::from_slice(&csr.dice_cert_chain)?, "client_vm_dice_chain")?;
+    validate_client_vm_dice_chain_prefix_match(&client_vm_dice_chain, &service_vm_dice_chain)?;
     // Validates the signatures in the Client VM DICE chain and extracts the partially decoded
     // DiceChainEntryPayloads.
     let client_vm_dice_chain =
         ClientVmDiceChain::validate_signatures_and_parse_dice_chain(client_vm_dice_chain)?;
 
+    // The last entry in the service VM DICE chain describes the service VM, which should
+    // be signed with the same key as the kernel image.
+    let service_vm_entry = service_vm_dice_chain.last().unwrap();
+    validate_kernel_authority_hash(client_vm_dice_chain.microdroid_kernel(), service_vm_entry)?;
+    validate_kernel_code_hash(&client_vm_dice_chain)?;
+
     // AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
     let aad = &[];
 
@@ -121,3 +135,60 @@
     let digest = sha256(message)?;
     key.ecdsa_sign(&digest)
 }
+
+/// Validates that the authority hash of the Microdroid kernel in the Client VM DICE chain
+/// matches the authority hash of the service VM entry in the service VM DICE chain, because
+/// the Microdroid kernel is signed with the same key as the one used for the service VM.
+fn validate_kernel_authority_hash(
+    kernel: &DiceChainEntryPayload,
+    service_vm_entry: &Value,
+) -> Result<()> {
+    if expected_kernel_authority_hash(service_vm_entry)? == kernel.authority_hash {
+        Ok(())
+    } else {
+        error!("The authority hash of the Microdroid kernel does not match the expected value");
+        Err(RequestProcessingError::InvalidDiceChain)
+    }
+}
+
+/// Validates that the kernel code hash in the Client VM DICE chain matches the code hashes
+/// 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 {
+        return Ok(());
+    }
+    if expected_kernel_code_hash_debug()? == kernel.code_hash {
+        if dice_chain.all_entries_are_secure() {
+            error!("The Microdroid kernel has debug initrd but the DICE chain is secure");
+            return Err(RequestProcessingError::InvalidDiceChain);
+        }
+        return Ok(());
+    }
+    error!("The kernel code hash in the Client VM DICE chain does not match any expected values");
+    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 expected_kernel_authority_hash(service_vm_entry: &Value) -> Result<[u8; HASH_SIZE]> {
+    let cose_sign1 = CoseSign1::from_cbor_value(service_vm_entry.clone())?;
+    let payload = cose_sign1.payload.ok_or_else(|| {
+        error!("No payload found in the service VM DICE chain entry");
+        RequestProcessingError::InternalError
+    })?;
+    let service_vm = DiceChainEntryPayload::from_slice(&payload)?;
+    Ok(service_vm.authority_hash)
+}
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
index 557b678..8c804da 100644
--- a/service_vm/requests/src/dice.rs
+++ b/service_vm/requests/src/dice.rs
@@ -126,7 +126,7 @@
         Ok(())
     }
 
-    fn microdroid_kernel(&self) -> &DiceChainEntryPayload {
+    pub(crate) fn microdroid_kernel(&self) -> &DiceChainEntryPayload {
         &self.payloads[self.payloads.len() - 2]
     }
 
@@ -147,15 +147,11 @@
 /// Validates that the `client_vm_dice_chain` matches the `service_vm_dice_chain` up to the pvmfw
 /// entry.
 ///
-/// Returns a CBOR value array of the client VM's DICE chain if the verification succeeds.
+/// Returns `Ok(())` if the verification succeeds.
 pub(crate) fn validate_client_vm_dice_chain_prefix_match(
-    client_vm_dice_chain: &[u8],
-    service_vm_dice_chain: &[u8],
-) -> Result<Vec<Value>> {
-    let client_vm_dice_chain =
-        value_to_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
-    let service_vm_dice_chain =
-        value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
+    client_vm_dice_chain: &[Value],
+    service_vm_dice_chain: &[Value],
+) -> Result<()> {
     if service_vm_dice_chain.len() < 3 {
         // The service VM's DICE chain must contain the root key and at least two other entries
         // that describe:
@@ -180,7 +176,7 @@
         );
         return Err(RequestProcessingError::InvalidDiceChain);
     }
-    Ok(client_vm_dice_chain)
+    Ok(())
 }
 
 #[derive(Debug, Clone)]
@@ -208,11 +204,8 @@
     #[allow(dead_code)]
     subject_public_key: PublicKey,
     mode: DiceMode,
-    /// TODO(b/271275206): Verify Microdroid kernel authority and code hashes.
-    #[allow(dead_code)]
-    code_hash: [u8; HASH_SIZE],
-    #[allow(dead_code)]
-    authority_hash: [u8; HASH_SIZE],
+    pub(crate) code_hash: [u8; HASH_SIZE],
+    pub(crate) authority_hash: [u8; HASH_SIZE],
     config_descriptor: ConfigDescriptor,
 }
 
@@ -230,42 +223,42 @@
             error!("No payload found in the DICE chain entry");
             RequestProcessingError::InvalidDiceChain
         })?;
-        let entries = value_to_map(Value::from_slice(&payload)?, "DiceChainEntryPayload")?;
-        build_payload(entries)
+        Self::from_slice(&payload)
     }
-}
 
-fn build_payload(entries: Vec<(Value, Value)>) -> Result<DiceChainEntryPayload> {
-    let mut builder = PayloadBuilder::default();
-    for (key, value) in entries.into_iter() {
-        let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?;
-        match key {
-            SUBJECT_PUBLIC_KEY => {
-                let subject_public_key = value_to_bytes(value, "subject_public_key")?;
-                let subject_public_key = CoseKey::from_slice(&subject_public_key)?.try_into()?;
-                builder.subject_public_key(subject_public_key)?;
+    pub(crate) fn from_slice(data: &[u8]) -> Result<Self> {
+        let entries = value_to_map(Value::from_slice(data)?, "DiceChainEntryPayload")?;
+        let mut builder = PayloadBuilder::default();
+        for (key, value) in entries.into_iter() {
+            let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?;
+            match key {
+                SUBJECT_PUBLIC_KEY => {
+                    let subject_public_key = value_to_bytes(value, "subject_public_key")?;
+                    let subject_public_key =
+                        CoseKey::from_slice(&subject_public_key)?.try_into()?;
+                    builder.subject_public_key(subject_public_key)?;
+                }
+                MODE => builder.mode(to_mode(value)?)?,
+                CODE_HASH => {
+                    let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
+                    builder.code_hash(code_hash)?;
+                }
+                AUTHORITY_HASH => {
+                    let authority_hash =
+                        value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
+                    builder.authority_hash(authority_hash)?;
+                }
+                CONFIG_DESC => {
+                    let config_descriptor = value_to_bytes(value, "config_descriptor")?;
+                    let config_descriptor = ConfigDescriptor::from_slice(&config_descriptor)?;
+                    builder.config_descriptor(config_descriptor)?;
+                }
+                _ => {}
             }
-            MODE => builder.mode(to_mode(value)?)?,
-            CODE_HASH => {
-                let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
-                builder.code_hash(code_hash)?;
-            }
-            AUTHORITY_HASH => {
-                let authority_hash =
-                    value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
-                builder.authority_hash(authority_hash)?;
-            }
-            CONFIG_DESC => {
-                let config_descriptor = value_to_bytes(value, "config_descriptor")?;
-                let config_descriptor = ConfigDescriptor::from_slice(&config_descriptor)?;
-                builder.config_descriptor(config_descriptor)?;
-            }
-            _ => {}
         }
+        builder.build()
     }
-    builder.build()
 }
-
 /// Represents a partially decoded `ConfigurationDescriptor`.
 ///
 /// The whole `ConfigurationDescriptor` is defined in: