Merge "libfdt: Add FdtNode::name()" into main
diff --git a/Android.bp b/Android.bp
index 22581b0..2f6fc20 100644
--- a/Android.bp
+++ b/Android.bp
@@ -54,7 +54,7 @@
             cfgs: ["llpvm_changes"],
         },
         release_avf_enable_multi_tenant_microdroid_vm: {
-            cfgs: ["payload_not_root"],
+            cfgs: ["multi_tenant"],
         },
         release_avf_enable_remote_attestation: {
             cfgs: ["remote_attestation"],
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 171389b..adf6309 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -72,6 +72,9 @@
       "path": "packages/modules/Virtualization/libs/avb"
     },
     {
+      "path": "packages/modules/Virtualization/libs/bssl"
+    },
+    {
       "path": "packages/modules/Virtualization/libs/capabilities"
     },
     {
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index db3d4f6..dcb1cba 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -94,7 +94,7 @@
     }
     mount(&crypt_device, mountpoint)
         .with_context(|| format!("Unable to mount {:?}", crypt_device))?;
-    if cfg!(payload_not_root) && needs_formatting {
+    if cfg!(multi_tenant) && needs_formatting {
         set_root_dir_permissions(mountpoint)?;
     }
     Ok(())
diff --git a/javalib/Android.bp b/javalib/Android.bp
index a124af7..cbc2a17 100644
--- a/javalib/Android.bp
+++ b/javalib/Android.bp
@@ -2,12 +2,25 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+soong_config_module_type {
+    name: "avf_flag_aware_android_app",
+    module_type: "android_app",
+    config_namespace: "ANDROID",
+    bool_variables: ["release_avf_allow_preinstalled_apps"],
+    properties: ["manifest"],
+}
+
 // Defines our permissions
-android_app {
+avf_flag_aware_android_app {
     name: "android.system.virtualmachine.res",
     installable: true,
     apex_available: ["com.android.virt"],
     platform_apis: true,
+    soong_config_variables: {
+        release_avf_allow_preinstalled_apps: {
+            manifest: "AndroidManifestNext.xml",
+        },
+    },
 }
 
 java_sdk_library {
diff --git a/javalib/AndroidManifestNext.xml b/javalib/AndroidManifestNext.xml
new file mode 100644
index 0000000..ebcb8ba
--- /dev/null
+++ b/javalib/AndroidManifestNext.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.android.virtualmachine.res">
+
+  <!-- @SystemApi Allows an application to create and run a Virtual Machine
+       using the Virtualization Framework APIs
+       (android.system.virtualmachine.*).
+       <p>Protection level: signature|preinstalled|development
+       @hide
+  -->
+  <permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE"
+      android:protectionLevel="signature|preinstalled|development" />
+
+  <!-- @hide Allows an application to run a Virtual Machine with a custom
+       kernel or a Microdroid configuration file.
+       <p>Not for use by third-party applications.
+  -->
+  <permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE"
+      android:protectionLevel="signature|development" />
+
+  <!-- @hide Allows an application to access various Virtual Machine debug
+       facilities, e.g. list all running VMs.
+       <p>Not for use by third-party applications.
+  -->
+  <permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE"
+      android:protectionLevel="signature" />
+
+  <application android:hasCode="false" />
+</manifest>
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 7c61712..12c099d 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -20,7 +20,7 @@
   public class VirtualMachineManager {
     method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public boolean isFeatureEnabled(String) throws android.system.virtualmachine.VirtualMachineException;
     field public static final String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
-    field public static final String FEATURE_PAYLOAD_NOT_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
+    field public static final String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
     field public static final String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
   }
 
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index e45fe99..a4927db 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -107,7 +107,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(
             prefix = "FEATURE_",
-            value = {FEATURE_DICE_CHANGES, FEATURE_PAYLOAD_NOT_ROOT, FEATURE_VENDOR_MODULES})
+            value = {FEATURE_DICE_CHANGES, FEATURE_MULTI_TENANT, FEATURE_VENDOR_MODULES})
     public @interface Features {}
 
     /**
@@ -123,8 +123,7 @@
      * @hide
      */
     @TestApi
-    public static final String FEATURE_PAYLOAD_NOT_ROOT =
-            IVirtualizationService.FEATURE_PAYLOAD_NON_ROOT;
+    public static final String FEATURE_MULTI_TENANT = IVirtualizationService.FEATURE_MULTI_TENANT;
 
     /**
      * Feature to allow vendor modules in Microdroid.
diff --git a/libs/bssl/Android.bp b/libs/bssl/Android.bp
new file mode 100644
index 0000000..0a2f334
--- /dev/null
+++ b/libs/bssl/Android.bp
@@ -0,0 +1,48 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libbssl_avf_defaults",
+    crate_name: "bssl_avf",
+    srcs: ["src/lib.rs"],
+    prefer_rlib: true,
+    apex_available: [
+        "com.android.virt",
+    ],
+}
+
+rust_library_rlib {
+    name: "libbssl_avf_nostd",
+    defaults: ["libbssl_avf_defaults"],
+    no_stdlibs: true,
+    stdlibs: [
+        "libcompiler_builtins.rust_sysroot",
+        "libcore.rust_sysroot",
+    ],
+    rustlibs: [
+        "libbssl_avf_error_nostd",
+        "libbssl_ffi_nostd",
+        "libcoset_nostd",
+        "liblog_rust_nostd",
+        "libzeroize_nostd",
+    ],
+}
+
+rust_defaults {
+    name: "libbssl_avf_test_defaults",
+    crate_name: "bssl_avf_test",
+    srcs: ["tests/tests.rs"],
+    test_suites: ["general-tests"],
+    static_libs: [
+        "libcrypto_baremetal",
+    ],
+}
+
+rust_test {
+    name: "libbssl_avf_nostd.test",
+    defaults: ["libbssl_avf_test_defaults"],
+    rustlibs: [
+        "libbssl_avf_nostd",
+    ],
+}
diff --git a/libs/bssl/TEST_MAPPING b/libs/bssl/TEST_MAPPING
new file mode 100644
index 0000000..a91e8c5
--- /dev/null
+++ b/libs/bssl/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" : "libbssl_avf_nostd.test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/libs/service_vm_comm/Android.bp b/libs/bssl/error/Android.bp
similarity index 61%
copy from libs/service_vm_comm/Android.bp
copy to libs/bssl/error/Android.bp
index cdb8fc3..dc2902e 100644
--- a/libs/service_vm_comm/Android.bp
+++ b/libs/bssl/error/Android.bp
@@ -3,9 +3,8 @@
 }
 
 rust_defaults {
-    name: "libservice_vm_comm_defaults",
-    crate_name: "service_vm_comm",
-    defaults: ["avf_build_flags_rust"],
+    name: "libbssl_avf_error_defaults",
+    crate_name: "bssl_avf_error",
     srcs: ["src/lib.rs"],
     prefer_rlib: true,
     apex_available: [
@@ -14,10 +13,11 @@
 }
 
 rust_library_rlib {
-    name: "libservice_vm_comm_nostd",
-    defaults: ["libservice_vm_comm_defaults"],
+    name: "libbssl_avf_error_nostd",
+    defaults: ["libbssl_avf_error_defaults"],
     no_stdlibs: true,
     stdlibs: [
+        "libcompiler_builtins.rust_sysroot",
         "libcore.rust_sysroot",
     ],
     rustlibs: [
@@ -26,12 +26,12 @@
 }
 
 rust_library {
-    name: "libservice_vm_comm",
-    defaults: ["libservice_vm_comm_defaults"],
-    rustlibs: [
-        "libserde",
-    ],
+    name: "libbssl_avf_error",
+    defaults: ["libbssl_avf_error_defaults"],
     features: [
         "std",
     ],
+    rustlibs: [
+        "libserde",
+    ],
 }
diff --git a/libs/bssl/error/src/code.rs b/libs/bssl/error/src/code.rs
new file mode 100644
index 0000000..7fb36c4
--- /dev/null
+++ b/libs/bssl/error/src/code.rs
@@ -0,0 +1,98 @@
+// 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 core::fmt;
+use serde::{Deserialize, Serialize};
+
+type BsslReasonCode = i32;
+type BsslLibraryCode = i32;
+
+/// BoringSSL reason code.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum ReasonCode {
+    NoError,
+    Global(GlobalError),
+    Cipher(CipherError),
+    Unknown(BsslReasonCode, BsslLibraryCode),
+}
+
+impl fmt::Display for ReasonCode {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::NoError => write!(f, "No error in the BoringSSL error queue."),
+            Self::Unknown(code, lib) => {
+                write!(f, "Unknown reason code '{code}' from the library '{lib}'")
+            }
+            other => write!(f, "{other:?}"),
+        }
+    }
+}
+
+/// Global errors may occur in any library.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/err.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum GlobalError {
+    Fatal,
+    MallocFailure,
+    ShouldNotHaveBeenCalled,
+    PassedNullParameter,
+    InternalError,
+    Overflow,
+}
+
+impl fmt::Display for GlobalError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "A global error occurred: {self:?}")
+    }
+}
+
+/// Errors occurred in the Cipher functions.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/cipher.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum CipherError {
+    AesKeySetupFailed,
+    BadDecrypt,
+    BadKeyLength,
+    BufferTooSmall,
+    CtrlNotImplemented,
+    CtrlOperationNotImplemented,
+    DataNotMultipleOfBlockLength,
+    InitializationError,
+    InputNotInitialized,
+    InvalidAdSize,
+    InvalidKeyLength,
+    InvalidNonceSize,
+    InvalidOperation,
+    IvTooLarge,
+    NoCipherSet,
+    OutputAliasesInput,
+    TagTooLarge,
+    TooLarge,
+    WrongFinalBlockLength,
+    NoDirectionSet,
+    InvalidNonce,
+}
+
+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
new file mode 100644
index 0000000..547ad43
--- /dev/null
+++ b/libs/bssl/error/src/lib.rs
@@ -0,0 +1,68 @@
+// 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.
+
+//! Errors and relating structs thrown by the BoringSSL wrapper library.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+mod code;
+
+use core::{fmt, result};
+use serde::{Deserialize, Serialize};
+
+pub use crate::code::{CipherError, GlobalError, ReasonCode};
+
+/// libbssl_avf result type.
+pub type Result<T> = result::Result<T, Error>;
+
+/// Error type used by libbssl_avf.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Error {
+    /// Failed to invoke a BoringSSL API.
+    CallFailed(ApiName, ReasonCode),
+
+    /// An unexpected internal error occurred.
+    InternalError,
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::CallFailed(api_name, reason) => {
+                write!(f, "Failed to invoke the BoringSSL API: {api_name:?}. Reason: {reason}")
+            }
+            Self::InternalError => write!(f, "An unexpected internal error occurred"),
+        }
+    }
+}
+
+/// BoringSSL API names.
+#[allow(missing_docs)]
+#[allow(non_camel_case_types)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum ApiName {
+    BN_new,
+    BN_bn2bin_padded,
+    CBB_flush,
+    CBB_len,
+    EC_KEY_check_key,
+    EC_KEY_generate_key,
+    EC_KEY_get0_group,
+    EC_KEY_get0_public_key,
+    EC_KEY_marshal_private_key,
+    EC_KEY_new_by_curve_name,
+    EC_POINT_get_affine_coordinates,
+    HKDF,
+    HMAC,
+}
diff --git a/libs/bssl/src/cbb.rs b/libs/bssl/src/cbb.rs
new file mode 100644
index 0000000..9b5f7fe
--- /dev/null
+++ b/libs/bssl/src/cbb.rs
@@ -0,0 +1,53 @@
+// 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.
+
+//! Helpers for using BoringSSL CBB (crypto byte builder) objects.
+
+use bssl_ffi::{CBB_init_fixed, CBB};
+use core::marker::PhantomData;
+use core::mem::MaybeUninit;
+
+/// Wraps a CBB that references a existing fixed-sized buffer; no memory is allocated, but the
+/// buffer cannot grow.
+pub struct CbbFixed<'a> {
+    cbb: CBB,
+    /// The CBB contains a mutable reference to the buffer, disguised as a pointer.
+    /// Make sure the borrow checker knows that.
+    _buffer: PhantomData<&'a mut [u8]>,
+}
+
+impl<'a> CbbFixed<'a> {
+    /// Create a new CBB that writes to the given buffer.
+    pub fn new(buffer: &'a mut [u8]) -> Self {
+        let mut cbb = MaybeUninit::uninit();
+        // SAFETY: `CBB_init_fixed()` is infallible and always returns one.
+        // The buffer remains valid during the lifetime of `cbb`.
+        unsafe { CBB_init_fixed(cbb.as_mut_ptr(), buffer.as_mut_ptr(), buffer.len()) };
+        // SAFETY: `cbb` has just been initialized by `CBB_init_fixed()`.
+        let cbb = unsafe { cbb.assume_init() };
+        Self { cbb, _buffer: PhantomData }
+    }
+}
+
+impl<'a> AsRef<CBB> for CbbFixed<'a> {
+    fn as_ref(&self) -> &CBB {
+        &self.cbb
+    }
+}
+
+impl<'a> AsMut<CBB> for CbbFixed<'a> {
+    fn as_mut(&mut self) -> &mut CBB {
+        &mut self.cbb
+    }
+}
diff --git a/libs/bssl/src/digest.rs b/libs/bssl/src/digest.rs
new file mode 100644
index 0000000..49e66e6
--- /dev/null
+++ b/libs/bssl/src/digest.rs
@@ -0,0 +1,49 @@
+// 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 digest functions in BoringSSL digest.h.
+
+use bssl_ffi::{EVP_MD_size, EVP_sha256, EVP_sha512, EVP_MD};
+
+/// Message digester wrapping `EVP_MD`.
+#[derive(Clone, Debug)]
+pub struct Digester(pub(crate) &'static EVP_MD);
+
+impl Digester {
+    /// Returns a `Digester` implementing `SHA-256` algorithm.
+    pub fn sha256() -> 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_sha256() };
+        // SAFETY: The returned pointer should always be valid and points to a static
+        // `EVP_MD`.
+        Self(unsafe { &*p })
+    }
+
+    /// Returns a `Digester` implementing `SHA-512` algorithm.
+    pub fn sha512() -> 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_sha512() };
+        // SAFETY: The returned pointer should always be valid and points to a static
+        // `EVP_MD`.
+        Self(unsafe { &*p })
+    }
+
+    /// Returns the digest size in bytes.
+    pub fn size(&self) -> usize {
+        // SAFETY: The inner pointer is fetched from EVP_* hash functions in BoringSSL digest.h
+        unsafe { EVP_MD_size(self.0) }
+    }
+}
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
new file mode 100644
index 0000000..7038e21
--- /dev/null
+++ b/libs/bssl/src/ec_key.rs
@@ -0,0 +1,203 @@
+// 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.
+
+//! Contains struct and functions that wraps the API related to EC_KEY in
+//! BoringSSL.
+
+use crate::cbb::CbbFixed;
+use crate::util::{check_int_result, to_call_failed_error};
+use alloc::vec::Vec;
+use bssl_avf_error::{ApiName, Error, Result};
+use bssl_ffi::{
+    BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, EC_KEY_free, EC_KEY_generate_key,
+    EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_marshal_private_key,
+    EC_KEY_new_by_curve_name, EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM,
+    EC_GROUP, EC_KEY, EC_POINT,
+};
+use core::ptr::{self, NonNull};
+use core::result;
+use coset::{iana, CoseKey, CoseKeyBuilder};
+use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
+
+const P256_AFFINE_COORDINATE_SIZE: usize = 32;
+
+type Coordinate = [u8; P256_AFFINE_COORDINATE_SIZE];
+
+/// Wrapper of an `EC_KEY` object, representing a public or private EC key.
+pub struct EcKey(NonNull<EC_KEY>);
+
+impl Drop for EcKey {
+    fn drop(&mut self) {
+        // SAFETY: It is safe because the key has been allocated by BoringSSL and isn't
+        // used after this.
+        unsafe { EC_KEY_free(self.0.as_ptr()) }
+    }
+}
+
+impl EcKey {
+    /// Creates a new EC P-256 key pair.
+    pub fn new_p256() -> Result<Self> {
+        // SAFETY: The returned pointer is checked below.
+        let ec_key = unsafe {
+            EC_KEY_new_by_curve_name(NID_X9_62_prime256v1) // EC P-256 CURVE Nid
+        };
+        let mut ec_key = NonNull::new(ec_key)
+            .map(Self)
+            .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))?;
+        ec_key.generate_key()?;
+        Ok(ec_key)
+    }
+
+    /// Generates a random, private key, calculates the corresponding public key and stores both
+    /// in the `EC_KEY`.
+    fn generate_key(&mut self) -> Result<()> {
+        // SAFETY: The non-null pointer is created with `EC_KEY_new_by_curve_name` and should
+        // point to a valid `EC_KEY`.
+        // The randomness is provided by `getentropy()` in `vmbase`.
+        let ret = unsafe { EC_KEY_generate_key(self.0.as_ptr()) };
+        check_int_result(ret, ApiName::EC_KEY_generate_key)
+    }
+
+    /// Returns the `CoseKey` for the public key.
+    pub fn cose_public_key(&self) -> Result<CoseKey> {
+        const ALGO: iana::Algorithm = iana::Algorithm::ES256;
+        const CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
+
+        let (x, y) = self.public_key_coordinates()?;
+        let key =
+            CoseKeyBuilder::new_ec2_pub_key(CURVE, x.to_vec(), y.to_vec()).algorithm(ALGO).build();
+        Ok(key)
+    }
+
+    /// Returns the x and y coordinates of the public key.
+    fn public_key_coordinates(&self) -> Result<(Coordinate, Coordinate)> {
+        let ec_group = self.ec_group()?;
+        let ec_point = self.public_key_ec_point()?;
+        let mut x = BigNum::new()?;
+        let mut y = BigNum::new()?;
+        let ctx = ptr::null_mut();
+        // SAFETY: All the parameters are checked non-null and initialized when needed.
+        // The last parameter `ctx` is generated when needed inside the function.
+        let ret = unsafe {
+            EC_POINT_get_affine_coordinates(ec_group, ec_point, x.as_mut_ptr(), y.as_mut_ptr(), ctx)
+        };
+        check_int_result(ret, ApiName::EC_POINT_get_affine_coordinates)?;
+        Ok((x.try_into()?, y.try_into()?))
+    }
+
+    /// Returns a pointer to the public key point inside `EC_KEY`. The memory region pointed
+    /// by the pointer is owned by the `EC_KEY`.
+    fn public_key_ec_point(&self) -> Result<*const EC_POINT> {
+        let ec_point =
+           // SAFETY: It is safe since the key pair has been generated and stored in the
+           // `EC_KEY` pointer.
+           unsafe { EC_KEY_get0_public_key(self.0.as_ptr()) };
+        if ec_point.is_null() {
+            Err(to_call_failed_error(ApiName::EC_KEY_get0_public_key))
+        } else {
+            Ok(ec_point)
+        }
+    }
+
+    /// Returns a pointer to the `EC_GROUP` object inside `EC_KEY`. The memory region pointed
+    /// by the pointer is owned by the `EC_KEY`.
+    fn ec_group(&self) -> Result<*const EC_GROUP> {
+        let group =
+           // SAFETY: It is safe since the key pair has been generated and stored in the
+           // `EC_KEY` pointer.
+           unsafe { EC_KEY_get0_group(self.0.as_ptr()) };
+        if group.is_null() {
+            Err(to_call_failed_error(ApiName::EC_KEY_get0_group))
+        } else {
+            Ok(group)
+        }
+    }
+
+    /// Returns the DER-encoded ECPrivateKey structure described in RFC 5915 Section 3:
+    ///
+    /// https://datatracker.ietf.org/doc/html/rfc5915#section-3
+    pub fn private_key(&self) -> Result<ZVec> {
+        const CAPACITY: usize = 256;
+        let mut buf = Zeroizing::new([0u8; CAPACITY]);
+        let mut cbb = CbbFixed::new(buf.as_mut());
+        let enc_flags = 0;
+        let ret =
+            // SAFETY: The function only write bytes to the buffer managed by the valid `CBB`
+            // object, and the key has been allocated by BoringSSL.
+            unsafe { EC_KEY_marshal_private_key(cbb.as_mut(), self.0.as_ptr(), enc_flags) };
+
+        check_int_result(ret, ApiName::EC_KEY_marshal_private_key)?;
+        // SAFETY: This is safe because the CBB pointer is a valid pointer initialized with
+        // `CBB_init_fixed()`.
+        check_int_result(unsafe { CBB_flush(cbb.as_mut()) }, ApiName::CBB_flush)?;
+        // SAFETY: This is safe because the CBB pointer is initialized with `CBB_init_fixed()`,
+        // and it has been flushed, thus it has no active children.
+        let len = unsafe { CBB_len(cbb.as_ref()) };
+        Ok(buf.get(0..len).ok_or(to_call_failed_error(ApiName::CBB_len))?.to_vec().into())
+    }
+}
+
+/// A u8 vector that is zeroed when dropped.
+#[derive(Zeroize, ZeroizeOnDrop)]
+pub struct ZVec(Vec<u8>);
+
+impl ZVec {
+    /// Extracts a slice containing the entire vector.
+    pub fn as_slice(&self) -> &[u8] {
+        &self.0[..]
+    }
+}
+
+impl From<Vec<u8>> for ZVec {
+    fn from(v: Vec<u8>) -> Self {
+        Self(v)
+    }
+}
+
+struct BigNum(NonNull<BIGNUM>);
+
+impl Drop for BigNum {
+    fn drop(&mut self) {
+        // SAFETY: The pointer has been created with `BN_new`.
+        unsafe { BN_clear_free(self.as_mut_ptr()) }
+    }
+}
+
+impl BigNum {
+    fn new() -> Result<Self> {
+        // SAFETY: The returned pointer is checked below.
+        let bn = unsafe { BN_new() };
+        NonNull::new(bn).map(Self).ok_or(to_call_failed_error(ApiName::BN_new))
+    }
+
+    fn as_mut_ptr(&mut self) -> *mut BIGNUM {
+        self.0.as_ptr()
+    }
+}
+
+/// Converts the `BigNum` to a big-endian integer. The integer is padded with leading zeros up to
+/// size `N`. The conversion fails if `N` is smaller thanthe size of the integer.
+impl<const N: usize> TryFrom<BigNum> for [u8; N] {
+    type Error = Error;
+
+    fn try_from(bn: BigNum) -> result::Result<Self, Self::Error> {
+        let mut num = [0u8; N];
+        // SAFETY: The `BIGNUM` pointer has been created with `BN_new`.
+        let ret = unsafe { BN_bn2bin_padded(num.as_mut_ptr(), num.len(), bn.0.as_ptr()) };
+        check_int_result(ret, ApiName::BN_bn2bin_padded)?;
+        Ok(num)
+    }
+}
+
+// TODO(b/301068421): Unit tests the EcKey.
diff --git a/libs/bssl/src/err.rs b/libs/bssl/src/err.rs
new file mode 100644
index 0000000..1ee40c9
--- /dev/null
+++ b/libs/bssl/src/err.rs
@@ -0,0 +1,112 @@
+// 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 error handling functions in BoringSSL err.h.
+
+use bssl_avf_error::{CipherError, GlobalError, ReasonCode};
+use bssl_ffi::{self, ERR_get_error, ERR_GET_LIB_RUST, ERR_GET_REASON_RUST};
+
+const NO_ERROR_REASON_CODE: i32 = 0;
+
+/// Returns the reason code for the least recent error and removes that
+/// error from the error queue.
+pub(crate) fn get_error_reason_code() -> ReasonCode {
+    let packed_error = get_packed_error();
+    let reason = get_reason(packed_error);
+    let lib = get_lib(packed_error);
+    map_to_reason_code(reason, lib)
+}
+
+/// Returns the packed error code for the least recent error and removes that
+/// error from the error queue.
+///
+/// Returns 0 if there are no errors in the queue.
+fn get_packed_error() -> u32 {
+    // SAFETY: This function only reads the error queue.
+    unsafe { ERR_get_error() }
+}
+
+fn get_reason(packed_error: u32) -> i32 {
+    // SAFETY: This function only reads the given error code.
+    unsafe { ERR_GET_REASON_RUST(packed_error) }
+}
+
+/// Returns the library code for the error.
+fn get_lib(packed_error: u32) -> i32 {
+    // SAFETY: This function only reads the given error code.
+    unsafe { ERR_GET_LIB_RUST(packed_error) }
+}
+
+fn map_to_reason_code(reason: i32, lib: i32) -> ReasonCode {
+    if reason == NO_ERROR_REASON_CODE {
+        return ReasonCode::NoError;
+    }
+    map_global_reason_code(reason)
+        .map(ReasonCode::Global)
+        .or_else(|| map_library_reason_code(reason, lib))
+        .unwrap_or(ReasonCode::Unknown(reason, lib))
+}
+
+/// Global errors may occur in any library.
+fn map_global_reason_code(reason: i32) -> Option<GlobalError> {
+    let reason = match reason {
+        bssl_ffi::ERR_R_FATAL => GlobalError::Fatal,
+        bssl_ffi::ERR_R_MALLOC_FAILURE => GlobalError::MallocFailure,
+        bssl_ffi::ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED => GlobalError::ShouldNotHaveBeenCalled,
+        bssl_ffi::ERR_R_PASSED_NULL_PARAMETER => GlobalError::PassedNullParameter,
+        bssl_ffi::ERR_R_INTERNAL_ERROR => GlobalError::InternalError,
+        bssl_ffi::ERR_R_OVERFLOW => GlobalError::Overflow,
+        _ => return None,
+    };
+    Some(reason)
+}
+
+fn map_library_reason_code(reason: i32, lib: i32) -> Option<ReasonCode> {
+    u32::try_from(lib).ok().and_then(|x| match x {
+        bssl_ffi::ERR_LIB_CIPHER => map_cipher_reason_code(reason).map(ReasonCode::Cipher),
+        _ => None,
+    })
+}
+
+fn map_cipher_reason_code(reason: i32) -> Option<CipherError> {
+    let error = match reason {
+        bssl_ffi::CIPHER_R_AES_KEY_SETUP_FAILED => CipherError::AesKeySetupFailed,
+        bssl_ffi::CIPHER_R_BAD_DECRYPT => CipherError::BadDecrypt,
+        bssl_ffi::CIPHER_R_BAD_KEY_LENGTH => CipherError::BadKeyLength,
+        bssl_ffi::CIPHER_R_BUFFER_TOO_SMALL => CipherError::BufferTooSmall,
+        bssl_ffi::CIPHER_R_CTRL_NOT_IMPLEMENTED => CipherError::CtrlNotImplemented,
+        bssl_ffi::CIPHER_R_CTRL_OPERATION_NOT_IMPLEMENTED => {
+            CipherError::CtrlOperationNotImplemented
+        }
+        bssl_ffi::CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH => {
+            CipherError::DataNotMultipleOfBlockLength
+        }
+        bssl_ffi::CIPHER_R_INITIALIZATION_ERROR => CipherError::InitializationError,
+        bssl_ffi::CIPHER_R_INPUT_NOT_INITIALIZED => CipherError::InputNotInitialized,
+        bssl_ffi::CIPHER_R_INVALID_AD_SIZE => CipherError::InvalidAdSize,
+        bssl_ffi::CIPHER_R_INVALID_KEY_LENGTH => CipherError::InvalidKeyLength,
+        bssl_ffi::CIPHER_R_INVALID_NONCE_SIZE => CipherError::InvalidNonceSize,
+        bssl_ffi::CIPHER_R_INVALID_OPERATION => CipherError::InvalidOperation,
+        bssl_ffi::CIPHER_R_IV_TOO_LARGE => CipherError::IvTooLarge,
+        bssl_ffi::CIPHER_R_NO_CIPHER_SET => CipherError::NoCipherSet,
+        bssl_ffi::CIPHER_R_OUTPUT_ALIASES_INPUT => CipherError::OutputAliasesInput,
+        bssl_ffi::CIPHER_R_TAG_TOO_LARGE => CipherError::TagTooLarge,
+        bssl_ffi::CIPHER_R_TOO_LARGE => CipherError::TooLarge,
+        bssl_ffi::CIPHER_R_WRONG_FINAL_BLOCK_LENGTH => CipherError::WrongFinalBlockLength,
+        bssl_ffi::CIPHER_R_NO_DIRECTION_SET => CipherError::NoDirectionSet,
+        bssl_ffi::CIPHER_R_INVALID_NONCE => CipherError::InvalidNonce,
+        _ => return None,
+    };
+    Some(error)
+}
diff --git a/libs/bssl/src/hkdf.rs b/libs/bssl/src/hkdf.rs
new file mode 100644
index 0000000..5dc6876
--- /dev/null
+++ b/libs/bssl/src/hkdf.rs
@@ -0,0 +1,49 @@
+// 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 HKDF functions in BoringSSL hkdf.h.
+
+use crate::digest::Digester;
+use crate::util::check_int_result;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::HKDF;
+
+/// Computes HKDF (as specified by [RFC 5869]) of initial keying material `secret` with
+/// `salt` and `info` using the given `digester`.
+///
+/// [RFC 5869]: https://www.rfc-editor.org/rfc/rfc5869.html
+pub fn hkdf<const N: usize>(
+    secret: &[u8],
+    salt: &[u8],
+    info: &[u8],
+    digester: Digester,
+) -> Result<[u8; N]> {
+    let mut key = [0u8; N];
+    // SAFETY: Only reads from/writes to the provided slices and the digester was non-null.
+    let ret = unsafe {
+        HKDF(
+            key.as_mut_ptr(),
+            key.len(),
+            digester.0,
+            secret.as_ptr(),
+            secret.len(),
+            salt.as_ptr(),
+            salt.len(),
+            info.as_ptr(),
+            info.len(),
+        )
+    };
+    check_int_result(ret, ApiName::HKDF)?;
+    Ok(key)
+}
diff --git a/libs/bssl/src/hmac.rs b/libs/bssl/src/hmac.rs
new file mode 100644
index 0000000..ddbbe4a
--- /dev/null
+++ b/libs/bssl/src/hmac.rs
@@ -0,0 +1,59 @@
+// 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 HMAC functions in BoringSSL hmac.h.
+
+use crate::digest::Digester;
+use crate::util::to_call_failed_error;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::{HMAC, SHA256_DIGEST_LENGTH};
+
+const SHA256_LEN: usize = SHA256_DIGEST_LENGTH as usize;
+
+/// Computes the HMAC using SHA-256 for the given `data` with the given `key`.
+pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; SHA256_LEN]> {
+    hmac::<SHA256_LEN>(key, data, Digester::sha256())
+}
+
+/// Computes the HMAC for the given `data` with the given `key` and `digester`.
+///
+/// The output size `HASH_LEN` should correspond to the length of the hash function's
+/// digest size in bytes.
+fn hmac<const HASH_LEN: usize>(
+    key: &[u8],
+    data: &[u8],
+    digester: Digester,
+) -> Result<[u8; HASH_LEN]> {
+    assert_eq!(digester.size(), HASH_LEN);
+
+    let mut out = [0u8; HASH_LEN];
+    let mut out_len = 0;
+    // SAFETY: Only reads from/writes to the provided slices and the digester was non-null.
+    let ret = unsafe {
+        HMAC(
+            digester.0,
+            key.as_ptr() as *const _,
+            key.len(),
+            data.as_ptr(),
+            data.len(),
+            out.as_mut_ptr(),
+            &mut out_len,
+        )
+    };
+    if !ret.is_null() && out_len == (out.len() as u32) {
+        Ok(out)
+    } else {
+        Err(to_call_failed_error(ApiName::HMAC))
+    }
+}
diff --git a/libs/service_vm_comm/src/lib.rs b/libs/bssl/src/lib.rs
similarity index 67%
copy from libs/service_vm_comm/src/lib.rs
copy to libs/bssl/src/lib.rs
index ca97ca1..8b38f5b 100644
--- a/libs/service_vm_comm/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -12,17 +12,24 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! This library contains the communication protocol used between the host
-//! and the service VM.
+//! Safe wrappers around the BoringSSL API.
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
 extern crate alloc;
 
-mod message;
-mod vsock;
+mod cbb;
+mod digest;
+mod ec_key;
+mod err;
+mod hkdf;
+mod hmac;
+mod util;
 
-pub use message::{
-    EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, ServiceVmRequest,
-};
-pub use vsock::VmType;
+pub use bssl_avf_error::{ApiName, CipherError, Error, ReasonCode, Result};
+
+pub use cbb::CbbFixed;
+pub use digest::Digester;
+pub use ec_key::{EcKey, ZVec};
+pub use hkdf::hkdf;
+pub use hmac::hmac_sha256;
diff --git a/libs/bssl/src/util.rs b/libs/bssl/src/util.rs
new file mode 100644
index 0000000..880c85b
--- /dev/null
+++ b/libs/bssl/src/util.rs
@@ -0,0 +1,37 @@
+// 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.
+
+//! Utility functions.
+
+use crate::err::get_error_reason_code;
+use bssl_avf_error::{ApiName, Error, Result};
+use log::error;
+
+pub(crate) fn check_int_result(ret: i32, api_name: ApiName) -> Result<()> {
+    match ret {
+        1 => Ok(()),
+        0 => Err(Error::CallFailed(api_name, get_error_reason_code())),
+        _ => {
+            error!(
+                "Received a return value ({}) other than 0 or 1 from the BoringSSL API: {:?}",
+                ret, api_name
+            );
+            Err(Error::InternalError)
+        }
+    }
+}
+
+pub(crate) fn to_call_failed_error(api_name: ApiName) -> Error {
+    Error::CallFailed(api_name, get_error_reason_code())
+}
diff --git a/libs/bssl/tests/hkdf_test.rs b/libs/bssl/tests/hkdf_test.rs
new file mode 100644
index 0000000..1cda042
--- /dev/null
+++ b/libs/bssl/tests/hkdf_test.rs
@@ -0,0 +1,95 @@
+// 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.
+
+//! Test HKDF with the test cases in [RFC 5869] Appendix A
+//!
+//! [RFC 5869]: https://datatracker.ietf.org/doc/html/rfc5869
+
+use bssl_avf::{hkdf, Digester, Result};
+
+#[test]
+fn rfc5869_test_case_1() -> Result<()> {
+    const IKM: [u8; 22] = [
+        0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+        0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+    ];
+    const SALT: [u8; 13] =
+        [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c];
+    const INFO: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
+    const L: usize = 42;
+    const OKM: [u8; L] = [
+        0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f,
+        0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4,
+        0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
+    ];
+    assert_eq!(OKM, hkdf::<L>(&IKM, &SALT, &INFO, Digester::sha256())?);
+    Ok(())
+}
+
+#[test]
+fn rfc5869_test_case_2() -> Result<()> {
+    const IKM: [u8; 80] = [
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+        0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+        0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+        0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+        0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+        0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+    ];
+    const SALT: [u8; 80] = [
+        0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
+        0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d,
+        0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c,
+        0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+        0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+        0xab, 0xac, 0xad, 0xae, 0xaf,
+    ];
+    const INFO: [u8; 80] = [
+        0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe,
+        0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
+        0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc,
+        0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,
+        0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa,
+        0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+    ];
+    const L: usize = 82;
+    const OKM: [u8; L] = [
+        0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49,
+        0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf,
+        0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb, 0x41, 0xc6, 0x5e,
+        0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, 0x36, 0x77, 0x93, 0xa9,
+        0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, 0xc1, 0x4c, 0x01,
+        0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87,
+    ];
+    assert_eq!(OKM, hkdf::<L>(&IKM, &SALT, &INFO, Digester::sha256())?);
+    Ok(())
+}
+
+#[test]
+fn rfc5869_test_case_3() -> Result<()> {
+    const IKM: [u8; 22] = [
+        0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+        0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+    ];
+    const SALT: [u8; 0] = [];
+    const INFO: [u8; 0] = [];
+    const L: usize = 42;
+    const OKM: [u8; L] = [
+        0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a,
+        0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73,
+        0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8,
+    ];
+    assert_eq!(OKM, hkdf::<L>(&IKM, &SALT, &INFO, Digester::sha256())?);
+    Ok(())
+}
diff --git a/libs/bssl/tests/hmac_test.rs b/libs/bssl/tests/hmac_test.rs
new file mode 100644
index 0000000..c09a863
--- /dev/null
+++ b/libs/bssl/tests/hmac_test.rs
@@ -0,0 +1,115 @@
+// 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.
+
+//! Tests HMAC with the test cases in [RFC 4231] Section 4
+//!
+//! [RFC 4231]: https://datatracker.ietf.org/doc/html/rfc4231
+
+use bssl_avf::{hmac_sha256, Result};
+
+#[test]
+fn rfc4231_test_case_1() -> Result<()> {
+    const KEY: &[u8; 20] = &[0x0b; 20];
+    const DATA: &[u8] = b"Hi There";
+    const HMAC_SHA256: [u8; 32] = [
+        0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1,
+        0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32,
+        0xcf, 0xf7,
+    ];
+    assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+    Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_2() -> Result<()> {
+    const KEY: &[u8] = b"Jefe";
+    const DATA: &[u8] = b"what do ya want for nothing?";
+    const HMAC_SHA256: [u8; 32] = [
+        0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, 0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75,
+        0xc7, 0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83, 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec,
+        0x38, 0x43,
+    ];
+    assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+    Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_3() -> Result<()> {
+    const KEY: &[u8; 20] = &[0xaa; 20];
+    const DATA: &[u8; 50] = &[0xdd; 50];
+    const HMAC_SHA256: [u8; 32] = [
+        0x77, 0x3e, 0xa9, 0x1e, 0x36, 0x80, 0x0e, 0x46, 0x85, 0x4d, 0xb8, 0xeb, 0xd0, 0x91, 0x81,
+        0xa7, 0x29, 0x59, 0x09, 0x8b, 0x3e, 0xf8, 0xc1, 0x22, 0xd9, 0x63, 0x55, 0x14, 0xce, 0xd5,
+        0x65, 0xfe,
+    ];
+    assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+    Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_4() -> Result<()> {
+    const KEY: &[u8; 25] = &[
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
+    ];
+    const DATA: &[u8; 50] = &[0xcd; 50];
+    const HMAC_SHA256: [u8; 32] = [
+        0x82, 0x55, 0x8a, 0x38, 0x9a, 0x44, 0x3c, 0x0e, 0xa4, 0xcc, 0x81, 0x98, 0x99, 0xf2, 0x08,
+        0x3a, 0x85, 0xf0, 0xfa, 0xa3, 0xe5, 0x78, 0xf8, 0x07, 0x7a, 0x2e, 0x3f, 0xf4, 0x67, 0x29,
+        0x66, 0x5b,
+    ];
+    assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+    Ok(())
+}
+
+/// Test with a truncation of output to 128 bits.
+#[test]
+fn rfc4231_test_case_5() -> Result<()> {
+    const KEY: &[u8; 20] = &[0x0c; 20];
+    const DATA: &[u8] = b"Test With Truncation";
+    const HMAC_SHA256: [u8; 16] = [
+        0xa3, 0xb6, 0x16, 0x74, 0x73, 0x10, 0x0e, 0xe0, 0x6e, 0x0c, 0x79, 0x6c, 0x29, 0x55, 0x55,
+        0x2b,
+    ];
+    let res = hmac_sha256(KEY, DATA)?;
+    assert_eq!(HMAC_SHA256, res[..16]);
+    Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_6() -> Result<()> {
+    const KEY: &[u8; 131] = &[0xaa; 131];
+    const DATA: &[u8] = b"Test Using Larger Than Block-Size Key - Hash Key First";
+    const HMAC_SHA256: [u8; 32] = [
+        0x60, 0xe4, 0x31, 0x59, 0x1e, 0xe0, 0xb6, 0x7f, 0x0d, 0x8a, 0x26, 0xaa, 0xcb, 0xf5, 0xb7,
+        0x7f, 0x8e, 0x0b, 0xc6, 0x21, 0x37, 0x28, 0xc5, 0x14, 0x05, 0x46, 0x04, 0x0f, 0x0e, 0xe3,
+        0x7f, 0x54,
+    ];
+    assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+    Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_7() -> Result<()> {
+    const KEY: &[u8; 131] = &[0xaa; 131];
+    const DATA: &str = "This is a test using a larger than block-size key and a larger than \
+           block-size data. The key needs to be hashed before being used by the HMAC algorithm.";
+    const HMAC_SHA256: [u8; 32] = [
+        0x9b, 0x09, 0xff, 0xa7, 0x1b, 0x94, 0x2f, 0xcb, 0x27, 0x63, 0x5f, 0xbc, 0xd5, 0xb0, 0xe9,
+        0x44, 0xbf, 0xdc, 0x63, 0x64, 0x4f, 0x07, 0x13, 0x93, 0x8a, 0x7f, 0x51, 0x53, 0x5c, 0x3a,
+        0x35, 0xe2,
+    ];
+    assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA.as_bytes())?);
+    Ok(())
+}
diff --git a/rialto/src/requests/mod.rs b/libs/bssl/tests/tests.rs
similarity index 83%
copy from rialto/src/requests/mod.rs
copy to libs/bssl/tests/tests.rs
index 2ed568c..1077787 100644
--- a/rialto/src/requests/mod.rs
+++ b/libs/bssl/tests/tests.rs
@@ -12,9 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! This module contains functions for the request processing.
+//! API tests of the crate `bssl_avf`.
 
-mod api;
-mod rkp;
-
-pub use api::process_request;
+mod hkdf_test;
+mod hmac_test;
diff --git a/libs/hyp/src/hypervisor/mod.rs b/libs/hyp/src/hypervisor.rs
similarity index 100%
rename from libs/hyp/src/hypervisor/mod.rs
rename to libs/hyp/src/hypervisor.rs
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
index 05fdb4a..280257b 100644
--- a/libs/libfdt/src/iterators.rs
+++ b/libs/libfdt/src/iterators.rs
@@ -14,6 +14,7 @@
 
 //! Iterators over cells, and various layers on top of them.
 
+use crate::FdtError;
 use crate::{AddrCells, SizeCells};
 use core::marker::PhantomData;
 use core::{mem::size_of, ops::Range, slice::ChunksExact};
@@ -59,6 +60,21 @@
     pub size: Option<T>,
 }
 
+impl<T: TryInto<usize>> TryFrom<Reg<T>> for Range<usize> {
+    type Error = FdtError;
+
+    fn try_from(reg: Reg<T>) -> Result<Self, Self::Error> {
+        let addr = to_usize(reg.addr)?;
+        let size = to_usize(reg.size.ok_or(FdtError::NotFound)?)?;
+        let end = addr.checked_add(size).ok_or(FdtError::BadValue)?;
+        Ok(addr..end)
+    }
+}
+
+fn to_usize<T: TryInto<usize>>(num: T) -> Result<usize, FdtError> {
+    num.try_into().map_err(|_| FdtError::BadValue)
+}
+
 impl<'a> RegIterator<'a> {
     pub(crate) fn new(
         cells: CellIterator<'a>,
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 43eadae..19ce0f7 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -349,7 +349,8 @@
         self.fdt
     }
 
-    fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
+    /// Returns the compatible node of the given name that is next after this node.
+    pub fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
         // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
         let ret = unsafe {
             libfdt_bindgen::fdt_node_offset_by_compatible(
@@ -362,6 +363,11 @@
         Ok(fdt_err_or_option(ret)?.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
+    /// Returns the first range of `reg` in this node.
+    pub fn first_reg(&self) -> Result<Reg<u64>> {
+        self.reg()?.ok_or(FdtError::NotFound)?.next().ok_or(FdtError::NotFound)
+    }
+
     fn address_cells(&self) -> Result<AddrCells> {
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
         unsafe { libfdt_bindgen::fdt_address_cells(self.fdt.as_ptr(), self.offset) }
@@ -534,7 +540,7 @@
         Ok(FdtNode { fdt: &*self.fdt, offset: fdt_err(ret)? })
     }
 
-    /// Return the compatible node of the given name that is next to this node
+    /// Returns the compatible node of the given name that is next after this node
     pub fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
         // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
         let ret = unsafe {
@@ -804,13 +810,12 @@
     }
 
     fn check_full(&self) -> Result<()> {
-        let len = self.buffer.len();
         // SAFETY: Only performs read accesses within the limits of the slice. If successful, this
         // call guarantees to other unsafe calls that the header contains a valid totalsize (w.r.t.
         // 'len' i.e. the self.fdt slice) that those C functions can use to perform bounds
         // checking. The library doesn't maintain an internal state (such as pointers) between
         // calls as it expects the client code to keep track of the objects (DT, nodes, ...).
-        let ret = unsafe { libfdt_bindgen::fdt_check_full(self.as_ptr(), len) };
+        let ret = unsafe { libfdt_bindgen::fdt_check_full(self.as_ptr(), self.capacity()) };
         fdt_err_expect_zero(ret)
     }
 
diff --git a/libs/microdroid_uids/src/lib.rs b/libs/microdroid_uids/src/lib.rs
index 04dc190..0248c61 100644
--- a/libs/microdroid_uids/src/lib.rs
+++ b/libs/microdroid_uids/src/lib.rs
@@ -29,7 +29,7 @@
 // helps avoid confusion.)
 
 /// Group ID shared by all payload users.
-pub const MICRODROID_PAYLOAD_GID: u32 = if cfg!(payload_not_root) { 6000 } else { 0 };
+pub const MICRODROID_PAYLOAD_GID: u32 = if cfg!(multi_tenant) { 6000 } else { 0 };
 
 /// User ID for the initial payload user.
-pub const MICRODROID_PAYLOAD_UID: u32 = if cfg!(payload_not_root) { 6000 } else { 0 };
+pub const MICRODROID_PAYLOAD_UID: u32 = if cfg!(multi_tenant) { 6000 } else { 0 };
diff --git a/libs/service_vm_comm/src/message.rs b/libs/service_vm_comm/src/message.rs
deleted file mode 100644
index 80956cb..0000000
--- a/libs/service_vm_comm/src/message.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-// 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.
-
-//! This module contains the requests and responses definitions exchanged
-//! between the host and the service VM.
-
-use alloc::vec::Vec;
-
-use serde::{Deserialize, Serialize};
-
-type MacedPublicKey = Vec<u8>;
-
-/// The main request type to be sent to the service VM.
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub enum ServiceVmRequest {
-    /// A request to be processed by the service VM.
-    ///
-    /// Each request has a corresponding response item.
-    Process(Request),
-
-    /// Shuts down the service VM. No response is expected from it.
-    Shutdown,
-}
-
-/// Represents a process request to be sent to the service VM.
-///
-/// Each request has a corresponding response item.
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub enum Request {
-    /// Reverse the order of the bytes in the provided byte array.
-    /// Currently this is only used for testing.
-    Reverse(Vec<u8>),
-
-    /// Generates a new ECDSA P-256 key pair that can be attested by the remote
-    /// server.
-    GenerateEcdsaP256KeyPair,
-
-    /// Creates a certificate signing request to be sent to the
-    /// provisioning server.
-    GenerateCertificateRequest(GenerateCertificateRequestParams),
-}
-
-/// Represents a response to a request sent to the service VM.
-///
-/// Each response corresponds to a specific request.
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub enum Response {
-    /// Reverse the order of the bytes in the provided byte array.
-    Reverse(Vec<u8>),
-
-    /// Returns the new ECDSA P-256 key pair.
-    GenerateEcdsaP256KeyPair(EcdsaP256KeyPair),
-
-    /// Returns a CBOR Certificate Signing Request (Csr) serialized into a byte array.
-    GenerateCertificateRequest(Vec<u8>),
-}
-
-/// Represents the params passed to GenerateCertificateRequest
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct GenerateCertificateRequestParams {
-    /// Contains the set of keys to certify.
-    pub keys_to_sign: Vec<MacedPublicKey>,
-
-    /// challenge contains a byte strong from the provisioning server which will be
-    /// included in the signed data of the CSR structure.
-    /// The supported sizes is between 0 and 64 bytes, inclusive.
-    pub challenge: Vec<u8>,
-}
-
-/// Represents an ECDSA P-256 key pair.
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct EcdsaP256KeyPair {
-    /// Contains a CBOR-encoded public key specified in:
-    ///
-    /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
-    pub maced_public_key: MacedPublicKey,
-
-    /// Contains a handle to the private key.
-    pub key_blob: Vec<u8>,
-}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index bac93a4..b494cfa 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -418,6 +418,7 @@
     ],
     properties: [
         "rollback_index",
+        "props",
     ],
 }
 
@@ -447,6 +448,12 @@
     soong_config_variables: {
         release_avf_enable_llpvm_changes: {
             rollback_index: 1,
+            props: [
+                {
+                    name: "com.android.virt.cap",
+                    value: "secretkeeper_protection",
+                },
+            ],
         },
     },
 }
@@ -487,6 +494,12 @@
     soong_config_variables: {
         release_avf_enable_llpvm_changes: {
             rollback_index: 1,
+            props: [
+                {
+                    name: "com.android.virt.cap",
+                    value: "secretkeeper_protection",
+                },
+            ],
         },
     },
 }
diff --git a/microdroid/init_debug_policy/Android.bp b/microdroid/init_debug_policy/Android.bp
index ed017e5..2a87ac9 100644
--- a/microdroid/init_debug_policy/Android.bp
+++ b/microdroid/init_debug_policy/Android.bp
@@ -10,7 +10,6 @@
     rustlibs: [
         "librustutils",
     ],
-    installable: false, // match with microdroid_init_rc.
     bootstrap: true,
     prefer_rlib: true,
 }
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index e9cb0ec..dd0ddbb 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -144,18 +144,9 @@
         Owned(format!("MICRODROID_UNKNOWN_RUNTIME_ERROR|{:?}", err))
     };
 
-    let death_reason_bytes = death_reason.as_bytes();
-    let mut sent_total = 0;
-    while sent_total < death_reason_bytes.len() {
+    for chunk in death_reason.as_bytes().chunks(16) {
         // TODO(b/220071963): Sometimes, sending more than 16 bytes at once makes MM hang.
-        let begin = sent_total;
-        let end = std::cmp::min(begin.saturating_add(16), death_reason_bytes.len());
-        OpenOptions::new()
-            .read(false)
-            .write(true)
-            .open(FAILURE_SERIAL_DEVICE)?
-            .write_all(&death_reason_bytes[begin..end])?;
-        sent_total = end;
+        OpenOptions::new().read(false).write(true).open(FAILURE_SERIAL_DEVICE)?.write_all(chunk)?;
     }
 
     Ok(())
@@ -738,9 +729,11 @@
         let mount_dir = format!("/mnt/extra-apk/{i}");
         create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
 
-        // don't wait, just detach
+        let mount_for_exec =
+            if cfg!(multi_tenant) { MountForExec::Allowed } else { MountForExec::Disallowed };
+        // These run asynchronously in parallel - we wait later for them to complete.
         zipfuse.mount(
-            MountForExec::Disallowed,
+            mount_for_exec,
             "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
             Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
             Path::new(&mount_dir),
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 523334f..8c21030 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -12,6 +12,7 @@
     ],
     rustlibs: [
         "libaarch64_paging",
+        "libbssl_avf_nostd",
         "libbssl_ffi_nostd",
         "libciborium_nostd",
         "libciborium_io_nostd",
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index 73d188b..6df1c4d 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -42,6 +42,8 @@
         ":test_image_with_unknown_vm_type_prop",
         ":test_image_with_multiple_props",
         ":test_image_with_duplicated_capability",
+        ":test_image_with_rollback_index_5",
+        ":test_image_with_multiple_capabilities",
         ":unsigned_test_image",
     ],
     prefer_rlib: true,
@@ -194,3 +196,26 @@
     private_key: ":pvmfw_sign_key",
     salt: "1111",
 }
+
+avb_add_hash_footer {
+    name: "test_image_with_rollback_index_5",
+    src: ":unsigned_test_image",
+    partition_name: "boot",
+    private_key: ":pvmfw_sign_key",
+    salt: "1211",
+    rollback_index: 5,
+}
+
+avb_add_hash_footer {
+    name: "test_image_with_multiple_capabilities",
+    src: ":unsigned_test_image",
+    partition_name: "boot",
+    private_key: ":pvmfw_sign_key",
+    salt: "2134",
+    props: [
+        {
+            name: "com.android.virt.cap",
+            value: "remote_attest|secretkeeper_protection",
+        },
+    ],
+}
diff --git a/pvmfw/avb/src/descriptor/mod.rs b/pvmfw/avb/src/descriptor.rs
similarity index 100%
rename from pvmfw/avb/src/descriptor/mod.rs
rename to pvmfw/avb/src/descriptor.rs
diff --git a/pvmfw/avb/src/ops.rs b/pvmfw/avb/src/ops.rs
index 539291b..c7b8b01 100644
--- a/pvmfw/avb/src/ops.rs
+++ b/pvmfw/avb/src/ops.rs
@@ -229,10 +229,14 @@
     _rollback_index_location: usize,
     out_rollback_index: *mut u64,
 ) -> AvbIOResult {
-    // Rollback protection is not yet implemented, but this method is required by
-    // `avb_slot_verify()`.
-    // We set `out_rollback_index` to 0 to ensure that the default rollback index (0)
-    // is never smaller than it, thus the rollback index check will pass.
+    // This method is used by `avb_slot_verify()` to read the stored_rollback_index at
+    // rollback_index_location.
+
+    // TODO(291213394) : Refine this comment once capability for rollback protection is defined.
+    // pvmfw does not compare stored_rollback_index with rollback_index for Antirollback protection
+    // Hence, we set `out_rollback_index` to 0 to ensure that the
+    // rollback_index (including default: 0) is never smaller than it,
+    // thus the rollback index check will pass.
     result_to_io_enum(write(out_rollback_index, 0))
 }
 
@@ -334,4 +338,8 @@
             unsafe { slice::from_raw_parts(data.loaded_partitions, data.num_loaded_partitions) };
         Ok(loaded_partitions)
     }
+
+    pub(crate) fn rollback_indexes(&self) -> &[u64] {
+        &self.as_ref().rollback_indexes
+    }
 }
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index ac945e2..492d387 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -23,6 +23,9 @@
 use avb_bindgen::{AvbPartitionData, AvbVBMetaData};
 use core::ffi::c_char;
 
+// We use this for the rollback_index field if AvbSlotVerifyDataWrap has empty rollback_indexes
+const DEFAULT_ROLLBACK_INDEX: u64 = 0;
+
 /// Verified data returned when the payload verification succeeds.
 #[derive(Debug, PartialEq, Eq)]
 pub struct VerifiedBootData<'a> {
@@ -36,6 +39,15 @@
     pub public_key: &'a [u8],
     /// VM capabilities.
     pub capabilities: Vec<Capability>,
+    /// Rollback index of kernel.
+    pub rollback_index: u64,
+}
+
+impl VerifiedBootData<'_> {
+    /// Returns whether the kernel have the given capability
+    pub fn has_capability(&self, cap: Capability) -> bool {
+        self.capabilities.contains(&cap)
+    }
 }
 
 /// This enum corresponds to the `DebugLevel` in `VirtualMachineConfig`.
@@ -48,15 +60,18 @@
 }
 
 /// VM Capability.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum Capability {
     /// Remote attestation.
     RemoteAttest,
+    /// Secretkeeper protected secrets.
+    SecretkeeperProtection,
 }
 
 impl Capability {
     const KEY: &[u8] = b"com.android.virt.cap";
     const REMOTE_ATTEST: &[u8] = b"remote_attest";
+    const SECRETKEEPER_PROTECTION: &[u8] = b"secretkeeper_protection";
     const SEPARATOR: u8 = b'|';
 
     fn get_capabilities(property_value: &[u8]) -> Result<Vec<Self>, PvmfwVerifyError> {
@@ -65,6 +80,7 @@
         for v in property_value.split(|b| *b == Self::SEPARATOR) {
             let cap = match v {
                 Self::REMOTE_ATTEST => Self::RemoteAttest,
+                Self::SECRETKEEPER_PROTECTION => Self::SecretkeeperProtection,
                 _ => return Err(PvmfwVerifyError::UnknownVbmetaProperty),
             };
             if res.contains(&cap) {
@@ -153,6 +169,10 @@
     let kernel_verify_result = ops.verify_partition(PartitionName::Kernel.as_cstr())?;
 
     let vbmeta_images = kernel_verify_result.vbmeta_images()?;
+    // TODO(b/302093437): Use explicit rollback_index_location instead of default
+    // location (first element).
+    let rollback_index =
+        *kernel_verify_result.rollback_indexes().first().unwrap_or(&DEFAULT_ROLLBACK_INDEX);
     verify_only_one_vbmeta_exists(vbmeta_images)?;
     let vbmeta_image = vbmeta_images[0];
     verify_vbmeta_is_from_kernel_partition(&vbmeta_image)?;
@@ -171,6 +191,7 @@
             initrd_digest: None,
             public_key: trusted_public_key,
             capabilities,
+            rollback_index,
         });
     }
 
@@ -196,5 +217,6 @@
         initrd_digest: Some(*initrd_descriptor.digest),
         public_key: trusted_public_key,
         capabilities,
+        rollback_index,
     })
 }
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index e7e4dcc..6344433 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -23,6 +23,7 @@
 use utils::*;
 
 const TEST_IMG_WITH_ONE_HASHDESC_PATH: &str = "test_image_with_one_hashdesc.img";
+const TEST_IMG_WITH_ROLLBACK_INDEX_5: &str = "test_image_with_rollback_index_5.img";
 const TEST_IMG_WITH_PROP_DESC_PATH: &str = "test_image_with_prop_desc.img";
 const TEST_IMG_WITH_SERVICE_VM_PROP_PATH: &str = "test_image_with_service_vm_prop.img";
 const TEST_IMG_WITH_UNKNOWN_VM_TYPE_PROP_PATH: &str = "test_image_with_unknown_vm_type_prop.img";
@@ -31,6 +32,7 @@
 const TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH: &str = "test_image_with_non_initrd_hashdesc.img";
 const TEST_IMG_WITH_INITRD_AND_NON_INITRD_DESC_PATH: &str =
     "test_image_with_initrd_and_non_initrd_desc.img";
+const TEST_IMG_WITH_MULTIPLE_CAPABILITIES: &str = "test_image_with_multiple_capabilities.img";
 const UNSIGNED_TEST_IMG_PATH: &str = "unsigned_test.img";
 
 const RANDOM_FOOTER_POS: usize = 30;
@@ -60,7 +62,7 @@
     let public_key = load_trusted_public_key()?;
     let verified_boot_data = verify_payload(
         &fs::read(TEST_IMG_WITH_ONE_HASHDESC_PATH)?,
-        /*initrd=*/ None,
+        /* initrd= */ None,
         &public_key,
     )
     .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
@@ -72,6 +74,7 @@
         initrd_digest: None,
         public_key: &public_key,
         capabilities: vec![],
+        rollback_index: 0,
     };
     assert_eq!(expected_boot_data, verified_boot_data);
 
@@ -82,7 +85,7 @@
 fn payload_with_non_initrd_descriptor_fails_verification_with_no_initrd() -> Result<()> {
     assert_payload_verification_fails(
         &fs::read(TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH)?,
-        /*initrd=*/ None,
+        /* initrd= */ None,
         &load_trusted_public_key()?,
         PvmfwVerifyError::InvalidDescriptors(avb::IoError::NoSuchPartition),
     )
@@ -103,7 +106,7 @@
     let public_key = load_trusted_public_key()?;
     let verified_boot_data = verify_payload(
         &fs::read(TEST_IMG_WITH_SERVICE_VM_PROP_PATH)?,
-        /*initrd=*/ None,
+        /* initrd= */ None,
         &public_key,
     )
     .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
@@ -115,6 +118,7 @@
         initrd_digest: None,
         public_key: &public_key,
         capabilities: vec![Capability::RemoteAttest],
+        rollback_index: 0,
     };
     assert_eq!(expected_boot_data, verified_boot_data);
 
@@ -125,7 +129,7 @@
 fn payload_with_unknown_vm_type_fails_verification_with_no_initrd() -> Result<()> {
     assert_payload_verification_fails(
         &fs::read(TEST_IMG_WITH_UNKNOWN_VM_TYPE_PROP_PATH)?,
-        /*initrd=*/ None,
+        /* initrd= */ None,
         &load_trusted_public_key()?,
         PvmfwVerifyError::UnknownVbmetaProperty,
     )
@@ -135,7 +139,7 @@
 fn payload_with_multiple_props_fails_verification_with_no_initrd() -> Result<()> {
     assert_payload_verification_fails(
         &fs::read(TEST_IMG_WITH_MULTIPLE_PROPS_PATH)?,
-        /*initrd=*/ None,
+        /* initrd= */ None,
         &load_trusted_public_key()?,
         PvmfwVerifyError::InvalidDescriptors(avb::IoError::Io),
     )
@@ -145,7 +149,7 @@
 fn payload_with_duplicated_capability_fails_verification_with_no_initrd() -> Result<()> {
     assert_payload_verification_fails(
         &fs::read(TEST_IMG_WITH_DUPLICATED_CAP_PATH)?,
-        /*initrd=*/ None,
+        /* initrd= */ None,
         &load_trusted_public_key()?,
         avb::SlotVerifyError::InvalidMetadata.into(),
     )
@@ -155,7 +159,7 @@
 fn payload_with_prop_descriptor_fails_verification_with_no_initrd() -> Result<()> {
     assert_payload_verification_fails(
         &fs::read(TEST_IMG_WITH_PROP_DESC_PATH)?,
-        /*initrd=*/ None,
+        /* initrd= */ None,
         &load_trusted_public_key()?,
         PvmfwVerifyError::UnknownVbmetaProperty,
     )
@@ -165,7 +169,7 @@
 fn payload_expecting_initrd_fails_verification_with_no_initrd() -> Result<()> {
     assert_payload_verification_fails(
         &load_latest_signed_kernel()?,
-        /*initrd=*/ None,
+        /* initrd= */ None,
         &load_trusted_public_key()?,
         avb::SlotVerifyError::InvalidMetadata.into(),
     )
@@ -176,7 +180,7 @@
     assert_payload_verification_with_initrd_fails(
         &load_latest_signed_kernel()?,
         &load_latest_initrd_normal()?,
-        /*trusted_public_key=*/ &[0u8; 0],
+        /* trusted_public_key= */ &[0u8; 0],
         avb::SlotVerifyError::PublicKeyRejected.into(),
     )
 }
@@ -186,7 +190,7 @@
     assert_payload_verification_with_initrd_fails(
         &load_latest_signed_kernel()?,
         &load_latest_initrd_normal()?,
-        /*trusted_public_key=*/ &[0u8; 512],
+        /* trusted_public_key= */ &[0u8; 512],
         avb::SlotVerifyError::PublicKeyRejected.into(),
     )
 }
@@ -205,7 +209,7 @@
 fn payload_with_an_invalid_initrd_fails_verification() -> Result<()> {
     assert_payload_verification_with_initrd_fails(
         &load_latest_signed_kernel()?,
-        /*initrd=*/ &fs::read(UNSIGNED_TEST_IMG_PATH)?,
+        /* initrd= */ &fs::read(UNSIGNED_TEST_IMG_PATH)?,
         &load_trusted_public_key()?,
         avb::SlotVerifyError::Verification.into(),
     )
@@ -383,3 +387,41 @@
         avb::SlotVerifyError::Verification.into(),
     )
 }
+
+#[test]
+fn payload_with_rollback_index() -> Result<()> {
+    let public_key = load_trusted_public_key()?;
+    let verified_boot_data = verify_payload(
+        &fs::read(TEST_IMG_WITH_ROLLBACK_INDEX_5)?,
+        /* initrd= */ None,
+        &public_key,
+    )
+    .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
+
+    let kernel_digest = hash(&[&hex::decode("1211")?, &fs::read(UNSIGNED_TEST_IMG_PATH)?]);
+    let expected_boot_data = VerifiedBootData {
+        debug_level: DebugLevel::None,
+        kernel_digest,
+        initrd_digest: None,
+        public_key: &public_key,
+        capabilities: vec![],
+        rollback_index: 5,
+    };
+    assert_eq!(expected_boot_data, verified_boot_data);
+    Ok(())
+}
+
+#[test]
+fn payload_with_multiple_capabilities() -> Result<()> {
+    let public_key = load_trusted_public_key()?;
+    let verified_boot_data = verify_payload(
+        &fs::read(TEST_IMG_WITH_MULTIPLE_CAPABILITIES)?,
+        /* initrd= */ None,
+        &public_key,
+    )
+    .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
+
+    assert!(verified_boot_data.has_capability(Capability::RemoteAttest));
+    assert!(verified_boot_data.has_capability(Capability::SecretkeeperProtection));
+    Ok(())
+}
diff --git a/pvmfw/avb/tests/utils.rs b/pvmfw/avb/tests/utils.rs
index 86d2398..70eba5f 100644
--- a/pvmfw/avb/tests/utils.rs
+++ b/pvmfw/avb/tests/utils.rs
@@ -117,6 +117,7 @@
         initrd_digest,
         public_key: &public_key,
         capabilities: vec![],
+        rollback_index: if cfg!(llpvm_changes) { 1 } else { 0 },
     };
     assert_eq!(expected_boot_data, verified_boot_data);
 
diff --git a/pvmfw/src/crypto.rs b/pvmfw/src/crypto.rs
index 94714c0..2b3d921 100644
--- a/pvmfw/src/crypto.rs
+++ b/pvmfw/src/crypto.rs
@@ -31,10 +31,8 @@
 use bssl_ffi::EVP_AEAD_CTX_seal;
 use bssl_ffi::EVP_AEAD_max_overhead;
 use bssl_ffi::EVP_aead_aes_256_gcm_randnonce;
-use bssl_ffi::EVP_sha512;
 use bssl_ffi::EVP_AEAD;
 use bssl_ffi::EVP_AEAD_CTX;
-use bssl_ffi::HKDF;
 use vmbase::cstr;
 
 #[derive(Debug)]
@@ -267,36 +265,6 @@
     }
 }
 
-pub fn hkdf_sh512<const N: usize>(secret: &[u8], salt: &[u8], info: &[u8]) -> Result<[u8; N]> {
-    let mut key = [0; N];
-    // SAFETY: The function shouldn't access any Rust variable and the returned value is accepted
-    // as a potentially NULL pointer.
-    let digest = unsafe { EVP_sha512() };
-
-    assert!(!digest.is_null());
-    // SAFETY: Only reads from/writes to the provided slices and supports digest was checked not
-    // be NULL.
-    let result = unsafe {
-        HKDF(
-            key.as_mut_ptr(),
-            key.len(),
-            digest,
-            secret.as_ptr(),
-            secret.len(),
-            salt.as_ptr(),
-            salt.len(),
-            info.as_ptr(),
-            info.len(),
-        )
-    };
-
-    if result == 1 {
-        Ok(key)
-    } else {
-        Err(ErrorIterator {})
-    }
-}
-
 pub fn init() {
     // SAFETY: Configures the internal state of the library - may be called multiple times.
     unsafe { CRYPTO_library_init() }
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 9542429..cc31f34 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -45,6 +45,7 @@
     pub code_hash: Hash,
     pub auth_hash: Hash,
     pub mode: DiceMode,
+    pub security_version: u64,
 }
 
 impl PartialInputs {
@@ -52,8 +53,10 @@
         let code_hash = to_dice_hash(data)?;
         let auth_hash = hash(data.public_key)?;
         let mode = to_dice_mode(data.debug_level);
+        // We use rollback_index from vbmeta as the security_version field in dice certificate.
+        let security_version = data.rollback_index;
 
-        Ok(Self { code_hash, auth_hash, mode })
+        Ok(Self { code_hash, auth_hash, mode, security_version })
     }
 
     pub fn write_next_bcc(
@@ -63,8 +66,12 @@
         next_bcc: &mut [u8],
     ) -> diced_open_dice::Result<()> {
         let mut config_descriptor_buffer = [0; 128];
-        let config_values =
-            DiceConfigValues { component_name: Some(cstr!("vm_entry")), ..Default::default() };
+        let config_values = DiceConfigValues {
+            component_name: Some(cstr!("vm_entry")),
+            security_version: if cfg!(llpvm_changes) { Some(self.security_version) } else { None },
+            ..Default::default()
+        };
+
         let config_descriptor_size =
             bcc_format_config_descriptor(&config_values, &mut config_descriptor_buffer)?;
         let config = &config_descriptor_buffer[..config_descriptor_size];
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 61de423..1f87dcc 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -443,7 +443,7 @@
 fn read_serial_info_from(fdt: &Fdt) -> libfdt::Result<SerialInfo> {
     let mut addrs: ArrayVec<[u64; SerialInfo::MAX_SERIALS]> = Default::default();
     for node in fdt.compatible_nodes(cstr!("ns16550a"))?.take(SerialInfo::MAX_SERIALS) {
-        let reg = node.reg()?.ok_or(FdtError::NotFound)?.next().ok_or(FdtError::NotFound)?;
+        let reg = node.first_reg()?;
         addrs.push(reg.addr);
     }
     Ok(SerialInfo { addrs })
diff --git a/pvmfw/src/instance.rs b/pvmfw/src/instance.rs
index f2b34da..75bc3d3 100644
--- a/pvmfw/src/instance.rs
+++ b/pvmfw/src/instance.rs
@@ -15,12 +15,12 @@
 //! Support for reading and writing to the instance.img.
 
 use crate::crypto;
-use crate::crypto::hkdf_sh512;
 use crate::crypto::AeadCtx;
 use crate::dice::PartialInputs;
 use crate::gpt;
 use crate::gpt::Partition;
 use crate::gpt::Partitions;
+use bssl_avf::{self, hkdf, Digester};
 use core::fmt;
 use core::mem::size_of;
 use diced_open_dice::DiceMode;
@@ -63,6 +63,8 @@
     UnsupportedEntrySize(usize),
     /// Failed to create VirtIO Block device.
     VirtIOBlkCreationFailed(virtio_drivers::Error),
+    /// An error happened during the interaction with BoringSSL.
+    BoringSslFailed(bssl_avf::Error),
 }
 
 impl fmt::Display for Error {
@@ -95,10 +97,19 @@
             Self::VirtIOBlkCreationFailed(e) => {
                 write!(f, "Failed to create VirtIO Block device: {e}")
             }
+            Self::BoringSslFailed(e) => {
+                write!(f, "An error happened during the interaction with BoringSSL: {e}")
+            }
         }
     }
 }
 
+impl From<bssl_avf::Error> for Error {
+    fn from(e: bssl_avf::Error) -> Self {
+        Self::BoringSslFailed(e)
+    }
+}
+
 pub type Result<T> = core::result::Result<T, Error>;
 
 pub fn get_or_generate_instance_salt(
@@ -111,7 +122,7 @@
     let entry = locate_entry(&mut instance_img)?;
     trace!("Found pvmfw instance.img entry: {entry:?}");
 
-    let key = hkdf_sh512::<32>(secret, /*salt=*/ &[], b"vm-instance");
+    let key = hkdf::<32>(secret, /* salt= */ &[], b"vm-instance", Digester::sha512())?;
     let mut blk = [0; BLK_SIZE];
     match entry {
         PvmfwEntry::Existing { header_index, payload_size } => {
@@ -124,7 +135,6 @@
 
             let payload = &blk[..payload_size];
             let mut entry = [0; size_of::<EntryBody>()];
-            let key = key.map_err(Error::FailedOpen)?;
             let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedOpen)?;
             let decrypted = aead.open(&mut entry, payload).map_err(Error::FailedOpen)?;
 
@@ -143,7 +153,6 @@
             let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
             let body = EntryBody::new(dice_inputs, &salt);
 
-            let key = key.map_err(Error::FailedSeal)?;
             let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedSeal)?;
             // We currently only support single-blk entries.
             let plaintext = body.as_bytes();
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index d39d51c..b8cbf1b 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -112,10 +112,22 @@
         info!("Please disregard any previous libavb ERROR about initrd_normal.");
     }
 
-    if verified_boot_data.capabilities.contains(&Capability::RemoteAttest) {
+    if verified_boot_data.has_capability(Capability::RemoteAttest) {
         info!("Service VM capable of remote attestation detected");
     }
 
+    if verified_boot_data.has_capability(Capability::SecretkeeperProtection) {
+        info!("Guest OS is capable of Secretkeeper protection");
+        // For Secretkeeper based Antirollback protection, rollback_index of the image > 0
+        if verified_boot_data.rollback_index == 0 {
+            error!(
+                "Expected positive rollback_index, found {:?}",
+                verified_boot_data.rollback_index
+            );
+            return Err(RebootReason::InvalidPayload);
+        };
+    }
+
     let next_bcc = heap::aligned_boxed_slice(NEXT_BCC_SIZE, GUEST_PAGE_SIZE).ok_or_else(|| {
         error!("Failed to allocate the next-stage BCC");
         RebootReason::InternalError
diff --git a/rialto/Android.bp b/rialto/Android.bp
index d8e4536..bc08d8c 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -9,8 +9,13 @@
     defaults: ["vmbase_ffi_defaults"],
     rustlibs: [
         "libaarch64_paging",
+        "libbssl_avf_nostd",
+        "libbssl_ffi_nostd",
         "libciborium_io_nostd",
         "libciborium_nostd",
+        "libcoset_nostd",
+        "libdiced_open_dice_nostd",
+        "libdiced_sample_inputs_nostd",
         "libhyp",
         "libfdtpci",
         "liblibfdt",
@@ -19,6 +24,7 @@
         "libtinyvec_nostd",
         "libvirtio_drivers",
         "libvmbase",
+        "libzeroize_nostd",
     ],
 }
 
@@ -103,6 +109,7 @@
         "android.system.virtualizationservice-rust",
         "libandroid_logger",
         "libanyhow",
+        "libciborium",
         "liblibc",
         "liblog_rust",
         "libservice_vm_comm",
diff --git a/rialto/src/error.rs b/rialto/src/error.rs
index 23667ed..d2bdbbe 100644
--- a/rialto/src/error.rs
+++ b/rialto/src/error.rs
@@ -16,9 +16,11 @@
 
 use aarch64_paging::MapError;
 use core::{fmt, result};
+use diced_open_dice::DiceError;
 use fdtpci::PciError;
 use hyp::Error as HypervisorError;
 use libfdt::FdtError;
+use service_vm_comm::RequestProcessingError;
 use vmbase::{memory::MemoryTrackerError, virtio::pci};
 
 pub type Result<T> = result::Result<T, Error>;
@@ -50,6 +52,10 @@
     SerializationFailed(CiboriumSerError),
     /// Failed to deserialize.
     DeserializationFailed(CiboriumDeError),
+    /// Failed DICE operation.
+    DiceOperationFailed(DiceError),
+    /// Failed to process request.
+    RequestProcessingFailed(RequestProcessingError),
 }
 
 impl fmt::Display for Error {
@@ -72,6 +78,8 @@
             }
             Self::SerializationFailed(e) => write!(f, "Failed to serialize: {e}"),
             Self::DeserializationFailed(e) => write!(f, "Failed to deserialize: {e}"),
+            Self::DiceOperationFailed(e) => write!(f, "Failed DICE operation: {e}"),
+            Self::RequestProcessingFailed(e) => write!(f, "Failed to process request: {e}"),
         }
     }
 }
@@ -123,3 +131,15 @@
         Self::DeserializationFailed(e)
     }
 }
+
+impl From<DiceError> for Error {
+    fn from(e: DiceError) -> Self {
+        Self::DiceOperationFailed(e)
+    }
+}
+
+impl From<RequestProcessingError> for Error {
+    fn from(e: RequestProcessingError) -> Self {
+        Self::RequestProcessingFailed(e)
+    }
+}
diff --git a/rialto/src/fdt.rs b/rialto/src/fdt.rs
new file mode 100644
index 0000000..8bb40c3
--- /dev/null
+++ b/rialto/src/fdt.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.
+
+//! High-level FDT functions.
+
+use core::ops::Range;
+use libfdt::{Fdt, FdtError};
+use vmbase::cstr;
+
+/// Reads the DICE data range from the given `fdt`.
+pub fn read_dice_range_from(fdt: &Fdt) -> libfdt::Result<Range<usize>> {
+    let node = fdt.node(cstr!("/reserved-memory"))?.ok_or(FdtError::NotFound)?;
+    let node = node.next_compatible(cstr!("google,open-dice"))?.ok_or(FdtError::NotFound)?;
+    node.first_reg()?.try_into()
+}
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index 4e91574..43215a0 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -20,15 +20,20 @@
 mod communication;
 mod error;
 mod exceptions;
+mod fdt;
 mod requests;
 
 extern crate alloc;
 
 use crate::communication::VsockStream;
 use crate::error::{Error, Result};
+use crate::fdt::read_dice_range_from;
+use alloc::boxed::Box;
+use bssl_ffi::CRYPTO_library_init;
 use ciborium_io::Write;
 use core::num::NonZeroUsize;
 use core::slice;
+use diced_open_dice::{bcc_handover_parse, DiceArtifacts};
 use fdtpci::PciInfo;
 use hyp::{get_mem_sharer, get_mmio_guard};
 use libfdt::FdtError;
@@ -131,6 +136,38 @@
         })?;
     }
 
+    // Initializes the crypto library before any crypto operations and after the heap is
+    // initialized.
+    // SAFETY: It is safe to call this function multiple times and concurrently.
+    unsafe {
+        CRYPTO_library_init();
+    }
+    let bcc_handover: Box<dyn DiceArtifacts> = match vm_type() {
+        VmType::ProtectedVm => {
+            let dice_range = read_dice_range_from(fdt)?;
+            info!("DICE range: {dice_range:#x?}");
+            // SAFETY: This region was written by pvmfw in its writable_data region. The region
+            // has no overlap with the main memory region and is safe to be mapped as read-only
+            // data.
+            let res = unsafe {
+                MEMORY.lock().as_mut().unwrap().alloc_range_outside_main_memory(&dice_range)
+            };
+            res.map_err(|e| {
+                error!("Failed to use DICE range from DT: {dice_range:#x?}");
+                e
+            })?;
+            let dice_start = dice_range.start as *const u8;
+            // SAFETY: There's no memory overlap and the region is mapped as read-only data.
+            let bcc_handover = unsafe { slice::from_raw_parts(dice_start, dice_range.len()) };
+            Box::new(bcc_handover_parse(bcc_handover)?)
+        }
+        // Currently, a sample DICE data is used for non-protected VMs, as these VMs only run
+        // in tests at the moment.
+        // If we intend to run non-protected rialto in production, we should retrieve real
+        // DICE chain data instead.
+        VmType::NonProtectedVm => Box::new(diced_sample_inputs::make_sample_bcc_and_cdis()?),
+    };
+
     let pci_info = PciInfo::from_fdt(fdt)?;
     debug!("PCI: {pci_info:#x?}");
     let mut pci_root = pci::initialize(pci_info, MEMORY.lock().as_mut().unwrap())
@@ -141,7 +178,7 @@
 
     let mut vsock_stream = VsockStream::new(socket_device, host_addr())?;
     while let ServiceVmRequest::Process(req) = vsock_stream.read_request()? {
-        let response = requests::process_request(req)?;
+        let response = requests::process_request(req, bcc_handover.as_ref())?;
         vsock_stream.write_response(&response)?;
         vsock_stream.flush()?;
     }
diff --git a/rialto/src/requests/mod.rs b/rialto/src/requests.rs
similarity index 97%
rename from rialto/src/requests/mod.rs
rename to rialto/src/requests.rs
index 2ed568c..d9e6f37 100644
--- a/rialto/src/requests/mod.rs
+++ b/rialto/src/requests.rs
@@ -15,6 +15,7 @@
 //! This module contains functions for the request processing.
 
 mod api;
+mod pub_key;
 mod rkp;
 
 pub use api::process_request;
diff --git a/rialto/src/requests/api.rs b/rialto/src/requests/api.rs
index c4b2d8e..59a7aed 100644
--- a/rialto/src/requests/api.rs
+++ b/rialto/src/requests/api.rs
@@ -17,21 +17,20 @@
 use super::rkp;
 use crate::error::Result;
 use alloc::vec::Vec;
+use diced_open_dice::DiceArtifacts;
 use service_vm_comm::{Request, Response};
 
 /// Processes a request and returns the corresponding response.
 /// This function serves as the entry point for the request processing
 /// module.
-pub fn process_request(request: Request) -> Result<Response> {
+pub fn process_request(request: Request, dice_artifacts: &dyn DiceArtifacts) -> Result<Response> {
     let response = match request {
         Request::Reverse(v) => Response::Reverse(reverse(v)),
-        Request::GenerateEcdsaP256KeyPair => {
-            let res = rkp::generate_ecdsa_p256_key_pair()?;
-            Response::GenerateEcdsaP256KeyPair(res)
-        }
+        Request::GenerateEcdsaP256KeyPair => rkp::generate_ecdsa_p256_key_pair(dice_artifacts)
+            .map_or_else(Response::Err, Response::GenerateEcdsaP256KeyPair),
         Request::GenerateCertificateRequest(p) => {
-            let res = rkp::generate_certificate_request(p)?;
-            Response::GenerateCertificateRequest(res)
+            rkp::generate_certificate_request(p, dice_artifacts)
+                .map_or_else(Response::Err, Response::GenerateCertificateRequest)
         }
     };
     Ok(response)
diff --git a/rialto/src/requests/pub_key.rs b/rialto/src/requests/pub_key.rs
new file mode 100644
index 0000000..110e3d2
--- /dev/null
+++ b/rialto/src/requests/pub_key.rs
@@ -0,0 +1,54 @@
+// 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 construction of the MACed public key.
+
+use alloc::vec::Vec;
+use bssl_avf::hmac_sha256;
+use core::result;
+use coset::{iana, CborSerializable, CoseKey, CoseMac0, CoseMac0Builder, HeaderBuilder};
+use service_vm_comm::RequestProcessingError;
+
+type Result<T> = result::Result<T, RequestProcessingError>;
+
+/// Verifies the MAC of the given public key.
+pub fn validate_public_key(maced_public_key: &[u8], hmac_key: &[u8]) -> Result<CoseKey> {
+    let cose_mac = CoseMac0::from_slice(maced_public_key)?;
+    cose_mac.verify_tag(&[], |tag, data| verify_tag(tag, data, hmac_key))?;
+    let payload = cose_mac.payload.ok_or(RequestProcessingError::KeyToSignHasEmptyPayload)?;
+    Ok(CoseKey::from_slice(&payload)?)
+}
+
+fn verify_tag(tag: &[u8], data: &[u8], hmac_key: &[u8]) -> Result<()> {
+    let computed_tag = hmac_sha256(hmac_key, data)?;
+    if tag == computed_tag {
+        Ok(())
+    } else {
+        Err(RequestProcessingError::InvalidMac)
+    }
+}
+
+/// Returns the MACed public key.
+pub fn build_maced_public_key(public_key: CoseKey, hmac_key: &[u8]) -> Result<Vec<u8>> {
+    const ALGO: iana::Algorithm = iana::Algorithm::HMAC_256_256;
+
+    let external_aad = &[];
+    let protected = HeaderBuilder::new().algorithm(ALGO).build();
+    let cose_mac = CoseMac0Builder::new()
+        .protected(protected)
+        .payload(public_key.to_vec()?)
+        .try_create_tag(external_aad, |data| hmac_sha256(hmac_key, data).map(|v| v.to_vec()))?
+        .build();
+    Ok(cose_mac.to_vec()?)
+}
diff --git a/rialto/src/requests/rkp.rs b/rialto/src/requests/rkp.rs
index 5977bfb..f96b85d 100644
--- a/rialto/src/requests/rkp.rs
+++ b/rialto/src/requests/rkp.rs
@@ -13,21 +13,153 @@
 // limitations under the License.
 
 //! This module contains functions related to the attestation of the
-//! service VM via the RKP (Remote Key Provisionning) server.
+//! service VM via the RKP (Remote Key Provisioning) server.
 
-use crate::error::Result;
+use super::pub_key::{build_maced_public_key, validate_public_key};
+use alloc::string::String;
+use alloc::vec;
 use alloc::vec::Vec;
-use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams};
+use bssl_avf::EcKey;
+use ciborium::{cbor, value::Value};
+use core::result;
+use coset::{iana, AsCborValue, CoseSign1, CoseSign1Builder, HeaderBuilder};
+use diced_open_dice::{kdf, keypair_from_seed, sign, DiceArtifacts, PrivateKey};
+use log::error;
+use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams, RequestProcessingError};
+use zeroize::Zeroizing;
 
-pub(super) fn generate_ecdsa_p256_key_pair() -> Result<EcdsaP256KeyPair> {
-    // TODO(b/299055662): Generate the key pair.
-    let key_pair = EcdsaP256KeyPair { maced_public_key: Vec::new(), key_blob: Vec::new() };
+type Result<T> = result::Result<T, RequestProcessingError>;
+
+/// The salt is generated randomly with:
+/// hexdump -vn32 -e'16/1 "0x%02X, " 1 "\n"' /dev/urandom
+const HMAC_KEY_SALT: [u8; 32] = [
+    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_LENGTH: usize = 32;
+
+pub(super) fn generate_ecdsa_p256_key_pair(
+    dice_artifacts: &dyn DiceArtifacts,
+) -> 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())?;
+
+    // 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() };
     Ok(key_pair)
 }
 
+const CSR_PAYLOAD_SCHEMA_V3: u8 = 3;
+const AUTH_REQ_SCHEMA_V1: u8 = 1;
+// TODO(b/300624493): Add a new certificate type for AVF CSR.
+const CERTIFICATE_TYPE: &str = "keymint";
+
+/// Builds the CSR described in:
+///
+/// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+/// generateCertificateRequestV2.cddl
 pub(super) fn generate_certificate_request(
-    _params: GenerateCertificateRequestParams,
+    params: GenerateCertificateRequestParams,
+    dice_artifacts: &dyn DiceArtifacts,
 ) -> Result<Vec<u8>> {
-    // TODO(b/299256925): Generate the certificate request
-    Ok(Vec::new())
+    let hmac_key = derive_hmac_key(dice_artifacts)?;
+    let mut public_keys: Vec<Value> = Vec::new();
+    for key_to_sign in params.keys_to_sign {
+        let public_key = validate_public_key(&key_to_sign, hmac_key.as_ref())?;
+        public_keys.push(public_key.to_cbor_value()?);
+    }
+    // Builds `CsrPayload`.
+    let csr_payload = cbor!([
+        Value::Integer(CSR_PAYLOAD_SCHEMA_V3.into()),
+        Value::Text(String::from(CERTIFICATE_TYPE)),
+        // TODO(b/299256925): Add device info in CBOR format here.
+        Value::Array(public_keys),
+    ])?;
+    let csr_payload = cbor_to_vec(&csr_payload)?;
+
+    // Builds `SignedData`.
+    let signed_data_payload =
+        cbor!([Value::Bytes(params.challenge.to_vec()), Value::Bytes(csr_payload)])?;
+    let signed_data = build_signed_data(&signed_data_payload, dice_artifacts)?.to_cbor_value()?;
+
+    // Builds `AuthenticatedRequest<CsrPayload>`.
+    // Currently `UdsCerts` is left empty because it is only needed for Samsung devices.
+    // Check http://b/301574013#comment3 for more information.
+    let uds_certs = Value::Map(Vec::new());
+    let dice_cert_chain = dice_artifacts
+        .bcc()
+        .map(read_to_value)
+        .ok_or(RequestProcessingError::MissingDiceChain)??;
+    let auth_req = cbor!([
+        Value::Integer(AUTH_REQ_SCHEMA_V1.into()),
+        uds_certs,
+        dice_cert_chain,
+        signed_data,
+    ])?;
+    cbor_to_vec(&auth_req)
+}
+
+fn derive_hmac_key(dice_artifacts: &dyn DiceArtifacts) -> Result<Zeroizing<[u8; HMAC_KEY_LENGTH]>> {
+    let mut key = Zeroizing::new([0u8; HMAC_KEY_LENGTH]);
+    kdf(dice_artifacts.cdi_seal(), &HMAC_KEY_SALT, HMAC_KEY_INFO, key.as_mut()).map_err(|e| {
+        error!("Failed to compute the HMAC key: {e}");
+        RequestProcessingError::InternalError
+    })?;
+    Ok(key)
+}
+
+/// Builds the `SignedData` for the given payload.
+fn build_signed_data(payload: &Value, dice_artifacts: &dyn DiceArtifacts) -> Result<CoseSign1> {
+    let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts).map_err(|e| {
+        error!("Failed to derive the CDI_Leaf_Priv: {e}");
+        RequestProcessingError::InternalError
+    })?;
+    let signing_algorithm = iana::Algorithm::EdDSA;
+    let protected = HeaderBuilder::new().algorithm(signing_algorithm).build();
+    let signed_data = CoseSign1Builder::new()
+        .protected(protected)
+        .payload(cbor_to_vec(payload)?)
+        .try_create_signature(&[], |message| sign_message(message, &cdi_leaf_priv))?
+        .build();
+    Ok(signed_data)
+}
+
+fn derive_cdi_leaf_priv(dice_artifacts: &dyn DiceArtifacts) -> diced_open_dice::Result<PrivateKey> {
+    let (_, private_key) = keypair_from_seed(dice_artifacts.cdi_attest())?;
+    Ok(private_key)
+}
+
+fn sign_message(message: &[u8], private_key: &PrivateKey) -> Result<Vec<u8>> {
+    Ok(sign(message, private_key.as_array())
+        .map_err(|e| {
+            error!("Failed to sign the CSR: {e}");
+            RequestProcessingError::InternalError
+        })?
+        .to_vec())
+}
+
+fn cbor_to_vec(v: &Value) -> Result<Vec<u8>> {
+    let mut data = Vec::new();
+    ciborium::into_writer(v, &mut data).map_err(coset::CoseError::from)?;
+    Ok(data)
+}
+
+/// Read a CBOR `Value` from a byte slice, failing if any extra data remains
+/// after the `Value` has been read.
+fn read_to_value(mut data: &[u8]) -> Result<Value> {
+    let value = ciborium::from_reader(&mut data).map_err(|e| {
+        error!("Failed to deserialize the data into CBOR value: {e}");
+        RequestProcessingError::CborValueError
+    })?;
+    if data.is_empty() {
+        Ok(value)
+    } else {
+        error!("CBOR input has extra data.");
+        Err(RequestProcessingError::CborValueError)
+    }
 }
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index b8ced95..6a6dcf4 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -22,12 +22,14 @@
     binder::{ParcelFileDescriptor, ProcessState},
 };
 use anyhow::{bail, Context, Result};
+use ciborium::value::Value;
 use log::info;
 use service_vm_comm::{
     EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, VmType,
 };
 use service_vm_manager::ServiceVm;
 use std::fs::File;
+use std::io;
 use std::panic;
 use std::path::PathBuf;
 use vmclient::VmInstance;
@@ -49,8 +51,8 @@
     let mut vm = start_service_vm(vm_type)?;
 
     check_processing_reverse_request(&mut vm)?;
-    check_processing_generating_key_pair_request(&mut vm)?;
-    check_processing_generating_certificate_request(&mut vm)?;
+    let maced_public_key = check_processing_generating_key_pair_request(&mut vm)?;
+    check_processing_generating_certificate_request(&mut vm, maced_public_key)?;
     Ok(())
 }
 
@@ -68,31 +70,57 @@
     Ok(())
 }
 
-fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<()> {
+fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<Vec<u8>> {
     let request = Request::GenerateEcdsaP256KeyPair;
 
     let response = vm.process_request(request)?;
     info!("Received response: {response:?}.");
 
     match response {
-        Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { .. }) => Ok(()),
-        _ => bail!("Incorrect response type"),
+        Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { maced_public_key, .. }) => {
+            assert_array_has_nonzero(&maced_public_key[..]);
+            Ok(maced_public_key)
+        }
+        _ => bail!("Incorrect response type: {response:?}"),
     }
 }
 
-fn check_processing_generating_certificate_request(vm: &mut ServiceVm) -> Result<()> {
-    let params = GenerateCertificateRequestParams { keys_to_sign: vec![], challenge: vec![] };
+fn assert_array_has_nonzero(v: &[u8]) {
+    assert!(v.iter().any(|&x| x != 0))
+}
+
+fn check_processing_generating_certificate_request(
+    vm: &mut ServiceVm,
+    maced_public_key: Vec<u8>,
+) -> Result<()> {
+    let params = GenerateCertificateRequestParams {
+        keys_to_sign: vec![maced_public_key],
+        challenge: vec![],
+    };
     let request = Request::GenerateCertificateRequest(params);
 
     let response = vm.process_request(request)?;
     info!("Received response: {response:?}.");
 
     match response {
-        Response::GenerateCertificateRequest(_) => Ok(()),
-        _ => bail!("Incorrect response type"),
+        Response::GenerateCertificateRequest(csr) => check_csr(csr),
+        _ => bail!("Incorrect response type: {response:?}"),
     }
 }
 
+/// TODO(b/300625792): Check the CSR with libhwtrust once the CSR is complete.
+fn check_csr(csr: Vec<u8>) -> Result<()> {
+    let mut reader = io::Cursor::new(csr);
+    let csr: Value = ciborium::from_reader(&mut reader)?;
+    match csr {
+        Value::Array(arr) => {
+            assert_eq!(4, arr.len());
+        }
+        _ => bail!("Incorrect CSR format: {csr:?}"),
+    }
+    Ok(())
+}
+
 fn start_service_vm(vm_type: VmType) -> Result<ServiceVm> {
     android_logger::init_once(
         android_logger::Config::default().with_tag("rialto").with_min_level(log::Level::Debug),
diff --git a/libs/service_vm_comm/Android.bp b/service_vm/comm/Android.bp
similarity index 76%
rename from libs/service_vm_comm/Android.bp
rename to service_vm/comm/Android.bp
index cdb8fc3..3a18052 100644
--- a/libs/service_vm_comm/Android.bp
+++ b/service_vm/comm/Android.bp
@@ -21,6 +21,10 @@
         "libcore.rust_sysroot",
     ],
     rustlibs: [
+        "libbssl_avf_error_nostd",
+        "libciborium_nostd",
+        "libcoset_nostd",
+        "liblog_rust_nostd",
         "libserde_nostd",
     ],
 }
@@ -29,6 +33,10 @@
     name: "libservice_vm_comm",
     defaults: ["libservice_vm_comm_defaults"],
     rustlibs: [
+        "libbssl_avf_error",
+        "libciborium",
+        "libcoset",
+        "liblog_rust",
         "libserde",
     ],
     features: [
diff --git a/libs/service_vm_comm/src/lib.rs b/service_vm/comm/src/lib.rs
similarity index 93%
rename from libs/service_vm_comm/src/lib.rs
rename to service_vm/comm/src/lib.rs
index ca97ca1..d8f7bd7 100644
--- a/libs/service_vm_comm/src/lib.rs
+++ b/service_vm/comm/src/lib.rs
@@ -23,6 +23,7 @@
 mod vsock;
 
 pub use message::{
-    EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, ServiceVmRequest,
+    EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, RequestProcessingError, Response,
+    ServiceVmRequest,
 };
 pub use vsock::VmType;
diff --git a/service_vm/comm/src/message.rs b/service_vm/comm/src/message.rs
new file mode 100644
index 0000000..f8d7420
--- /dev/null
+++ b/service_vm/comm/src/message.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.
+
+//! This module contains the requests and responses definitions exchanged
+//! between the host and the service VM.
+
+use alloc::vec::Vec;
+use core::fmt;
+use log::error;
+use serde::{Deserialize, Serialize};
+
+type MacedPublicKey = Vec<u8>;
+
+/// The main request type to be sent to the service VM.
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum ServiceVmRequest {
+    /// A request to be processed by the service VM.
+    ///
+    /// Each request has a corresponding response item.
+    Process(Request),
+
+    /// Shuts down the service VM. No response is expected from it.
+    Shutdown,
+}
+
+/// Represents a process request to be sent to the service VM.
+///
+/// Each request has a corresponding response item.
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum Request {
+    /// Reverse the order of the bytes in the provided byte array.
+    /// Currently this is only used for testing.
+    Reverse(Vec<u8>),
+
+    /// Generates a new ECDSA P-256 key pair that can be attested by the remote
+    /// server.
+    GenerateEcdsaP256KeyPair,
+
+    /// Creates a certificate signing request to be sent to the
+    /// provisioning server.
+    GenerateCertificateRequest(GenerateCertificateRequestParams),
+}
+
+/// Represents a response to a request sent to the service VM.
+///
+/// Each response corresponds to a specific request.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Response {
+    /// Reverse the order of the bytes in the provided byte array.
+    Reverse(Vec<u8>),
+
+    /// Returns the new ECDSA P-256 key pair.
+    GenerateEcdsaP256KeyPair(EcdsaP256KeyPair),
+
+    /// Returns a CBOR Certificate Signing Request (Csr) serialized into a byte array.
+    GenerateCertificateRequest(Vec<u8>),
+
+    /// Encountered an error during the request processing.
+    Err(RequestProcessingError),
+}
+
+/// Errors related to request processing.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum RequestProcessingError {
+    /// An error happened during the interaction with BoringSSL.
+    BoringSslError(bssl_avf_error::Error),
+
+    /// An error happened during the interaction with coset.
+    CosetError,
+
+    /// An unexpected internal error occurred.
+    InternalError,
+
+    /// Any key to sign lacks a valid MAC. Maps to `STATUS_INVALID_MAC`.
+    InvalidMac,
+
+    /// No payload found in a key to sign.
+    KeyToSignHasEmptyPayload,
+
+    /// An error happened when serializing to/from a `Value`.
+    CborValueError,
+
+    /// The DICE chain of the service VM is missing.
+    MissingDiceChain,
+}
+
+impl fmt::Display for RequestProcessingError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::BoringSslError(e) => {
+                write!(f, "An error happened during the interaction with BoringSSL: {e}")
+            }
+            Self::CosetError => write!(f, "Encountered an error with coset"),
+            Self::InternalError => write!(f, "An unexpected internal error occurred"),
+            Self::InvalidMac => write!(f, "A key to sign lacks a valid MAC."),
+            Self::KeyToSignHasEmptyPayload => write!(f, "No payload found in a key to sign."),
+            Self::CborValueError => {
+                write!(f, "An error happened when serializing to/from a CBOR Value.")
+            }
+            Self::MissingDiceChain => write!(f, "The DICE chain of the service VM is missing"),
+        }
+    }
+}
+
+impl From<bssl_avf_error::Error> for RequestProcessingError {
+    fn from(e: bssl_avf_error::Error) -> Self {
+        Self::BoringSslError(e)
+    }
+}
+
+impl From<coset::CoseError> for RequestProcessingError {
+    fn from(e: coset::CoseError) -> Self {
+        error!("Coset error: {e}");
+        Self::CosetError
+    }
+}
+
+impl From<ciborium::value::Error> for RequestProcessingError {
+    fn from(e: ciborium::value::Error) -> Self {
+        error!("CborValueError: {e}");
+        Self::CborValueError
+    }
+}
+
+/// Represents the params passed to GenerateCertificateRequest
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct GenerateCertificateRequestParams {
+    /// Contains the set of keys to certify.
+    pub keys_to_sign: Vec<MacedPublicKey>,
+
+    /// challenge contains a byte strong from the provisioning server which will be
+    /// included in the signed data of the CSR structure.
+    /// The supported sizes is between 0 and 64 bytes, inclusive.
+    pub challenge: Vec<u8>,
+}
+
+/// Represents an ECDSA P-256 key pair.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct EcdsaP256KeyPair {
+    /// Contains a CBOR-encoded public key specified in:
+    ///
+    /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
+    pub maced_public_key: MacedPublicKey,
+
+    /// Contains a handle to the private key.
+    pub key_blob: Vec<u8>,
+}
diff --git a/libs/service_vm_comm/src/vsock.rs b/service_vm/comm/src/vsock.rs
similarity index 100%
rename from libs/service_vm_comm/src/vsock.rs
rename to service_vm/comm/src/vsock.rs
diff --git a/service_vm_manager/Android.bp b/service_vm/manager/Android.bp
similarity index 100%
rename from service_vm_manager/Android.bp
rename to service_vm/manager/Android.bp
diff --git a/service_vm_manager/src/lib.rs b/service_vm/manager/src/lib.rs
similarity index 100%
rename from service_vm_manager/src/lib.rs
rename to service_vm/manager/src/lib.rs
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index e2795ec..b7a34ae 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -555,4 +555,8 @@
     protected boolean isFeatureEnabled(String featureName) throws Exception {
         return getVirtualMachineManager().isFeatureEnabled(featureName);
     }
+
+    protected void assumeProtectedVM() {
+        assumeTrue("Skip on non-protected VM", mProtectedVm);
+    }
 }
diff --git a/tests/pvmfw/Android.bp b/tests/pvmfw/Android.bp
index 61667f3..474c62e 100644
--- a/tests/pvmfw/Android.bp
+++ b/tests/pvmfw/Android.bp
@@ -9,6 +9,20 @@
 }
 
 genrule {
+    name: "test_avf_debug_policy_with_ramdump",
+    defaults: ["test_avf_dts_to_dtb"],
+    srcs: ["assets/avf_debug_policy_with_ramdump.dts"],
+    out: ["avf_debug_policy_with_ramdump.dtbo"],
+}
+
+genrule {
+    name: "test_avf_debug_policy_without_ramdump",
+    defaults: ["test_avf_dts_to_dtb"],
+    srcs: ["assets/avf_debug_policy_without_ramdump.dts"],
+    out: ["avf_debug_policy_without_ramdump.dtbo"],
+}
+
+genrule {
     name: "test_avf_debug_policy_with_adb",
     defaults: ["test_avf_dts_to_dtb"],
     srcs: ["assets/avf_debug_policy_with_adb.dts"],
@@ -39,6 +53,8 @@
     data: [
         ":MicrodroidTestApp",
         ":pvmfw_test",
+        ":test_avf_debug_policy_with_ramdump",
+        ":test_avf_debug_policy_without_ramdump",
         ":test_avf_debug_policy_with_adb",
         ":test_avf_debug_policy_without_adb",
         "assets/bcc.dat",
diff --git a/tests/pvmfw/assets/avf_debug_policy_with_ramdump.dts b/tests/pvmfw/assets/avf_debug_policy_with_ramdump.dts
new file mode 100644
index 0000000..139d28e
--- /dev/null
+++ b/tests/pvmfw/assets/avf_debug_policy_with_ramdump.dts
@@ -0,0 +1,22 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    fragment@avf {
+        target-path = "/";
+
+        __overlay__ {
+            avf {
+                guest {
+                    common {
+                        ramdump = <1>;
+                    };
+                    microdroid {
+                        adb = <1>; // adb is required to check VM's bootargs.
+                    };
+                };
+            };
+        };
+    };
+};
+
diff --git a/tests/pvmfw/assets/avf_debug_policy_without_ramdump.dts b/tests/pvmfw/assets/avf_debug_policy_without_ramdump.dts
new file mode 100644
index 0000000..8e0e44c
--- /dev/null
+++ b/tests/pvmfw/assets/avf_debug_policy_without_ramdump.dts
@@ -0,0 +1,22 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    fragment@avf {
+        target-path = "/";
+
+        __overlay__ {
+            avf {
+                guest {
+                    common {
+                        ramdump = <0>;
+                    };
+                    microdroid {
+                        adb = <1>; // adb is required to check VM's bootargs.
+                    };
+                };
+            };
+        };
+    };
+};
+
diff --git a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
index 410e6e0..7d0faa4 100644
--- a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
+++ b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
@@ -192,6 +192,43 @@
         launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
     }
 
+    @Test
+    public void testRamdumpInDebugPolicy_withDebugLevelNone_hasRamdumpArgs() throws Exception {
+        prepareCustomDebugPolicy("avf_debug_policy_with_ramdump.dtbo");
+        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
+
+        assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
+        assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+                .contains("crashkernel=");
+        assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+                .isEqualTo(HEX_STRING_ONE);
+    }
+
+    @Test
+    public void testNoRamdumpInDebugPolicy_withDebugLevelNone_noRamdumpArgs() throws Exception {
+        prepareCustomDebugPolicy("avf_debug_policy_without_ramdump.dtbo");
+        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
+
+        assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH))
+                .doesNotContain("crashkernel=");
+        assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+                .doesNotContain("crashkernel=");
+        assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+                .isEqualTo(HEX_STRING_ZERO);
+    }
+
+    @Test
+    public void testNoRamdumpInDebugPolicy_withDebugLevelFull_hasRamdumpArgs() throws Exception {
+        prepareCustomDebugPolicy("avf_debug_policy_without_ramdump.dtbo");
+        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
+
+        assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
+        assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+                .contains("crashkernel=");
+        assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+                .isEqualTo(HEX_STRING_ZERO);
+    }
+
     private boolean isDebugPolicyEnabled(@NonNull String dtPropertyPath)
             throws DeviceNotAvailableException {
         CommandRunner runner = new CommandRunner(mAndroidDevice);
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 4b9f803..40c5cae 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -946,12 +946,18 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
     public void changingNonDebuggableVmDebuggableInvalidatesVmIdentity() throws Exception {
+        // Debuggability changes initrd which is verified by pvmfw.
+        // Therefore, skip this on non-protected VM.
+        assumeProtectedVM();
         changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL);
     }
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
     public void changingDebuggableVmNonDebuggableInvalidatesVmIdentity() throws Exception {
+        // Debuggability changes initrd which is verified by pvmfw.
+        // Therefore, skip this on non-protected VM.
+        assumeProtectedVM();
         changeDebugLevel(DEBUG_LEVEL_FULL, DEBUG_LEVEL_NONE);
     }
 
@@ -1544,7 +1550,7 @@
     @CddTest(requirements = {"9.17/C-1-1"})
     public void payloadIsNotRoot() throws Exception {
         assumeSupportedDevice();
-        assumeFeatureEnabled(VirtualMachineManager.FEATURE_PAYLOAD_NOT_ROOT);
+        assumeFeatureEnabled(VirtualMachineManager.FEATURE_MULTI_TENANT);
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index c660414..12d8724 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -82,6 +82,8 @@
         "libtempfile",
     ],
     data: [
+        ":test_avf_debug_policy_with_ramdump",
+        ":test_avf_debug_policy_without_ramdump",
         ":test_avf_debug_policy_with_adb",
         ":test_avf_debug_policy_without_adb",
     ],
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 790cdb5..684aa64 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -18,7 +18,7 @@
 use crate::atom::{
     write_vm_booted_stats, write_vm_creation_stats};
 use crate::composite::make_composite_image;
-use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
+use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VfioDevice, VmContext, VmInstance, VmState};
 use crate::debug_config::DebugConfig;
 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
 use crate::selinux::{getfilecon, SeContext};
@@ -34,7 +34,7 @@
     IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
     IVirtualMachineCallback::IVirtualMachineCallback,
     IVirtualizationService::IVirtualizationService,
-    IVirtualizationService::FEATURE_PAYLOAD_NON_ROOT,
+    IVirtualizationService::FEATURE_MULTI_TENANT,
     IVirtualizationService::FEATURE_VENDOR_MODULES,
     IVirtualizationService::FEATURE_DICE_CHANGES,
     MemoryTrimLevel::MemoryTrimLevel,
@@ -276,7 +276,7 @@
         // TODO(b/298012279): make this scalable.
         match feature {
             FEATURE_DICE_CHANGES => Ok(cfg!(dice_changes)),
-            FEATURE_PAYLOAD_NON_ROOT => Ok(cfg!(payload_not_root)),
+            FEATURE_MULTI_TENANT => Ok(cfg!(multi_tenant)),
             FEATURE_VENDOR_MODULES => Ok(cfg!(vendor_modules)),
             _ => {
                 warn!("unknown feature {feature}");
@@ -458,7 +458,7 @@
             }
         };
 
-        if !config.devices.is_empty() {
+        let vfio_devices = if !config.devices.is_empty() {
             let mut set = HashSet::new();
             for device in config.devices.iter() {
                 let path = canonicalize(device)
@@ -469,8 +469,17 @@
                         .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
                 }
             }
-            GLOBAL_SERVICE.bindDevicesToVfioDriver(&config.devices)?;
-        }
+            GLOBAL_SERVICE
+                .bindDevicesToVfioDriver(&config.devices)?
+                .into_iter()
+                .map(|x| VfioDevice {
+                    sysfs_path: PathBuf::from(&x.sysfsPath),
+                    dtbo_node: x.dtboNode,
+                })
+                .collect::<Vec<_>>()
+        } else {
+            vec![]
+        };
 
         // Actually start the VM.
         let crosvm_config = CrosvmConfig {
@@ -495,7 +504,7 @@
             platform_version: parse_platform_version_req(&config.platformVersion)?,
             detect_hangup: is_app_config,
             gdb_port,
-            vfio_devices: config.devices.iter().map(PathBuf::from).collect(),
+            vfio_devices,
         };
         let instance = Arc::new(
             VmInstance::new(
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 77dd76f..b053d99 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -115,7 +115,7 @@
     pub platform_version: VersionReq,
     pub detect_hangup: bool,
     pub gdb_port: Option<NonZeroU16>,
-    pub vfio_devices: Vec<PathBuf>,
+    pub vfio_devices: Vec<VfioDevice>,
 }
 
 /// A disk image to pass to crosvm for a VM.
@@ -125,6 +125,12 @@
     pub writable: bool,
 }
 
+#[derive(Clone, Debug)]
+pub struct VfioDevice {
+    pub sysfs_path: PathBuf,
+    pub dtbo_node: String,
+}
+
 /// The lifecycle state which the payload in the VM has reported itself to be in.
 ///
 /// Note that the order of enum variants is significant; only forward transitions are allowed by
@@ -144,7 +150,7 @@
     /// The VM has not yet tried to start.
     NotStarted {
         ///The configuration needed to start the VM, if it has not yet been started.
-        config: CrosvmConfig,
+        config: Box<CrosvmConfig>,
     },
     /// The VM has been started.
     Running {
@@ -171,7 +177,8 @@
 pub struct VmMetric {
     /// Recorded timestamp when the VM is started.
     pub start_timestamp: Option<SystemTime>,
-    /// Update most recent guest_time periodically from /proc/[crosvm pid]/stat while VM is running.
+    /// Update most recent guest_time periodically from /proc/[crosvm pid]/stat while VM is
+    /// running.
     pub cpu_guest_time: Option<i64>,
     /// Update maximum RSS values periodically from /proc/[crosvm pid]/smaps while VM is running.
     pub rss: Option<Rss>,
@@ -184,6 +191,7 @@
     fn start(&mut self, instance: Arc<VmInstance>) -> Result<(), Error> {
         let state = mem::replace(self, VmState::Failed);
         if let VmState::NotStarted { config } = state {
+            let config = *config;
             let detect_hangup = config.detect_hangup;
             let (failure_pipe_read, failure_pipe_write) = create_pipe()?;
             let vfio_devices = config.vfio_devices.clone();
@@ -303,7 +311,7 @@
             .flatten()
             .map_or_else(|| format!("{}", requester_uid), |u| u.name);
         let instance = VmInstance {
-            vm_state: Mutex::new(VmState::NotStarted { config }),
+            vm_state: Mutex::new(VmState::NotStarted { config: Box::new(config) }),
             vm_context,
             cid,
             crosvm_control_socket_path: temporary_directory.join("crosvm.sock"),
@@ -342,7 +350,7 @@
         &self,
         child: Arc<SharedChild>,
         mut failure_pipe_read: File,
-        vfio_devices: Vec<PathBuf>,
+        vfio_devices: Vec<VfioDevice>,
     ) {
         let result = child.wait();
         match &result {
@@ -641,10 +649,10 @@
 }
 
 fn death_reason(result: &Result<ExitStatus, io::Error>, mut failure_reason: &str) -> DeathReason {
-    if let Some(position) = failure_reason.find('|') {
+    if let Some((reason, info)) = failure_reason.split_once('|') {
         // Separator indicates extra context information is present after the failure name.
-        error!("Failure info: {}", &failure_reason[(position + 1)..]);
-        failure_reason = &failure_reason[..position];
+        error!("Failure info: {info}");
+        failure_reason = reason;
     }
     if let Ok(status) = result {
         match failure_reason {
@@ -694,9 +702,9 @@
 const SYSFS_PLATFORM_DEVICES_PATH: &str = "/sys/devices/platform/";
 const VFIO_PLATFORM_DRIVER_PATH: &str = "/sys/bus/platform/drivers/vfio-platform";
 
-fn vfio_argument_for_platform_device(path: &Path) -> Result<String, Error> {
+fn vfio_argument_for_platform_device(device: &VfioDevice) -> Result<String, Error> {
     // Check platform device exists
-    let path = path.canonicalize()?;
+    let path = device.sysfs_path.canonicalize()?;
     if !path.starts_with(SYSFS_PLATFORM_DEVICES_PATH) {
         bail!("{path:?} is not a platform device");
     }
@@ -708,7 +716,7 @@
     }
 
     if let Some(p) = path.to_str() {
-        Ok(format!("--vfio={p},iommu=viommu"))
+        Ok(format!("--vfio={p},iommu=viommu,dt-symbol={0}", device.dtbo_node))
     } else {
         bail!("invalid path {path:?}");
     }
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index 9b13475..5d22f59 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -239,6 +239,38 @@
     }
 
     #[test]
+    fn test_read_avf_debug_policy_with_ramdump() -> Result<()> {
+        let debug_config = DebugConfig::from_custom_debug_overlay_policy(
+            DebugLevel::FULL,
+            "avf_debug_policy_with_ramdump.dtbo".as_ref(),
+        )
+        .unwrap();
+
+        assert_eq!(DebugLevel::FULL, debug_config.debug_level);
+        assert!(!debug_config.debug_policy_log);
+        assert!(debug_config.debug_policy_ramdump);
+        assert!(debug_config.debug_policy_adb);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_read_avf_debug_policy_without_ramdump() -> Result<()> {
+        let debug_config = DebugConfig::from_custom_debug_overlay_policy(
+            DebugLevel::FULL,
+            "avf_debug_policy_without_ramdump.dtbo".as_ref(),
+        )
+        .unwrap();
+
+        assert_eq!(DebugLevel::FULL, debug_config.debug_level);
+        assert!(!debug_config.debug_policy_log);
+        assert!(!debug_config.debug_policy_ramdump);
+        assert!(debug_config.debug_policy_adb);
+
+        Ok(())
+    }
+
+    #[test]
     fn test_read_avf_debug_policy_with_adb() -> Result<()> {
         let debug_config = DebugConfig::from_custom_debug_overlay_policy(
             DebugLevel::FULL,
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 9255e1c..d6a1299 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -23,7 +23,7 @@
 
 interface IVirtualizationService {
     const String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
-    const String FEATURE_PAYLOAD_NON_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
+    const String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
     const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
 
     /**
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 62d66c0..f3a7617 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -23,6 +23,10 @@
 import android.system.virtualizationservice_internal.IGlobalVmContext;
 
 interface IVirtualizationServiceInternal {
+    parcelable BoundDevice {
+        String sysfsPath;
+        String dtboNode;
+    }
     /**
      * Removes the memlock rlimit of the calling process.
      *
@@ -68,6 +72,7 @@
      * Bind given devices to vfio driver.
      *
      * @param devices paths of sysfs nodes of devices to assign.
+     * @return a list of pairs (sysfs path, DTBO node label) for devices.
      */
-    void bindDevicesToVfioDriver(in String[] devices);
+    BoundDevice[] bindDevicesToVfioDriver(in String[] devices);
 }
diff --git a/virtualizationservice/assignable_devices.xsd b/virtualizationservice/assignable_devices.xsd
index 842542e..8f43019 100644
--- a/virtualizationservice/assignable_devices.xsd
+++ b/virtualizationservice/assignable_devices.xsd
@@ -25,6 +25,7 @@
     </xs:element>
     <xs:complexType name="device">
         <xs:attribute name="kind" type="xs:string"/>
+        <xs:attribute name="dtbo_node" type="xs:string"/>
         <xs:attribute name="sysfs_path" type="xs:string"/>
     </xs:complexType>
 </xs:schema>
diff --git a/virtualizationservice/schema/current.txt b/virtualizationservice/schema/current.txt
index ed0763a..ef99294 100644
--- a/virtualizationservice/schema/current.txt
+++ b/virtualizationservice/schema/current.txt
@@ -3,8 +3,10 @@
 
   public class Device {
     ctor public Device();
+    method public String getDtbo_node();
     method public String getKind();
     method public String getSysfs_path();
+    method public void setDtbo_node(String);
     method public void setKind(String);
     method public void setSysfs_path(String);
   }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 645a82b..ed5c513 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -28,6 +28,7 @@
     AtomVmCreationRequested::AtomVmCreationRequested,
     AtomVmExited::AtomVmExited,
     IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
+    IVirtualizationServiceInternal::BoundDevice::BoundDevice,
     IVirtualizationServiceInternal::IVirtualizationServiceInternal,
     IVfioHandler::{BpVfioHandler, IVfioHandler},
 };
@@ -179,44 +180,14 @@
     fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
         check_use_custom_virtual_machine()?;
 
-        let mut ret = Vec::new();
-        let xml_path = Path::new("/vendor/etc/avf/assignable_devices.xml");
-        if !xml_path.exists() {
-            return Ok(ret);
-        }
-
-        let xml = fs::read(xml_path)
-            .context("Failed to read assignable_devices.xml")
-            .with_log()
-            .or_service_specific_exception(-1)?;
-
-        let xml = String::from_utf8(xml)
-            .context("assignable_devices.xml is not a valid UTF-8 file")
-            .with_log()
-            .or_service_specific_exception(-1)?;
-
-        let devices: Devices = serde_xml_rs::from_str(&xml)
-            .context("can't parse assignable_devices.xml")
-            .with_log()
-            .or_service_specific_exception(-1)?;
-
-        let mut device_set = HashSet::new();
-
-        for device in devices.device.into_iter() {
-            if device_set.contains(&device.sysfs_path) {
-                warn!("duplicated assignable device {device:?}; ignoring...")
-            } else if Path::new(&device.sysfs_path).exists() {
-                device_set.insert(device.sysfs_path.clone());
-                ret.push(AssignableDevice { kind: device.kind, node: device.sysfs_path });
-            } else {
-                warn!("assignable device {device:?} doesn't exist; ignoring...");
-            }
-        }
-
-        Ok(ret)
+        Ok(get_assignable_devices()?
+            .device
+            .into_iter()
+            .map(|x| AssignableDevice { node: x.sysfs_path, kind: x.kind })
+            .collect::<Vec<_>>())
     }
 
-    fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<()> {
+    fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<Vec<BoundDevice>> {
         check_use_custom_virtual_machine()?;
 
         let vfio_service: Strong<dyn IVfioHandler> =
@@ -233,7 +204,17 @@
             vfio_service.writeVmDtbo(&ParcelFileDescriptor::new(dtbo))?;
         }
 
-        Ok(())
+        Ok(get_assignable_devices()?
+            .device
+            .into_iter()
+            .filter_map(|x| {
+                if devices.contains(&x.sysfs_path) {
+                    Some(BoundDevice { sysfsPath: x.sysfs_path, dtboNode: x.dtbo_node })
+                } else {
+                    None
+                }
+            })
+            .collect::<Vec<_>>())
     }
 }
 
@@ -241,14 +222,54 @@
 #[derive(Debug, Deserialize)]
 struct Device {
     kind: String,
+    dtbo_node: String,
     sysfs_path: String,
 }
 
-#[derive(Debug, Deserialize)]
+#[derive(Debug, Default, Deserialize)]
 struct Devices {
     device: Vec<Device>,
 }
 
+fn get_assignable_devices() -> binder::Result<Devices> {
+    let xml_path = Path::new("/vendor/etc/avf/assignable_devices.xml");
+    if !xml_path.exists() {
+        return Ok(Devices { ..Default::default() });
+    }
+
+    let xml = fs::read(xml_path)
+        .context("Failed to read assignable_devices.xml")
+        .with_log()
+        .or_service_specific_exception(-1)?;
+
+    let xml = String::from_utf8(xml)
+        .context("assignable_devices.xml is not a valid UTF-8 file")
+        .with_log()
+        .or_service_specific_exception(-1)?;
+
+    let mut devices: Devices = serde_xml_rs::from_str(&xml)
+        .context("can't parse assignable_devices.xml")
+        .with_log()
+        .or_service_specific_exception(-1)?;
+
+    let mut device_set = HashSet::new();
+    devices.device.retain(move |device| {
+        if device_set.contains(&device.sysfs_path) {
+            warn!("duplicated assignable device {device:?}; ignoring...");
+            return false;
+        }
+
+        if !Path::new(&device.sysfs_path).exists() {
+            warn!("assignable device {device:?} doesn't exist; ignoring...");
+            return false;
+        }
+
+        device_set.insert(device.sysfs_path.clone());
+        true
+    });
+    Ok(devices)
+}
+
 #[derive(Debug, Default)]
 struct GlobalVmInstance {
     /// The unique CID assigned to the VM for vsock communication.
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index 06f8ad4..a9a07a5 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -19,7 +19,7 @@
     DeviceInfo::DeviceInfo,
     IRemotelyProvisionedComponent::{
         BnRemotelyProvisionedComponent, IRemotelyProvisionedComponent, STATUS_FAILED,
-        STATUS_REMOVED,
+        STATUS_INVALID_MAC, STATUS_REMOVED,
     },
     MacedPublicKey::MacedPublicKey,
     ProtectedData::ProtectedData,
@@ -28,6 +28,7 @@
 use anyhow::Context;
 use avflog::LogResult;
 use binder::{BinderFeatures, Interface, IntoBinderResult, Result as BinderResult, Status, Strong};
+use service_vm_comm::{RequestProcessingError, Response};
 
 /// Constructs a binder object that implements `IRemotelyProvisionedComponent`.
 pub(crate) fn new_binder() -> Strong<dyn IRemotelyProvisionedComponent> {
@@ -65,12 +66,18 @@
             ))
             .with_log();
         }
-        let key_pair = rkpvm::generate_ecdsa_p256_key_pair()
+        let res = rkpvm::generate_ecdsa_p256_key_pair()
             .context("Failed to generate ECDSA P-256 key pair")
             .with_log()
             .or_service_specific_exception(STATUS_FAILED)?;
-        macedPublicKey.macedKey = key_pair.maced_public_key;
-        Ok(key_pair.key_blob)
+        match res {
+            Response::GenerateEcdsaP256KeyPair(key_pair) => {
+                macedPublicKey.macedKey = key_pair.maced_public_key;
+                Ok(key_pair.key_blob)
+            }
+            _ => Err(to_service_specific_error(res)),
+        }
+        .with_log()
     }
 
     fn generateCertificateRequest(
@@ -94,10 +101,42 @@
         keysToSign: &[MacedPublicKey],
         challenge: &[u8],
     ) -> BinderResult<Vec<u8>> {
-        // TODO(b/299259624): Validate the MAC of the keys to certify.
-        rkpvm::generate_certificate_request(keysToSign, challenge)
+        const MAX_CHALLENGE_SIZE: usize = 64;
+        if challenge.len() > MAX_CHALLENGE_SIZE {
+            let message = format!(
+                "Challenge is too big. Actual: {:?}. Maximum: {:?}.",
+                challenge.len(),
+                MAX_CHALLENGE_SIZE
+            );
+            return Err(Status::new_service_specific_error_str(STATUS_FAILED, Some(message)))
+                .with_log();
+        }
+        let res = rkpvm::generate_certificate_request(keysToSign, challenge)
             .context("Failed to generate certificate request")
             .with_log()
-            .or_service_specific_exception(STATUS_FAILED)
+            .or_service_specific_exception(STATUS_FAILED)?;
+        match res {
+            Response::GenerateCertificateRequest(res) => Ok(res),
+            _ => Err(to_service_specific_error(res)),
+        }
+        .with_log()
+    }
+}
+
+fn to_service_specific_error(response: Response) -> Status {
+    match response {
+        Response::Err(e) => match e {
+            RequestProcessingError::InvalidMac => {
+                Status::new_service_specific_error_str(STATUS_INVALID_MAC, Some(format!("{e}")))
+            }
+            _ => Status::new_service_specific_error_str(
+                STATUS_FAILED,
+                Some(format!("Failed to process request: {e}.")),
+            ),
+        },
+        other => Status::new_service_specific_error_str(
+            STATUS_FAILED,
+            Some(format!("Incorrect response type: {other:?}")),
+        ),
     }
 }
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 80953b5..d6e87eb 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -18,7 +18,7 @@
 
 use android_hardware_security_rkp::aidl::android::hardware::security::keymint::MacedPublicKey::MacedPublicKey;
 use anyhow::{bail, Context, Result};
-use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response};
+use service_vm_comm::{GenerateCertificateRequestParams, Request, Response};
 use service_vm_manager::ServiceVm;
 
 pub(crate) fn request_certificate(csr: &[u8]) -> Result<Vec<u8>> {
@@ -33,19 +33,16 @@
     }
 }
 
-pub(crate) fn generate_ecdsa_p256_key_pair() -> Result<EcdsaP256KeyPair> {
+pub(crate) fn generate_ecdsa_p256_key_pair() -> Result<Response> {
     let mut vm = ServiceVm::start()?;
     let request = Request::GenerateEcdsaP256KeyPair;
-    match vm.process_request(request).context("Failed to process request")? {
-        Response::GenerateEcdsaP256KeyPair(key_pair) => Ok(key_pair),
-        _ => bail!("Incorrect response type"),
-    }
+    vm.process_request(request).context("Failed to process request")
 }
 
 pub(crate) fn generate_certificate_request(
     keys_to_sign: &[MacedPublicKey],
     challenge: &[u8],
-) -> Result<Vec<u8>> {
+) -> Result<Response> {
     let params = GenerateCertificateRequestParams {
         keys_to_sign: keys_to_sign.iter().map(|v| v.macedKey.to_vec()).collect(),
         challenge: challenge.to_vec(),
@@ -53,8 +50,5 @@
     let request = Request::GenerateCertificateRequest(params);
 
     let mut vm = ServiceVm::start()?;
-    match vm.process_request(request).context("Failed to process request")? {
-        Response::GenerateCertificateRequest(csr) => Ok(csr),
-        _ => bail!("Incorrect response type"),
-    }
+    vm.process_request(request).context("Failed to process request")
 }
diff --git a/vmbase/src/layout/mod.rs b/vmbase/src/layout.rs
similarity index 100%
rename from vmbase/src/layout/mod.rs
rename to vmbase/src/layout.rs
diff --git a/vmbase/src/memory/mod.rs b/vmbase/src/memory.rs
similarity index 100%
rename from vmbase/src/memory/mod.rs
rename to vmbase/src/memory.rs
diff --git a/vmbase/src/memory/shared.rs b/vmbase/src/memory/shared.rs
index dfa29e4..6c8a844 100644
--- a/vmbase/src/memory/shared.rs
+++ b/vmbase/src/memory/shared.rs
@@ -135,7 +135,26 @@
     /// Allocate the address range for a const slice; returns None if failed.
     pub fn alloc_range(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
         let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly };
-        self.check(&region)?;
+        self.check_allocatable(&region)?;
+        self.page_table.map_rodata(&get_va_range(range)).map_err(|e| {
+            error!("Error during range allocation: {e}");
+            MemoryTrackerError::FailedToMap
+        })?;
+        self.add(region)
+    }
+
+    /// Allocates the address range for a const slice.
+    ///
+    /// # Safety
+    ///
+    /// Callers of this method need to ensure that the `range` is valid for mapping as read-only
+    /// data.
+    pub unsafe fn alloc_range_outside_main_memory(
+        &mut self,
+        range: &MemoryRange,
+    ) -> Result<MemoryRange> {
+        let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly };
+        self.check_no_overlap(&region)?;
         self.page_table.map_rodata(&get_va_range(range)).map_err(|e| {
             error!("Error during range allocation: {e}");
             MemoryTrackerError::FailedToMap
@@ -146,7 +165,7 @@
     /// Allocate the address range for a mutable slice; returns None if failed.
     pub fn alloc_range_mut(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
         let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadWrite };
-        self.check(&region)?;
+        self.check_allocatable(&region)?;
         self.page_table.map_data_dbm(&get_va_range(range)).map_err(|e| {
             error!("Error during mutable range allocation: {e}");
             MemoryTrackerError::FailedToMap
@@ -196,13 +215,20 @@
         Ok(())
     }
 
-    /// Checks that the given region is within the range of the `MemoryTracker` and doesn't overlap
-    /// with any other previously allocated regions, and that the regions ArrayVec has capacity to
-    /// add it.
-    fn check(&self, region: &MemoryRegion) -> Result<()> {
+    /// Checks that the memory region meets the following criteria:
+    /// - It is within the range of the `MemoryTracker`.
+    /// - It does not overlap with any previously allocated regions.
+    /// - The `regions` ArrayVec has sufficient capacity to add it.
+    fn check_allocatable(&self, region: &MemoryRegion) -> Result<()> {
         if !region.range.is_within(&self.total) {
             return Err(MemoryTrackerError::OutOfRange);
         }
+        self.check_no_overlap(region)
+    }
+
+    /// Checks that the given region doesn't overlap with any other previously allocated regions,
+    /// and that the regions ArrayVec has capacity to add it.
+    fn check_no_overlap(&self, region: &MemoryRegion) -> Result<()> {
         if self.regions.iter().any(|r| region.range.overlaps(&r.range)) {
             return Err(MemoryTrackerError::Overlaps);
         }
diff --git a/vmbase/src/virtio/mod.rs b/vmbase/src/virtio.rs
similarity index 100%
rename from vmbase/src/virtio/mod.rs
rename to vmbase/src/virtio.rs