Merge "[test] Add e2e instrumentation test for VM attestation" into main am: c9029437cb

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Virtualization/+/2914123

Change-Id: Ibfad3dc465436c4d5e6f2fc92d482fc28fc8490d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4da96c8..f146b4e 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -12,6 +12,9 @@
       "name": "MicrodroidTestApp"
     },
     {
+      "name": "VmAttestationTestApp"
+    },
+    {
       "name": "CustomPvmfwHostTestCases"
     },
     {
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 5aff93f..3ea50e2 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -2,6 +2,7 @@
 package android.system.virtualmachine {
 
   public class VirtualMachine implements java.lang.AutoCloseable {
+    method @FlaggedApi("RELEASE_AVF_ENABLE_REMOTE_ATTESTATION") @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public void enableTestAttestation() throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull @WorkerThread public java.io.OutputStream getConsoleInput() throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull public java.io.File getRootDir();
   }
@@ -26,6 +27,7 @@
     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_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+    field public static final String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
     field public static final String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
   }
 
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 5025e88..b4ba00b 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -42,6 +42,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.FlaggedApi;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -1202,6 +1203,28 @@
     }
 
     /**
+     * Enables the VM to request attestation in testing mode.
+     *
+     * <p>This function provisions a key pair for the VM attestation testing, a fake certificate
+     * will be associated to the fake key pair when the VM requests attestation in testing mode.
+     *
+     * <p>The provisioned key pair can only be used in subsequent calls to {@link
+     * AVmPayload_requestAttestationForTesting} within a running VM.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
+    @FlaggedApi("RELEASE_AVF_ENABLE_REMOTE_ATTESTATION")
+    public void enableTestAttestation() throws VirtualMachineException {
+        try {
+            mVirtualizationService.getBinder().enableTestAttestation();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM
      * needs to be stopped to avoid inconsistency in its state representation.
      *
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 1607c0a..1a4b53a 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -116,7 +116,12 @@
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(
             prefix = "FEATURE_",
-            value = {FEATURE_DICE_CHANGES, FEATURE_MULTI_TENANT, FEATURE_VENDOR_MODULES})
+            value = {
+                FEATURE_DICE_CHANGES,
+                FEATURE_MULTI_TENANT,
+                FEATURE_REMOTE_ATTESTATION,
+                FEATURE_VENDOR_MODULES
+            })
     public @interface Features {}
 
     /**
@@ -136,6 +141,15 @@
     public static final String FEATURE_MULTI_TENANT = IVirtualizationService.FEATURE_MULTI_TENANT;
 
     /**
+     * Feature to allow remote attestation in Microdroid.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String FEATURE_REMOTE_ATTESTATION =
+            IVirtualizationService.FEATURE_REMOTE_ATTESTATION;
+
+    /**
      * Feature to allow vendor modules in Microdroid.
      *
      * @hide
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 4813b35..b7a539b 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -107,9 +107,13 @@
      * serving as proof of the freshness of the result.
      *
      * @param challenge the maximum supported challenge size is 64 bytes.
+     * @param testMode whether the attestation is only for testing purposes. If testMode is true,
+     * caller must invoke {@link VirtualMachineManager#enableTestAttestation} prior to
+     * calling this method to provision a key pair to sign the attested result, and the returned
+     * certificate chain will not be RKP server rooted.
      *
      * @return An {@link AttestationResult} parcelable containing an attested key pair and its
      *         certification chain.
      */
-    AttestationResult requestAttestation(in byte[] challenge);
+    AttestationResult requestAttestation(in byte[] challenge, in boolean testMode);
 }
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 20a1b89..959197a 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -68,7 +68,11 @@
         Ok(self.secret.dice_artifacts().cdi_attest().to_vec())
     }
 
-    fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<AttestationResult> {
+    fn requestAttestation(
+        &self,
+        challenge: &[u8],
+        test_mode: bool,
+    ) -> binder::Result<AttestationResult> {
         self.check_restricted_apis_allowed()?;
         let ClientVmAttestationData { private_key, csr } =
             generate_attestation_key_and_csr(challenge, self.secret.dice_artifacts())
@@ -88,7 +92,7 @@
                 )
             })
             .with_log()?;
-        let cert_chain = self.virtual_machine_service.requestAttestation(&csr)?;
+        let cert_chain = self.virtual_machine_service.requestAttestation(&csr, test_mode)?;
         Ok(AttestationResult {
             privateKey: private_key.as_slice().to_vec(),
             certificateChain: cert_chain,
diff --git a/service_vm/test_apk/Android.bp b/service_vm/test_apk/Android.bp
new file mode 100644
index 0000000..8f5fb41
--- /dev/null
+++ b/service_vm/test_apk/Android.bp
@@ -0,0 +1,44 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "VmAttestationTestApp",
+    test_suites: [
+        "general-tests",
+    ],
+    srcs: ["src/java/**/*.java"],
+    static_libs: [
+        "MicrodroidDeviceTestHelper",
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+        "com.android.virt.vm_attestation.testservice-java",
+        "truth",
+    ],
+    jni_libs: ["libvm_attestation_test_payload"],
+    jni_uses_platform_apis: true,
+    use_embedded_native_libs: true,
+    sdk_version: "test_current",
+    compile_multilib: "first",
+}
+
+rust_defaults {
+    name: "vm_attestation_test_payload_defaults",
+    crate_name: "vm_attestation_test_payload",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/native/main.rs"],
+    prefer_rlib: true,
+    rustlibs: [
+        "com.android.virt.vm_attestation.testservice-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "libavflog",
+        "liblog_rust",
+        "libvm_payload_bindgen",
+    ],
+}
+
+rust_ffi {
+    name: "libvm_attestation_test_payload",
+    defaults: ["vm_attestation_test_payload_defaults"],
+}
diff --git a/service_vm/test_apk/AndroidManifest.xml b/service_vm/test_apk/AndroidManifest.xml
new file mode 100644
index 0000000..b998b7f
--- /dev/null
+++ b/service_vm/test_apk/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.virt.vm_attestation.testapp">
+     <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+     <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+
+     <application />
+     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.virt.vm_attestation.testapp"
+        android:label="Microdroid VM attestation" />
+</manifest>
diff --git a/service_vm/test_apk/AndroidTest.xml b/service_vm/test_apk/AndroidTest.xml
new file mode 100644
index 0000000..18b4e46
--- /dev/null
+++ b/service_vm/test_apk/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Runs Microdroid VM remote attestation tests.">
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="test-file-name" value="VmAttestationTestApp.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/microdroid" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts/microdroid" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.virt.vm_attestation.testapp" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="shell-timeout" value="300000" />
+        <option name="test-timeout" value="300000" />
+    </test>
+</configuration>
diff --git a/service_vm/test_apk/aidl/Android.bp b/service_vm/test_apk/aidl/Android.bp
new file mode 100644
index 0000000..3ecce46
--- /dev/null
+++ b/service_vm/test_apk/aidl/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_team: "trendy_team_virtualization",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "com.android.virt.vm_attestation.testservice",
+    srcs: ["com/android/virt/vm_attestation/testservice/**/*.aidl"],
+    unstable: true,
+    backend: {
+        java: {
+            gen_rpc: true,
+        },
+        rust: {
+            enabled: true,
+        },
+    },
+}
diff --git a/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl b/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
new file mode 100644
index 0000000..94a7b8d
--- /dev/null
+++ b/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 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.
+ */
+
+package com.android.virt.vm_attestation.testservice;
+
+/** {@hide} */
+interface IAttestationService {
+    const int PORT = 5679;
+
+    /**
+     * Requests attestation for testing.
+     *
+     * A fake key pair should be provisioned with the call to
+     * {@link VirtualMachine#enableTestAttestation()} before calling this method.
+     *
+     * The attestation result will be cached in the VM and can be validated with
+     * {@link #validateAttestationResult}.
+     */
+    void requestAttestationForTesting();
+
+    /**
+     * Validates the attestation result returned by the last call to
+     * {@link #requestAttestationForTesting}.
+     */
+    void validateAttestationResult();
+}
diff --git a/service_vm/test_apk/assets/config.json b/service_vm/test_apk/assets/config.json
new file mode 100644
index 0000000..caae3ce
--- /dev/null
+++ b/service_vm/test_apk/assets/config.json
@@ -0,0 +1,10 @@
+{
+    "os": {
+      "name": "microdroid"
+    },
+    "task": {
+      "type": "microdroid_launcher",
+      "command": "libvm_attestation_test_payload.so"
+    },
+    "export_tombstones": true
+  }
\ No newline at end of file
diff --git a/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java b/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
new file mode 100644
index 0000000..80e88e9
--- /dev/null
+++ b/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package com.android.virt.vm_attestation.testapp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineManager;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+import com.android.virt.vm_attestation.testservice.IAttestationService;
+
+@RunWith(BlockJUnit4ClassRunner.class)
+public class VmAttestationTests extends MicrodroidDeviceTestBase {
+    private static final String TAG = "VmAttestationTest";
+    private static final String DEFAULT_CONFIG = "assets/config.json";
+
+    @Before
+    public void setup() throws IOException {
+        grantPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
+        grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+        // TODO(b/318333789): Test using GKI as guest kernel.
+        prepareTestSetup(true /* protectedVm */, null /* use microdroid kernel */);
+        setMaxPerformanceTaskProfile();
+    }
+
+    @Test
+    public void requestingAttestationSucceeds() throws Exception {
+        assume().withMessage("Remote attestation is not supported on CF.")
+                .that(isCuttlefish())
+                .isFalse();
+        assumeFeatureEnabled(VirtualMachineManager.FEATURE_REMOTE_ATTESTATION);
+
+        VirtualMachineConfig.Builder builder =
+                newVmConfigBuilderWithPayloadConfig(DEFAULT_CONFIG)
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .setVmOutputCaptured(true);
+        VirtualMachineConfig config = builder.build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("attestation_client", config);
+
+        vm.enableTestAttestation();
+        CompletableFuture<Exception> exception = new CompletableFuture<>();
+        CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
+        VmEventListener listener =
+                new VmEventListener() {
+                    @Override
+                    public void onPayloadReady(VirtualMachine vm) {
+                        payloadReady.complete(true);
+                        try {
+                            IAttestationService service =
+                                    IAttestationService.Stub.asInterface(
+                                            vm.connectToVsockServer(IAttestationService.PORT));
+                            android.os.Trace.beginSection("runningVmRequestsAttestation");
+                            service.requestAttestationForTesting();
+                            android.os.Trace.endSection();
+                            service.validateAttestationResult();
+                        } catch (Exception e) {
+                            exception.complete(e);
+                        } finally {
+                            forceStop(vm);
+                        }
+                    }
+                };
+
+        listener.runToFinish(TAG, vm);
+        assertThat(payloadReady.getNow(false)).isTrue();
+        assertThat(exception.getNow(null)).isNull();
+    }
+}
diff --git a/service_vm/test_apk/src/native/main.rs b/service_vm/test_apk/src/native/main.rs
new file mode 100644
index 0000000..26b8062
--- /dev/null
+++ b/service_vm/test_apk/src/native/main.rs
@@ -0,0 +1,271 @@
+// Copyright 2024, 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.
+
+//! Main executable of VM attestation for end-to-end testing.
+
+use anyhow::{anyhow, ensure, Result};
+use avflog::LogResult;
+use com_android_virt_vm_attestation_testservice::{
+    aidl::com::android::virt::vm_attestation::testservice::IAttestationService::{
+        BnAttestationService, IAttestationService, PORT,
+    },
+    binder::{self, unstable_api::AsNative, BinderFeatures, Interface, IntoBinderResult, Strong},
+};
+use log::{error, info};
+use std::{
+    ffi::{c_void, CStr},
+    panic,
+    ptr::{self, NonNull},
+    result,
+    sync::{Arc, Mutex},
+};
+use vm_payload_bindgen::{
+    attestation_status_t, AIBinder, AVmAttestationResult, AVmAttestationResult_free,
+    AVmAttestationResult_getCertificateAt, AVmAttestationResult_getCertificateCount,
+    AVmAttestationResult_getPrivateKey, AVmAttestationResult_resultToString,
+    AVmAttestationResult_sign, AVmPayload_notifyPayloadReady,
+    AVmPayload_requestAttestationForTesting, AVmPayload_runVsockRpcServer,
+};
+
+/// Entry point of the Service VM client.
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn AVmPayload_main() {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("service_vm_client")
+            .with_min_level(log::Level::Debug),
+    );
+    // Redirect panic messages to logcat.
+    panic::set_hook(Box::new(|panic_info| {
+        error!("{}", panic_info);
+    }));
+    if let Err(e) = try_main() {
+        error!("failed with {:?}", e);
+        std::process::exit(1);
+    }
+}
+
+fn try_main() -> Result<()> {
+    info!("Welcome to Service VM Client!");
+
+    let mut service = AttestationService::new_binder().as_binder();
+    let service = service.as_native_mut() as *mut AIBinder;
+    let param = ptr::null_mut();
+    // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
+    // is the same type as `sys::AIBinder`. It is safe for `on_ready` to be invoked at any time,
+    // with any parameter.
+    unsafe { AVmPayload_runVsockRpcServer(service, PORT.try_into()?, Some(on_ready), param) };
+}
+
+extern "C" fn on_ready(_param: *mut c_void) {
+    // SAFETY: It is safe to call `AVmPayload_notifyPayloadReady` at any time.
+    unsafe { AVmPayload_notifyPayloadReady() };
+}
+
+struct AttestationService {
+    res: Arc<Mutex<Option<AttestationResult>>>,
+}
+
+impl Interface for AttestationService {}
+
+impl AttestationService {
+    fn new_binder() -> Strong<dyn IAttestationService> {
+        let res = Arc::new(Mutex::new(None));
+        BnAttestationService::new_binder(AttestationService { res }, BinderFeatures::default())
+    }
+}
+
+impl IAttestationService for AttestationService {
+    fn requestAttestationForTesting(&self) -> binder::Result<()> {
+        // The data below is only a placeholder generated randomly with urandom
+        let challenge = &[
+            0x6c, 0xad, 0x52, 0x50, 0x15, 0xe7, 0xf4, 0x1d, 0xa5, 0x60, 0x7e, 0xd2, 0x7d, 0xf1,
+            0x51, 0x67, 0xc3, 0x3e, 0x73, 0x9b, 0x30, 0xbd, 0x04, 0x20, 0x2e, 0xde, 0x3b, 0x1d,
+            0xc8, 0x07, 0x11, 0x7b,
+        ];
+        let res = AttestationResult::request_attestation(challenge)
+            .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))
+            .with_log()
+            .or_service_specific_exception(-1)?;
+        *self.res.lock().unwrap() = Some(res);
+        Ok(())
+    }
+
+    fn validateAttestationResult(&self) -> binder::Result<()> {
+        // TODO(b/191073073): Returns the attestation result to the host for validation.
+        self.res.lock().unwrap().as_ref().unwrap().log().or_service_specific_exception(-1)
+    }
+}
+
+#[derive(Debug)]
+struct AttestationResult(NonNull<AVmAttestationResult>);
+
+// Safety: `AttestationResult` is not `Send` because it contains a raw pointer to a C struct.
+unsafe impl Send for AttestationResult {}
+
+impl AttestationResult {
+    fn request_attestation(challenge: &[u8]) -> result::Result<Self, attestation_status_t> {
+        let mut res: *mut AVmAttestationResult = ptr::null_mut();
+        // SAFETY: It is safe as we only read the challenge within its bounds and the
+        // function does not retain any reference to it.
+        let status = unsafe {
+            AVmPayload_requestAttestationForTesting(
+                challenge.as_ptr() as *const c_void,
+                challenge.len(),
+                &mut res,
+            )
+        };
+        if status == attestation_status_t::ATTESTATION_OK {
+            info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
+            let res = NonNull::new(res).expect("The attestation result is null");
+            Ok(Self(res))
+        } else {
+            Err(status)
+        }
+    }
+
+    fn certificate_chain(&self) -> Result<Vec<Box<[u8]>>> {
+        let num_certs = get_certificate_count(self.as_ref());
+        let mut certs = Vec::with_capacity(num_certs);
+        for i in 0..num_certs {
+            certs.push(get_certificate_at(self.as_ref(), i)?);
+        }
+        Ok(certs)
+    }
+
+    fn private_key(&self) -> Result<Box<[u8]>> {
+        get_private_key(self.as_ref())
+    }
+
+    fn sign(&self, message: &[u8]) -> Result<Box<[u8]>> {
+        sign_with_attested_key(self.as_ref(), message)
+    }
+
+    fn log(&self) -> Result<()> {
+        let cert_chain = self.certificate_chain()?;
+        info!("Attestation result certificateChain = {:?}", cert_chain);
+
+        let private_key = self.private_key()?;
+        info!("Attestation result privateKey = {:?}", private_key);
+
+        let message = b"Hello from Service VM client";
+        info!("Signing message: {:?}", message);
+        let signature = self.sign(message)?;
+        info!("Signature: {:?}", signature);
+        Ok(())
+    }
+}
+
+impl AsRef<AVmAttestationResult> for AttestationResult {
+    fn as_ref(&self) -> &AVmAttestationResult {
+        // SAFETY: This field is private, and only populated with a successful call to
+        // `AVmPayload_requestAttestation`.
+        unsafe { self.0.as_ref() }
+    }
+}
+
+impl Drop for AttestationResult {
+    fn drop(&mut self) {
+        // SAFETY: This field is private, and only populated with a successful call to
+        // `AVmPayload_requestAttestation`, and not freed elsewhere.
+        unsafe { AVmAttestationResult_free(self.0.as_ptr()) };
+    }
+}
+
+fn get_certificate_count(res: &AVmAttestationResult) -> usize {
+    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+    // before getting freed.
+    unsafe { AVmAttestationResult_getCertificateCount(res) }
+}
+
+fn get_certificate_at(res: &AVmAttestationResult, index: usize) -> Result<Box<[u8]>> {
+    let size =
+        // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+        // before getting freed.
+        unsafe { AVmAttestationResult_getCertificateAt(res, index, ptr::null_mut(), 0) };
+    let mut cert = vec![0u8; size];
+    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+    // before getting freed. This function only writes within the bounds of `cert`.
+    // And `cert` cannot overlap `res` because we just allocated it.
+    let size = unsafe {
+        AVmAttestationResult_getCertificateAt(
+            res,
+            index,
+            cert.as_mut_ptr() as *mut c_void,
+            cert.len(),
+        )
+    };
+    ensure!(size == cert.len());
+    Ok(cert.into_boxed_slice())
+}
+
+fn get_private_key(res: &AVmAttestationResult) -> Result<Box<[u8]>> {
+    let size =
+        // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+        // before getting freed.
+        unsafe { AVmAttestationResult_getPrivateKey(res, ptr::null_mut(), 0) };
+    let mut private_key = vec![0u8; size];
+    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+    // before getting freed. This function only writes within the bounds of `private_key`.
+    // And `private_key` cannot overlap `res` because we just allocated it.
+    let size = unsafe {
+        AVmAttestationResult_getPrivateKey(
+            res,
+            private_key.as_mut_ptr() as *mut c_void,
+            private_key.len(),
+        )
+    };
+    ensure!(size == private_key.len());
+    Ok(private_key.into_boxed_slice())
+}
+
+fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Box<[u8]>> {
+    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+    // before getting freed.
+    let size = unsafe {
+        AVmAttestationResult_sign(
+            res,
+            message.as_ptr() as *const c_void,
+            message.len(),
+            ptr::null_mut(),
+            0,
+        )
+    };
+    let mut signature = vec![0u8; size];
+    // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+    // before getting freed. This function only writes within the bounds of `signature`.
+    // And `signature` cannot overlap `res` because we just allocated it.
+    let size = unsafe {
+        AVmAttestationResult_sign(
+            res,
+            message.as_ptr() as *const c_void,
+            message.len(),
+            signature.as_mut_ptr() as *mut c_void,
+            signature.len(),
+        )
+    };
+    ensure!(size <= signature.len());
+    signature.truncate(size);
+    Ok(signature.into_boxed_slice())
+}
+
+fn status_to_cstr(status: attestation_status_t) -> &'static CStr {
+    // SAFETY: The function only reads the given enum status and returns a pointer to a
+    // static string.
+    let message = unsafe { AVmAttestationResult_resultToString(status) };
+    // SAFETY: The pointer returned by `AVmAttestationResult_resultToString` is guaranteed to
+    // point to a valid C String that lives forever.
+    unsafe { CStr::from_ptr(message) }
+}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 771863b..62df47d 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -38,6 +38,7 @@
     IVirtualizationService::FEATURE_MULTI_TENANT,
     IVirtualizationService::FEATURE_VENDOR_MODULES,
     IVirtualizationService::FEATURE_DICE_CHANGES,
+    IVirtualizationService::FEATURE_REMOTE_ATTESTATION,
     MemoryTrimLevel::MemoryTrimLevel,
     Partition::Partition,
     PartitionType::PartitionType,
@@ -307,6 +308,7 @@
         match feature {
             FEATURE_DICE_CHANGES => Ok(cfg!(dice_changes)),
             FEATURE_MULTI_TENANT => Ok(cfg!(multi_tenant)),
+            FEATURE_REMOTE_ATTESTATION => Ok(cfg!(remote_attestation)),
             FEATURE_VENDOR_MODULES => Ok(cfg!(vendor_modules)),
             _ => {
                 warn!("unknown feature {feature}");
@@ -314,6 +316,10 @@
             }
         }
     }
+
+    fn enableTestAttestation(&self) -> binder::Result<()> {
+        GLOBAL_SERVICE.enableTestAttestation()
+    }
 }
 
 impl VirtualizationService {
@@ -1391,8 +1397,8 @@
         Ok(sk.map(|s| BnSecretkeeper::new_binder(SecretkeeperProxy(s), BinderFeatures::default())))
     }
 
-    fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
-        GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32)
+    fn requestAttestation(&self, csr: &[u8], test_mode: bool) -> binder::Result<Vec<Certificate>> {
+        GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32, test_mode)
     }
 }
 
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 92a5812..7962bc3 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -24,6 +24,7 @@
 interface IVirtualizationService {
     const String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
     const String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+    const String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
     const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
 
     /**
@@ -73,4 +74,10 @@
 
     /** Returns whether given feature is enabled. */
     boolean isFeatureEnabled(in String feature);
+
+    /**
+     * Provisions a key pair for the VM attestation testing, a fake certificate will be
+     * associated to the fake key pair when the VM requests attestation in testing mode.
+     */
+    void enableTestAttestation();
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index dd94526..abfc45a 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -61,10 +61,20 @@
      *                     attested is owned by this app.
      *                     The uniqueness of the UID ensures that no two VMs owned by different apps
      *                     are able to correlate keys.
+     * @param testMode Whether the request is for testing purposes.
      * @return A sequence of DER-encoded X.509 certificates that make up the attestation
      *         key's certificate chain. The attestation key is provided in the CSR.
      */
-    Certificate[] requestAttestation(in byte[] csr, int requesterUid);
+    Certificate[] requestAttestation(in byte[] csr, int requesterUid, in boolean testMode);
+
+    /**
+     * Provisions a key pair for the VM attestation testing, a fake certificate will be
+     * associated to the fake key pair when the VM requests attestation in testing mode.
+     *
+     * The provisioned key pair will be used in the subsequent call to {@link #requestAttestation}
+     * with testMode set to true.
+     */
+    void enableTestAttestation();
 
     /**
      * Get a list of assignable devices.
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index cf91302..6806a5c 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -51,10 +51,11 @@
      * Requests a certificate chain for the provided certificate signing request (CSR).
      *
      * @param csr The certificate signing request.
+     * @param testMode Whether the request is for test purposes.
      * @return A sequence of DER-encoded X.509 certificates that make up the attestation
      *         key's certificate chain. The attestation key is provided in the CSR.
      */
-    Certificate[] requestAttestation(in byte[] csr);
+    Certificate[] requestAttestation(in byte[] csr, in boolean testMode);
 
     /**
      * Request connection to Secretkeeper. This is used by pVM to store Anti-Rollback protected
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index a1a1fb9..d0c5d4a 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -16,7 +16,8 @@
 
 use crate::{get_calling_pid, get_calling_uid, REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME};
 use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
-use crate::rkpvm::request_attestation;
+use crate::rkpvm::{request_attestation, generate_ecdsa_p256_key_pair};
+use crate::remote_provisioning;
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
 use android_system_virtualizationservice::{
@@ -38,6 +39,7 @@
 use anyhow::{anyhow, ensure, Context, Result};
 use avflog::LogResult;
 use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong, IntoBinderResult};
+use service_vm_comm::Response;
 use lazy_static::lazy_static;
 use libc::VMADDR_CID_HOST;
 use log::{error, info, warn};
@@ -73,7 +75,73 @@
 
 const CHUNK_RECV_MAX_LEN: usize = 1024;
 
+/// The fake certificate is used for testing only when a client VM requests attestation in test
+/// mode, it is a single certificate extracted on an unregistered device for testing.
+/// Here is the snapshot of the certificate:
+///
+/// ```
+/// Certificate:
+/// Data:
+/// Version: 3 (0x2)
+/// Serial Number:
+///     59:ae:50:98:95:e1:34:25:f1:21:93:c0:4c:e5:24:66
+/// Signature Algorithm: ecdsa-with-SHA256
+/// Issuer: CN = Droid Unregistered Device CA, O = Google Test LLC
+/// Validity
+///     Not Before: Feb  5 14:39:39 2024 GMT
+///     Not After : Feb 14 14:39:39 2024 GMT
+/// Subject: CN = 59ae509895e13425f12193c04ce52466, O = TEE
+/// Subject Public Key Info:
+///     Public Key Algorithm: id-ecPublicKey
+///         Public-Key: (256 bit)
+///         pub:
+///             04:30:32:cd:95:12:b0:71:8b:b7:14:44:26:58:d5:
+///             82:8c:25:55:2c:6d:ef:98:e3:4f:88:d0:74:82:09:
+///             3e:8d:6c:f0:f2:18:d5:83:0e:0d:f2:ce:c5:15:38:
+///             e5:6a:e6:4d:4d:95:15:b7:24:e7:cb:4b:63:42:21:
+///             bc:36:c6:0a:d8
+///         ASN1 OID: prime256v1
+///         NIST CURVE: P-256
+/// X509v3 extensions:
+///  ...
+/// ```
+const FAKE_CERTIFICATE_FOR_TESTING: &[u8] = &[
+    0x30, 0x82, 0x01, 0xee, 0x30, 0x82, 0x01, 0x94, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x59,
+    0xae, 0x50, 0x98, 0x95, 0xe1, 0x34, 0x25, 0xf1, 0x21, 0x93, 0xc0, 0x4c, 0xe5, 0x24, 0x66, 0x30,
+    0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x41, 0x31, 0x25, 0x30,
+    0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x44, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x55, 0x6e,
+    0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63,
+    0x65, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
+    0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x4c, 0x4c, 0x43, 0x30, 0x1e,
+    0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x30, 0x35, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x17,
+    0x0d, 0x32, 0x34, 0x30, 0x32, 0x31, 0x34, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x30, 0x39,
+    0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x35, 0x39, 0x61, 0x65, 0x35,
+    0x30, 0x39, 0x38, 0x39, 0x35, 0x65, 0x31, 0x33, 0x34, 0x32, 0x35, 0x66, 0x31, 0x32, 0x31, 0x39,
+    0x33, 0x63, 0x30, 0x34, 0x63, 0x65, 0x35, 0x32, 0x34, 0x36, 0x36, 0x31, 0x0c, 0x30, 0x0a, 0x06,
+    0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, 0x54, 0x45, 0x45, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a,
+    0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
+    0x03, 0x42, 0x00, 0x04, 0x30, 0x32, 0xcd, 0x95, 0x12, 0xb0, 0x71, 0x8b, 0xb7, 0x14, 0x44, 0x26,
+    0x58, 0xd5, 0x82, 0x8c, 0x25, 0x55, 0x2c, 0x6d, 0xef, 0x98, 0xe3, 0x4f, 0x88, 0xd0, 0x74, 0x82,
+    0x09, 0x3e, 0x8d, 0x6c, 0xf0, 0xf2, 0x18, 0xd5, 0x83, 0x0e, 0x0d, 0xf2, 0xce, 0xc5, 0x15, 0x38,
+    0xe5, 0x6a, 0xe6, 0x4d, 0x4d, 0x95, 0x15, 0xb7, 0x24, 0xe7, 0xcb, 0x4b, 0x63, 0x42, 0x21, 0xbc,
+    0x36, 0xc6, 0x0a, 0xd8, 0xa3, 0x76, 0x30, 0x74, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+    0x16, 0x04, 0x14, 0x39, 0x81, 0x41, 0x0a, 0xb9, 0xf3, 0xf4, 0x5b, 0x75, 0x97, 0x4a, 0x46, 0xd6,
+    0x30, 0x9e, 0x1d, 0x7a, 0x3b, 0xec, 0xa8, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+    0x30, 0x16, 0x80, 0x14, 0x82, 0xbd, 0x00, 0xde, 0xcb, 0xc5, 0xe7, 0x72, 0x87, 0x3d, 0x1c, 0x0a,
+    0x1e, 0x78, 0x4f, 0xf5, 0xd3, 0xc1, 0x3e, 0xb8, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+    0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+    0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x11, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+    0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x1e, 0x04, 0x03, 0xa1, 0x01, 0x08, 0x30, 0x0a, 0x06, 0x08,
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00,
+    0xae, 0xd8, 0x40, 0x9e, 0x37, 0x3e, 0x5c, 0x9c, 0xe2, 0x93, 0x3d, 0x8c, 0xf7, 0x05, 0x10, 0xe7,
+    0xd1, 0x2b, 0x87, 0x8a, 0xee, 0xd6, 0x1e, 0x6c, 0x3b, 0xd2, 0x91, 0x3e, 0xa5, 0xdf, 0x91, 0x20,
+    0x02, 0x20, 0x7f, 0x0f, 0x29, 0x54, 0x60, 0x80, 0x07, 0x50, 0x5f, 0x56, 0x6b, 0x9f, 0xe0, 0x94,
+    0xb4, 0x3f, 0x3b, 0x0f, 0x61, 0xa0, 0x33, 0x40, 0xe6, 0x1a, 0x42, 0xda, 0x4b, 0xa4, 0xfd, 0x92,
+    0xb9, 0x0f,
+];
+
 lazy_static! {
+    static ref FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING: Mutex<Option<Vec<u8>>> = Mutex::new(None);
     static ref VFIO_SERVICE: Strong<dyn IVfioHandler> =
         wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())
             .expect("Could not connect to VfioHandler");
@@ -169,10 +237,41 @@
         Ok(cids)
     }
 
+    fn enableTestAttestation(&self) -> binder::Result<()> {
+        check_manage_access()?;
+        check_use_custom_virtual_machine()?;
+        if !cfg!(remote_attestation) {
+            return Err(Status::new_exception_str(
+                ExceptionCode::UNSUPPORTED_OPERATION,
+                Some(
+                    "enableTestAttestation is not supported with the remote_attestation \
+                     feature disabled",
+                ),
+            ))
+            .with_log();
+        }
+        let res = generate_ecdsa_p256_key_pair()
+            .context("Failed to generate ECDSA P-256 key pair for testing")
+            .with_log()
+            .or_service_specific_exception(-1)?;
+        match res {
+            Response::GenerateEcdsaP256KeyPair(key_pair) => {
+                FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
+                    .lock()
+                    .unwrap()
+                    .replace(key_pair.key_blob.to_vec());
+                Ok(())
+            }
+            _ => Err(remote_provisioning::to_service_specific_error(res)),
+        }
+        .with_log()
+    }
+
     fn requestAttestation(
         &self,
         csr: &[u8],
         requester_uid: i32,
+        test_mode: bool,
     ) -> binder::Result<Vec<Certificate>> {
         check_manage_access()?;
         if !cfg!(remote_attestation) {
@@ -186,14 +285,31 @@
             .with_log();
         }
         info!("Received csr. Requestting attestation...");
-        let attestation_key = get_rkpd_attestation_key(
-            REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
-            requester_uid as u32,
-        )
-        .context("Failed to retrieve the remotely provisioned keys")
-        .with_log()
-        .or_service_specific_exception(-1)?;
-        let mut certificate_chain = split_x509_certificate_chain(&attestation_key.encodedCertChain)
+        let (key_blob, certificate_chain) = if test_mode {
+            check_use_custom_virtual_machine()?;
+            info!("Using the fake key blob for testing...");
+            (
+                FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
+                    .lock()
+                    .unwrap()
+                    .clone()
+                    .ok_or_else(|| anyhow!("No key blob for testing"))
+                    .with_log()
+                    .or_service_specific_exception(-1)?,
+                FAKE_CERTIFICATE_FOR_TESTING.to_vec(),
+            )
+        } else {
+            info!("Retrieving the remotely provisioned keys from RKPD...");
+            let attestation_key = get_rkpd_attestation_key(
+                REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+                requester_uid as u32,
+            )
+            .context("Failed to retrieve the remotely provisioned keys")
+            .with_log()
+            .or_service_specific_exception(-1)?;
+            (attestation_key.keyBlob, attestation_key.encodedCertChain)
+        };
+        let mut certificate_chain = split_x509_certificate_chain(&certificate_chain)
             .context("Failed to split the remotely provisioned certificate chain")
             .with_log()
             .or_service_specific_exception(-1)?;
@@ -206,7 +322,7 @@
         }
         let certificate = request_attestation(
             csr.to_vec(),
-            attestation_key.keyBlob,
+            key_blob,
             certificate_chain[0].encodedCertificate.clone(),
         )
         .context("Failed to request attestation")
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index 40f54db..c2c04df 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -145,7 +145,7 @@
     }
 }
 
-fn to_service_specific_error(response: Response) -> Status {
+pub(crate) fn to_service_specific_error(response: Response) -> Status {
     match response {
         Response::Err(e) => match e {
             RequestProcessingError::InvalidMac => {
diff --git a/vm_payload/include-restricted/vm_payload_restricted.h b/vm_payload/include-restricted/vm_payload_restricted.h
index 15c37ed..d7324a8 100644
--- a/vm_payload/include-restricted/vm_payload_restricted.h
+++ b/vm_payload/include-restricted/vm_payload_restricted.h
@@ -55,4 +55,25 @@
  */
 size_t AVmPayload_getDiceAttestationCdi(void* _Nullable data, size_t size);
 
+/**
+ * Requests attestation for the VM for testing only.
+ *
+ * This function is only for testing and will not return a real RKP server backed
+ * certificate chain.
+ *
+ * Prior to calling this function, the caller must provision a key pair to be used in
+ * this function with `VirtualMachineManager#enableTestAttestation`.
+ *
+ * \param challenge A pointer to the challenge buffer.
+ * \param challenge_size size of the challenge. The maximum supported challenge size is
+ *          64 bytes. The status ATTESTATION_ERROR_INVALID_CHALLENGE will be returned if
+ *          an invalid challenge is passed.
+ * \param result The remote attestation result will be filled here if the attestation
+ *               succeeds. The result remains valid until it is freed with
+ *              `AVmPayload_freeAttestationResult`.
+ */
+attestation_status_t AVmPayload_requestAttestationForTesting(
+        const void* _Nonnull challenge, size_t challenge_size,
+        struct AVmAttestationResult* _Nullable* _Nonnull result) __INTRODUCED_IN(__ANDROID_API_V__);
+
 __END_DECLS
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 3483e1d..af755c9 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -211,7 +211,7 @@
  * If `size` is smaller than the total size of the signature, the signature will be
  * truncated to this `size`.
  *
- * \return The total size of the signature.
+ * \return The size of the signature, or the size needed if the supplied buffer is too small.
  *
  * [RFC 6979]: https://datatracker.ietf.org/doc/html/rfc6979
  */
diff --git a/vm_payload/libvm_payload.map.txt b/vm_payload/libvm_payload.map.txt
index 975a5a3..caf8f84 100644
--- a/vm_payload/libvm_payload.map.txt
+++ b/vm_payload/libvm_payload.map.txt
@@ -8,6 +8,7 @@
     AVmPayload_getApkContentsPath;       # systemapi introduced=UpsideDownCake
     AVmPayload_getEncryptedStoragePath;  # systemapi introduced=UpsideDownCake
     AVmPayload_requestAttestation;       # systemapi introduced=VanillaIceCream
+    AVmPayload_requestAttestationForTesting; # systemapi introduced=VanillaIceCream
     AVmAttestationResult_getPrivateKey;  # systemapi introduced=VanillaIceCream
     AVmAttestationResult_sign;           # systemapi introduced=VanillaIceCream
     AVmAttestationResult_free;           # systemapi introduced=VanillaIceCream
diff --git a/vm_payload/src/lib.rs b/vm_payload/src/lib.rs
index 7978059..6188b21 100644
--- a/vm_payload/src/lib.rs
+++ b/vm_payload/src/lib.rs
@@ -39,6 +39,9 @@
 };
 use vm_payload_status_bindgen::attestation_status_t;
 
+/// Maximum size of an ECDSA signature for EC P-256 key is 72 bytes.
+const MAX_ECDSA_P256_SIGNATURE_SIZE: usize = 72;
+
 lazy_static! {
     static ref VM_APK_CONTENTS_PATH_C: CString =
         CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed");
@@ -273,9 +276,6 @@
 /// Behavior is undefined if any of the following conditions are violated:
 ///
 /// * `challenge` must be [valid] for reads of `challenge_size` bytes.
-/// * `res` must be [valid] to write the attestation result.
-/// * The region of memory beginning at `challenge` with `challenge_size` bytes must not
-///  overlap with the region of memory `res` points to.
 ///
 /// [valid]: ptr#safety
 #[no_mangle]
@@ -284,6 +284,60 @@
     challenge_size: usize,
     res: &mut *mut AttestationResult,
 ) -> attestation_status_t {
+    // SAFETY: The caller guarantees that `challenge` is valid for reads and `res` is valid
+    // for writes.
+    unsafe {
+        request_attestation(
+            challenge,
+            challenge_size,
+            false, // test_mode
+            res,
+        )
+    }
+}
+
+/// Requests the remote attestation of the client VM for testing.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `challenge` must be [valid] for reads of `challenge_size` bytes.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_requestAttestationForTesting(
+    challenge: *const u8,
+    challenge_size: usize,
+    res: &mut *mut AttestationResult,
+) -> attestation_status_t {
+    // SAFETY: The caller guarantees that `challenge` is valid for reads and `res` is valid
+    // for writes.
+    unsafe {
+        request_attestation(
+            challenge,
+            challenge_size,
+            true, // test_mode
+            res,
+        )
+    }
+}
+
+/// Requests the remote attestation of the client VM.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `challenge` must be [valid] for reads of `challenge_size` bytes.
+///
+/// [valid]: ptr#safety
+unsafe fn request_attestation(
+    challenge: *const u8,
+    challenge_size: usize,
+    test_mode: bool,
+    res: &mut *mut AttestationResult,
+) -> attestation_status_t {
     initialize_logging();
     const MAX_CHALLENGE_SIZE: usize = 64;
     if challenge_size > MAX_CHALLENGE_SIZE {
@@ -297,7 +351,7 @@
         unsafe { std::slice::from_raw_parts(challenge, challenge_size) }
     };
     let service = unwrap_or_abort(get_vm_payload_service());
-    match service.requestAttestation(challenge) {
+    match service.requestAttestation(challenge, test_mode) {
         Ok(attestation_res) => {
             *res = Box::into_raw(Box::new(attestation_res));
             attestation_status_t::ATTESTATION_OK
@@ -400,27 +454,36 @@
     data: *mut u8,
     size: usize,
 ) -> usize {
+    // A DER-encoded ECDSA signature can have varying sizes even with the same EC Key and message,
+    // due to the encoding of the random values r and s that are part of the signature.
+    if size == 0 {
+        return MAX_ECDSA_P256_SIGNATURE_SIZE;
+    }
     if message_size == 0 {
         panic!("Message to be signed must not be empty.")
     }
     // SAFETY: See the requirements on `message` above.
     let message = unsafe { std::slice::from_raw_parts(message, message_size) };
     let signature = unwrap_or_abort(try_ecdsa_sign(message, &res.privateKey));
-    if size != 0 {
-        let data = NonNull::new(data).expect("data must not be null when size > 0");
-        // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
-        // the length of either buffer, and the caller ensures that `signature` cannot overlap
-        // `data`. We allow data to be null, which is never valid, but only if size == 0
-        // which is checked above.
-        unsafe {
-            ptr::copy_nonoverlapping(
-                signature.as_ptr(),
-                data.as_ptr(),
-                std::cmp::min(signature.len(), size),
-            )
-        };
+    let data = NonNull::new(data).expect("data must not be null when size > 0");
+    // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+    // the length of either buffer, and the caller ensures that `signature` cannot overlap
+    // `data`. We allow data to be null, which is never valid, but only if size == 0
+    // which is checked above.
+    unsafe {
+        ptr::copy_nonoverlapping(
+            signature.as_ptr(),
+            data.as_ptr(),
+            usize::min(signature.len(), size),
+        )
+    };
+    if size < signature.len() {
+        // If the buffer is too small, return the maximum size of the signature to allow the caller
+        // to allocate a buffer large enough to call this function again.
+        MAX_ECDSA_P256_SIGNATURE_SIZE
+    } else {
+        signature.len()
     }
-    signature.len()
 }
 
 fn try_ecdsa_sign(message: &[u8], der_encoded_ec_private_key: &[u8]) -> Result<Vec<u8>> {